Python基础
介绍
输入和输出
- 所有的通过input获取的数据,都是字符串类型
- print()
变量
- 程序就是用来处理数据的,而变量就是用来存储数据的
- 变量在程序中就是用一个变量名表示了,变量名必须是大小写英文、数字和
_
的组合,且不能用数字开头 - 可以使用type(变量的名字),来查看变量的类型
- 在Python中,等号
=
是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量- 这种变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。
常量
Python中,通常用全部大写的变量名表示常量
- 比如常用的数学常数π就是一个常量,但事实上
PI
仍然是一个变量,Python根本没有任何机制保证PI
不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法
数据类型
可变对象与不可变对象
- 可变对象
- 列表list,字典dict,集合set
- 特点: 这个些数据类型,是可以直接在原对象上进行修改数据,修改完成后,并不影响原对象地址
- 不可变对象
- 数字 int,字符串 str,浮点数 float,布尔类型 bool,元组 tuple
- 特点: 这些数据都是不可以直接修改的,如果在修改或赋值时,都会开辟一个新空间
- 对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
整数int
浮点数float
- 浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的
- 整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(除法难道也是精确的?是的!),而浮点数运算则可能会有四舍五入的误差。
字符串string
字符串是以单引号
'
或双引号"
括起来的任意文本-
如果字符串内部既包含
'
又包含"
怎么办?可以用转义字符\
来标识- 转义字符
\
可以转义很多字符,比如\n
表示换行,\t
表示制表符,字符\
本身也要转义,所以\\
表示的字符就是\
- Python还允许用
r''
表示''
内部的字符串默认不转义
- 转义字符
字符串与编码
Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
ASCII编码是1个字节,而Unicode编码通常是2个字节。
UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言
- Python提供了
ord()
函数获取字符的整数表示, -
chr()
函数把编码转换为对应的字符
>>> ord('A')
65
>>> ord('中')
20013
-
Python对
bytes
类型的数据用带b
前缀的单引号或双引号表示:x = b'ABC'
纯英文的
str
可以用ASCII
编码为bytes
,内容是一样的,含有中文的str
可以用UTF-8
编码为bytes
,用encode()方法读到的数据就是
bytes
。要把bytes
变为str
,就需要用decode()
方法
列表,字典,集合
布尔值
- 布尔值只有
True
、False
两种值
空值None
空值是Python里一个特殊的值,用None
表示。None
不能理解为0
,因为0
是有意义的,而None
是一个特殊的空值。
数据类型的转换
函数 | 说明 |
---|---|
int(x [,base ]) | 将x转换为一个整数 |
float(x ) | 将x转换为一个浮点数 |
complex(real [,imag ]) | 创建一个复数,real为实部,imag为虚部 |
str(x ) | 将对象 x 转换为字符串 |
repr(x ) | 将对象 x 转换为表达式字符串 |
eval(str ) | 用来计算在字符串中的有效Python表达式,并返回一个对象 |
tuple(s ) | 将序列 s 转换为一个元组 |
list(s ) | 将序列 s 转换为一个列表 |
chr(x ) | 将一个整数转换为一个Unicode字符 |
ord(x ) | 将一个字符转换为它的ASCII整数值 |
hex(x ) | 将一个整数转换为一个十六进制字符串 |
oct(x ) | 将一个整数转换为一个八进制字符串 |
bin(x ) | 将一个整数转换为一个二进制字符串 |
运算符
运算符 | 描述 | 实例 |
---|---|---|
+ | 加 | 两个对象相加 a + b 输出结果 30 |
- | 减 | 得到负数或是一个数减去另一个数 a - b 输出结果 -10 |
* | 乘 | 两个数相乘或是返回一个被重复若干次的字符串 a * b 输出结果 200 |
/ | 除 | b / a 输出结果 2 |
// | 取整除 | 返回商的整数部分 9//2 输出结果 4 , 9.0//2.0 输出结果 4.0 |
% | 取余 | 返回除法的余数 b % a 输出结果 0 |
** | 指数 | a**b 为10的20次方, 输出结果 100000000000000000000 |
- 复合赋值运算符
运算符 | 描述 | 实例 |
---|---|---|
+= | 加法赋值运算符 | c += a 等效于 c = c + a |
-= | 减法赋值运算符 | c -= a 等效于 c = c - a |
*= | 乘法赋值运算符 | c *= a 等效于 c = c * a |
/= | 除法赋值运算符 | c /= a 等效于 c = c / a |
%= | 取模赋值运算符 | c %= a 等效于 c = c % a |
**= | 幂赋值运算符 | c **= a 等效于 c = c ** a |
//= | 取整除赋值运算符 | c //= a 等效于 c = c // a |
字符串string
字符串输出
格式化操作符
格式符号 | 转换 |
---|---|
%c | 字符 |
%s | 字符串 |
%d | 有符号十进制整数 |
%u | 无符号十进制整数 |
%o | 八进制整数 |
%x | 十六进制整数(小写字母0x) |
%X | 十六进制整数(大写字母0X) |
%f | 浮点数 |
%e | 科学计数法(小写'e') |
%E | 科学计数法(大写“E”) |
%g | %f和%e 的简写 |
%G | %f和%E的简写 |
age = 18
name = "xiaohua"
print("我的姓名是%s, 年龄是%d" % (name, age))
f-strings
f-strings 以字母 'f' 或 'F' 为前缀, 格式化字符串使用一对单引号、双引号、三单引号、三双引号
name = '峰哥'
age = 33
format_string1 = f'我的名字是 {name}, 我的年龄是 {age}'
format()
使用字符串的format()
方法,它会用传入的参数依次替换字符串内的占位符{0}
、{1}
……
>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'
常见操作
如有字符串mystr = 'hello world itcast and itcastcpp'
find & rfind
检测 str 是否包含在 mystr中,如果是返回开始的索引值,否则返回-1
mystr.find(str, start=0, end=len(mystr))
mystr.find('it', 0, 10)
index & rindex
跟find()方法一样,只不过如果str不在 mystr中会报一个异常.
mystr.index(str, start=0, end=len(mystr))
count
返回 str在start和end之间 在 mystr里面出现的次数
mystr.count(str, start=0, end=len(mystr))
replace
把 mystr 中的 str1 替换成 str2,如果 count 指定,则替换不超过 count 次.
mystr.replace(str1, str2, mystr.count(str1))
capitalize
把字符串的第一个字符大写
mystr.capitalize()
title
把字符串的每个单词首字母大写
>>> a = "hello itcast"
>>> a.title()
'Hello Itcast'
lower
转换 mystr 中所有大写字符为小写
mystr.lower()
upper
转换 mystr 中的小写字母为大写
mystr.upper()
startswith
检查字符串是否是以 "hello" 开头, 是则返回 True,否则返回 False
mystr.startswith("hello")
endswith
检查字符串是否以"world"结束,如果是返回True,否则返回 False.
mystr.endswith("world")
ljust & rjust
返回一个原字符串左对齐,并使用空格填充至长度 width 的新字符串
mystr.ljust(width)
center
返回一个原字符串居中,并使用空格填充至长度 width 的新字符串
mystr.center(width)
lstrip & rstrip & strip
删除 mystr 左边/右边/两边的空白字符或指定字符
mystr.lstrip()
mystr.lstrip(“#”)
split
以 str 为分隔符切片 mystr,如果 maxsplit有指定值,则仅分隔 maxsplit 个子字符串
mystr.split(" ", 2)
splitlines
按照行分隔,返回一个包含各行作为元素的列表
mystr.splitlines()
partition & rpartition
把mystr以str分割成三部分,str前,str和str后
mystr.partition(str)
join
alist 中每个元素用str连接,构造出一个新的字符串
str = "_"
alist = ['1', '2', '3']
str.join(alist)
>>> ‘1_2_3’
isalpha & isdigit & isalnum & isspace
如果 mystr 所有字符都是字母/数字/字母或数字/空格 则返回 True,否则返回 False
mystr.isalpha()
列表list
Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。
相关操作
添加元素
- append(),向列表末尾添加元素
- extend(),可以将另一个列表中的元素逐一添加到列表中
- insert(index, item),在指定位置index前插入元素object
修改元素
- li[i] = x,把索引为i的元素修改为x
查找元素
所谓的查找,就是看看指定的元素是否存在
python中查找的常用方法为:
- in(存在),如果存在那么结果为true,否则为false
- not in(不存在),如果不存在那么结果为true,否则false
index查找(与字符串查找相同)
>>> a = ['a', 'b', 'c', 'a', 'b']
>>> a.index('a', 1, 3) # 注意是左闭右开区间
删除元素
-
del:根据下标进行删除
del li[i] # 删除列表li中索引为i元素
-
pop():按索引删除一个元素,删除时会返回被删除的元素
li.pop() # 删除最后一个元素 li.pop(i) # 删除列表li中索引为i元素
-
remove:根据元素的值进行删除,删除第一个符合条件的值
>>> str=[1,2,3,4,5,2,6] >>> str.remove(2) >>> str [1, 3, 4, 5, 2, 6]
个数len()
用len()
函数可以获得list元素的个数
排序sort()
sort方法是将list按特定顺序重新排列,默认为由小到大,参数reverse=True可改为倒序,由大到小。
遍历
-
使用for循环
- 通过for ... in ... 我们可以遍历字符串、列表、元组、字典等
namesList = ['xiaoWang','xiaoZhang','xiaoHua'] for name in namesList: print(name)
-
使用while循环
namesList = ['xiaoWang','xiaoZhang','xiaoHua'] i = 0 while i < len(namesList): print(namesList[i]) i+ = 1
元组tuple
- 另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改,元组使用小括号,列表使用方括号。
- 因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。
- 元组只有一个元素
t = (1,)
小结
-
元组修改元素
将元组转换为列表并更改值
-
通过现有字符串的片段在构造一个新的字符串的方式来等同于更新元组操作
tuple_1=(1,2,3,"ewang","demo") #通过索引更新 tuple_1=tuple_1[0],tuple_1[2],tuple_1[4] print tuple_1 #通过切片更新 tuple_1=tuple_1[0:2] print tuple_1 # 添加元素 tuple_1 = tuple_1 + (4,)
-
列表和元组区别
- 列表是可变类型,元组是不可变类型,可变类型值,值发生变化地址不变;不可变类型指,值发生改变,地址也发生改变,值指向一个新的值
切片
切片是指对操作的对象截取其中一部分的操作。
字符串、列表、元组都支持切片操作。
语法
- [起始:结束:步长]
- 注意:选取的区间从"起始"位开始,到"结束"位的前一位结束(不包含结束位本身)(左闭右开),步长表示选取间隔。
使用
s = "abcdefghijk"
print(s[0:5:1])
print(s[0:5:2])
print(s[3:6]) # 默认步长可以不写,默认为1
print(s[:5]) # 开始索引也可以不写,默认从头开始
print(s[5:]) # 结束也可以不写,默认到最后
print(s[:]) # 全默认,默认截取整串
print(s)
print(s[10:20]) # 切片时不会出现下标越界错误
# 切片的下标还可是以负数
# 负数是,是从右向左切片,起始下标为 -1
print(s[-1:-5])
print(s[-1:-5:-1])
# 特殊需要记住的切片方式
# 使用切片实现字符串逆序
print(s[::-1])
字典dict
使用键-值(key-value)存储,具有极快的查找速度。
相关操作
查看元素
-
通过键访问值
di['key'] = value # 若访问不存在的键,则会报错
-
在我们不确定字典中是否存在某个键而又想获取其值时,可以使用get方法,还可以设置默认值
>>> age = info.get('age') >>> age #'age'键不存在,所以age为None >>> type(age)
>>> age = info.get('age', 18) # 若info中不存在'age'这个键,就返回默认值18 >>> age 18
修改元素
字典的每个元素中的数据是可以修改的,只要通过key找到,即可修改
di['a'] = x # 把键为a的值修改为x
添加元素
在使用 变量名['键'] = 数据 时,这个“键”在字典中,不存在,那么就会新增这个元素
di['newkey'] = newvalue
删除元素
pop('key'):按键删除一个元素,删除时会返回被删除元素的值
del di['key']:删除指定键
del di:删除整个字典对象
-
clear():清空整个字典内容
di.clear() >>>{}
个数len()
len(di)
keys()
返回一个包含字典所有KEY的列表
values()
返回一个包含字典所有value的列表
items()
返回一个包含所有(键,值)元祖的列表
di = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
di.keys()
>>> ['Michael', 'Bob', 'Tracy']
di.values()
>>> [95, 75, 85]
di.items()
>>> [('Michael', 95), ('Bob', 75), ('Tracy', 85)]
遍历
遍历键key
di = {'Michael': 95, 'Bob': 75}
for key in di.keys():
print(key)
>>>
Michael
Bob
遍历值value
di = {'Michael': 95, 'Bob': 75}
for value in di.values():
print(value)
>>>
95
75
遍历项(元素)
di = {'Michael': 95, 'Bob': 75}
for item in di.items():
print(item)
>>>
('Michael', 95)
('Bob', 75)
遍历字典的key-value
di = {'Michael': 95, 'Bob': 75}
for key,value in di.items():
print('%s, %s'% (key,value))
>>>
Michael, 95
Bob, 75
小结
dict内部存放的顺序和key放入的顺序是没有关系的。
-
和list比较,dict有以下几个特点:
- 查找和插入的速度极快,不会随着key的增加而变慢;
- 需要占用大量的内存,内存浪费多。
而list相反:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
所以,dict是用空间来换取时间的一种方法。
-
dict的key必须是不可变对象。
- 这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)。
- 要保证hash的正确性,作为key的对象就不能变。字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key
集合set
set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
相关操作
添加元素
通过add(key)
方法可以添加元素到set中,可以重复添加,但不会有效果
s.add(key)
删除元素
通过remove(key)
方法可以删除元素
s.remove(key)
clear()清空元素
交集(&)
交集指的是两个不同的集合中相同的集合打印出来
>>> a = set('abc')
>>> b = set('cdef')
>>> a & b
set(['c'])
并集(|)
将两个集合中所有元素合并到一起
>>> a = set('abc')
>>> b = set('cdef')
>>> a | b
set(['a', 'c', 'b', 'e', 'd', 'f'])
差集(-)
差集指的是两个没有集合中不同的元素,前面的集合为准
>>> a = set('abc')
>>> b = set('cdef')
>>> a - b
set(['a', 'b'])
对称差集(^)
集合A与集合B中所有不属于A∩B的元素的集合
>>> a = set('abc')
>>> b = set('cdef')
>>> a ^ b
set(['a', 'b', 'd' 'e', 'd', 'f'])
[图片上传失败...(image-90cfb9-1620306245275)]
判断语句
循环语句
break和continue
- break的作用:立刻结束break**所在的循环
- continue的作用:用来结束本次循环,紧接着执行下一次的循环
- break/continue只能用在循环中,除此以外不能单独使用
- break/continue在嵌套循环中,只对最近的一层循环起作用
高级特性
列表生成式
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
# 还可以使用两层循环,可以生成全排列
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
# 把一个list中所有的字符串变成小写
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']
迭代与迭代器
迭代是访问集合元素的一种方式,如果给定一个list或tuple,我们可以通过for
循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
可以直接作用于
for
循环的对象统称为可迭代对象:Iterable
。可以被
next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。它们表示一个惰性计算的序列生成器都是
Iterator
对象,但list
、dict
、str
虽然是可迭代对象Iterable
,却不是迭代器Iterator
。把
list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数-
可以直接作用于
for
循环的数据类型有以下几种:一类是集合数据类型,如
list
、tuple
、dict
、set
、str
等;一类是
generator
,包括生成器和带yield
的generator function。这些可以直接作用于
for
循环的对象统称为可迭代对象:Iterable
。 可以使用
isinstance()
判断一个对象是否是Iterable
对象
生成器
根据程序员制定的规则循环生成数据,当条件不成立时则生成数据结束。数据不是一次性全部生成出来,而是使用一个,再生成一个,可以节约大量的内存。
创建方式
-
生成器推导式
-
只要把一个列表生成式的
[]
改成()
,就创建了一个generator>>> g = (x * x for x in range(10)) >>> g
at 0x1022ef630> >>> next(g) 0 >>> next(g) 1 …… >>> next(g) 81 >>> next(g) Traceback (most recent call last): File " ", line 1, in StopIteration generator保存的是算法,每次调用
next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。也可使用
for
循环,因为generator也是可迭代对象;我们创建了一个generator后,基本上永远不会调用next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。
-
-
yield 关键字
-
只要在def函数里面看到有 yield 关键字那么就是生成器
def get_value(n): for i in range(n): print('生成第一个值') # yield 关键字的作用是将这个函数变成一个生成器对象 # 执行时,解释器遇到 yield 后会中断代码的执行,并返回yield后的数据, # 下一次再执行时,会恢复前面yield中断的状态,继续执行 yield i print('第一个生成完成') g = get_value(4) value = next(g) print(value) value = next(g) print(value)
代码执行到 yield 会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
yield 就是保存当前程序执行状态
-
应用
-
斐波那契数列
def fibonacci(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done' fib = fibonacci(5) # 遍历生成的数据 for value in fib: print(value)
迭代器和生成器的区别
(1)生成器:
生成器本质上就是一个函数,它记住了上一次返回时在函数体中的位置。
对生成器函数的第二次(或第n次)调用,跳转到函数上一次挂起的位置。
而且记录了程序执行的上下文。
生成器不仅“记住”了它的数据状态,生成还记住了程序执行的位置。(2)迭代器
迭代器是一种支持next()操作的对象。它包含了一组元素,当执行next()操作时,返回其中一个元素。
当所有元素都被返回后,再执行next()报异常—StopIteration
生成器一定是可迭代的,也一定是迭代器对象(3)区别:
①生成器是生成元素的,迭代器是访问集合元素的一中方式
②迭代输出生成器的内容
③迭代器是一种支持next()操作的对象
④迭代器(iterator):其中iterator对象表示的是一个数据流,可以把它看做一个有序序列,但我们不能提前知道序列的长度,只有通过nex()函数实现需要计算的下一个数据。可以看做生成器的一个子集。
函数
如果在开发程序时,需要某块代码多次,但是为了提高编写的效率以及代码的重用,所以把具有独立功能的代码块组织为一个小模块,这就是函数
基础
局部变量与全局变量
-
局部变量
- 局部变量,就是在函数内部定义的变量
- 其作用范围是这个函数内部,即只能在这个函数中使用,在函数的外部是不能使用的
- 因为其作用范围只是在自己的函数内部,所以不同的函数可以定义相同名字的局部变量
- 局部变量的作用,为了临时保存数据需要在函数中定义变量来进行存储
- 当函数调用时,局部变量被创建,当函数调用完成后这个变量就不能够使用了
-
全局变量
在函数外边定义的变量叫做
全局变量
全局变量能够在所有的函数中进行访问
当函数内出现局部变量和全局变量相同名字时,函数内部中的
变量名 = 数据
此时理解为定义了一个局部变量,而不是修改全局变量的值-
如果在函数中出现
global 全局变量的名字
那么这个函数中即使出现和全局变量名相同的变量名 = 数据
也理解为对全局变量进行修改,而不是定义局部变量# 可以使用一次global对多个全局变量进行声明 global a, b # 还可以用多次global声明都是可以的 # global a # global b
定义函数
定义一个函数要使用def
语句,依次写出函数名、括号、括号中的参数和冒号:
,然后,在缩进块中编写函数体,函数的返回值用return
语句返回。
函数名也是变量
调用函数
通过 函数名() 即可完成调用
- 每次调用函数时,函数都会从头开始执行,当这个函数中的代码执行完毕后,意味着调用结束了
- 当然了如果函数中执行到了return也会结束函数
- 函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”
参数
默认参数(缺省参数)
- 在定义函数时,函数中的形式参数,被赋值,这个值就是默认值
- 当在函数调用时,如果给定了值,那么就使用给定值,如果没有给定值,那就使用默认值
- ++注意:默认值参数只能出现在参数列表的最右侧**++
位置参数(实参)
- 使用位置参数时,因为类型的原因,那么实参的顺序要和形参的顺序完全一致;
- 当没有默认值的情况下,参数的个数和也要一致
关键词参数(形参)
- 在定义形式参时,每个参数都可以理解成一个key;
使用这个key,可以明确的为当前这个参数进行赋值;使用关键字参数,可以忽略参数的顺序问题
不定长位置参数
- *args 在参数中定义了该形参后,那可以通过 *args 接收多个不确定个数的位置参数
- ++加了星号(*)的变量args会存放所有未命名的变量参数,*args为元组++
不定长关键字参数
- **kwargs 在参数中定义了该形参后,那可以通过 **kwargs 接收多个不确定个数的关键字参数
- 加**的变量kwargs会存放命名参数,即形如key=value的参数, **kwargs为字典.
小结
定义时小括号中的参数,用来接收参数用的,称为 “形参”
调用时小括号中的参数,用来传递给函数用的,称为 “实参”
当全局变量和局部变量同名时,在函数内使用变量,优先使用局部变量;局部变量优先级高于全局变量
-
参数定义的顺序必须是:必选参数、默认参数、不定长位置参数和不定长关键字参数。
def f1(a, b, c=0, *args, **kwargs): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
对于任意函数,都可以通过类似
func(*args, **kw)
的形式调用它,无论它的参数是如何定义的。
高阶函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
map()函数
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
把这个list所有数字转为字符串:
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce()函数
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数
reduce
把结果继续和序列的下一个元素做累积计算:
import functools
my_list = [1, 2, 3, 4, 5]
def f(x1, x2):
return x1 + x2
result = functools.reduce(f, my_list)
print(result)
>>>15
当然求和运算可以直接用Python内建函数sum()
,没必要动用reduce
。
如果要把序列[1, 3, 5, 7, 9]
变换成整数13579
:
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
filter()函数
和map()
类似,filter()
也接收一个函数和一个序列,不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]
注意到filter()
函数返回的是一个Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()
函数获得所有结果并返回list。
sorted()函数
Python内置的sorted()
函数就可以对list进行排序
此外,sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序,例如按绝对值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
给sorted
传入key函数,即可实现忽略大小写的排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
匿名函数lambda
定义的函数没有名字,这样的函数叫做匿名函数.
介绍
格式:lambda [形参1], [形参2], ... : [单行表达式] 或 [函数调用]
-
lambda定义和普通函数的区别:
- lambda 没有函数名,不必担心函数名冲突
- lambda 参数列表外没有括号
- lambda 函数体中,只能实现简单的表达式计算或函数调用
- lambda 函数体中,不能使用Return,if,while,for-in 这些都不行,不用写
return
,返回值就是该表达式的结果。 - lambda 函数体中,可以使用if 实现的三目运算符.
应用
-
定义简单的单行函数
my_function = lambda a, b: a + b
-
作为函数的参数进行传递
解决目标: 1、提高函数的通用性 2、减少代码量
-
例子
#现有字典 d={‘a’:24,‘g’:52,‘i’:12,‘k’:33}请按字典中的 value 值进行排序? sorted(d.items(),key = lambda x:x[1]) #一句话解决阶乘函数 reduce(lambda x,y: x*y, range(1,n+1))
闭包
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
构成条件
- 在函数嵌套(函数里面再定义函数)的前提下
- 内部函数使用了外部函数的变量(还包括外部函数的参数)
- 外部函数返回了内部函数
示例
# 定义一个外部函数
def func_out(num1):
# 定义一个内部函数
def func_inner(num2):
# 内部函数使用了外部函数的变量(num1)
result = num1 + num2
print("结果是:", result)
# 外部函数返回了内部函数,这里返回的内部函数就是闭包
return func_inner
# 创建闭包实例
f = func_out(1)
# 执行闭包
f(2)
f(3)
>>>
结果是: 3
结果是: 4
小结
闭包可以保存外部函数内的变量,不会随着外部函数调用完而销毁。
闭包不仅可以保存外部函数的变量还可以提高代码的可重用行。
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
-
在内部函数中修改外部变量使用nonlocal 关键字
nonlocal num1 # 告诉解释器,此处使用的是 外部变量a # 修改外部变量num1 num1 = 10
装饰器
给已有函数增加额外功能的函数,它本质上就是一个闭包函数。
特点
- 不修改已有函数的源代码
- 不修改已有函数的调用方式
- 给已有函数增加额外的功能
语法格式
# 装饰器
def decorator(fn): # fn:被装饰的目标函数.
def inner():
'''执行函数之前'''
fn() # 执行被装饰的目标函数
'''执行函数之后'''
return inner
例子
# 添加一个登录验证的功能
def check(fn):
print("装饰器函数执行了")
def inner():
print("请先登录....")
fn()
return inner
# 使用语法糖方式来装饰函数
@check
def comment():
print("发表评论")
comment()
>>>
请先登录....
发表评论
装饰带有参数和返回值的函数
# 添加输出日志的功能
def logging(fn):
def inner(num1, num2):
print("--正在努力计算--")
result = fn(num1, num2)
return result
return inner
# 使用装饰器装饰函数
@logging
def sum_num(a, b):
result = a + b
return result
result = sum_num(1, 2)
print(result)
>>>
--正在努力计算--
3
通用装饰器
# 通用装饰器
def logging(fn):
def inner(*args, **kwargs):
print("--正在努力计算--")
result = fn(*args, **kwargs)
return result
return inner
多个装饰器
- 装饰过程是: 离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程
def make_div(func):
"""对被装饰的函数的返回值 div标签"""
def inner():
return "" + func() + ""
return inner
def make_p(func):
"""对被装饰的函数的返回值 p标签"""
def inner():
return "" + func() + "
"
return inner
# 装饰过程: 1 content = make_p(content) 2 content = make_div(content)
# content = make_div(make_p(content))
@make_div
@make_p
def content():
return "人生苦短"
result = content()
print(result)
>>>
人生苦短
带有参数的装饰器
使用装饰器装饰函数的时候可以传入指定参数,语法格式: @装饰器(参数,...)
-
写法:
在装饰器外面再包裹上一个函数,让最外面的函数接收参数,返回的是装饰器,因为@符号后面必须是装饰器实例。
# 添加输出日志的功能
def logging(flag):
def decorator(fn):
def inner(num1, num2):
if flag == "+":
print("--正在努力加法计算--")
elif flag == "-":
print("--正在努力减法计算--")
result = fn(num1, num2)
return result
return inner
# 返回装饰器
return decorator
# 使用装饰器装饰函数
@logging("+")
def add(a, b):
result = a + b
return result
result = add(1, 2)
print(result)
面向对象
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
三大特征有:封装性、继承性、多态性。
类和对象
面向对象编程的2个非常重要的概念:类和对象
类是抽象的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
-
类的构成
- 类的名称:类名(命名规则按照"大驼峰命名法")
- 类的属性:一组数据
- 类的方法:允许对进行操作的方法 (行为)
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
-
对象
- 对象名 = 类名(参数列表....)
- 对象调用方法的格式:对象名.方法名(参数列表)
- 注意:方法中参数列表中的第一个参数self,不需要手动传递,这个参数是由解释器在执行程序时,自动传递的默认会将当前调用方法的对象引用传递进去
在方法内通过self获取对象属性
class Hero(object):
"""定义了一个英雄类,可以移动和攻击"""
def move(self):
"""实例方法"""
print("正在前往事发地点...")
def attack(self):
"""实例方法"""
print("发出了一招强力的普通攻击...")
def info(self):
"""在类的实例方法中,通过self获取该对象的属性"""
print("英雄 %s 的生命值 :%d" % (self.name, self.hp))
print("英雄 %s 的攻击力 :%d" % (self.name, self.atk))
print("英雄 %s 的护甲值 :%d" % (self.name, self.armor))
# 实例化了一个英雄对象 泰达米尔
taidamier = Hero()
# 给对象添加属性,以及对应的属性值
taidamier.name = "泰达米尔" # 姓名
taidamier.hp = 2600 # 生命值
taidamier.atk = 450 # 攻击力
taidamier.armor = 200 # 护甲值
# 通过.成员选择运算符,获取对象的实例方法
taidamier.info() # 只需要调用实例方法info(),即可获取英雄的属性
taidamier.move()
taidamier.attack()
_init_()方法
两个下划线开始,两个下划线结束的方法,就是魔法方法,__init__()就是一个魔法方法,通常用来做属性初始化 或 赋值 操作。
-
__init__()
方法,在创建一个对象时默认被调用,不需要手动调用 -
__init__(self)
中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去。如果在创建对象时传递了2个实参,那么__init__(self)
中出了self作为第一个形参外还需要2个形参,例如__init__(self,x,y)
__str__()方法
用来显示信息,该方法需要 return 一个数据,并且只有self一个参数,当在类的外部 print(对象)
则打印这个数据
def __str__(self):
return "英雄 <%s> 数据: 生命值 %d" % (self.name, self.hp)
- 当使用print输出对象的时候,默认打印对象的内存地址。如果类定义了
__str__(self)
方法,那么就会打印从在这个方法中return
的数据 -
__str__
方法通常返回一个字符串,作为这个对象的描述信息
__del__()
方法
当删除对象时,python解释器也会默认调用
- 当有变量保存了一个对象的引用时,此对象的引用计数就会加1;当使用del() 删除变量指向的对象时,则会减少对象的引用计数。
- 如果对象的引用计数不为1,那么会让这个对象的引用计数减1,当对象的引用计数为0的时候,则对象才会被真正删除(内存被回收)。
小结
- 在类内部获取 属性 和 实例方法,通过self获取;
- 在类外部获取 属性 和 实例方法,通过对象名获取。
- 如果一个类有多个对象,每个对象的属性是各自保存的,都有各自独立的地址;
- 但是实例方法是所有对象共享的,只占用一份内存空间。类会通过self来判断是哪个对象调用了实例方法。
封装
意义
- 将属性和方法放到一起做为一个整体,然后通过实例化对象来处理;
- 隐藏内部实现细节,只需要和对象及其属性和方法交互就可以了;
- 对类的属性和方法增加 访问权限控制。
私有属性和私有方法
在属性名和方法名 前面 加上两个下划线 __
- 类的私有属性 和 私有方法,都不能通过对象直接访问,但是可以在本类内部访问;
- 类的私有属性 和 私有方法,都不会被子类继承,子类也无法访问;
- 私有属性 和 私有方法 往往用来处理类的内部事情,不通过对象处理,起到安全作用
修改私有属性
定义一个可以调用的公有方法,在这个公有方法内访问修改。
通常会定义get_xxx()方法和set_xxx()方法来获取和修改私有属性值。
-
set/get方法对私有属性操作时的好处:
- 提供精确的访问控制权限
- 隐藏实现细节,让代码更安全
- 可以提供更加安全的数据有效性控制
class Master(object): def __init__(self): # 私有属性,可以在类内部通过self调用,但不能通过对象访问 self.__money = 10000 # 返回私有属性的值 def get_money(self): return self.__money # 接收参数,修改私有属性的值 def set_money(self, num): self.__money = num
继承
- 在程序中,继承描述的是多个类之间的所属关系。
- 如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里。
- 那么类A就是基类,也叫做父类;类B就是派生类,也叫做子类。
单继承
- 继承的格式
class 子类名(父类名):
pass
当发生继承后,子类会继承父类中的属性和方法,可以直接 使用
在子类中不能直接使用父类中的私有方法;通过继承得到的父类的公有方法,间接 执行父类的私有方法
因为子类提供了 init 方法后,那么在使用子类实例对象时,就会调用 子类自己 init 方法;如果想父类中的属性可以得到,需要执行父类中的init方法
格式:父类名.__init__(self,父类中需要属性参数列表)
多层继承
子类继承父类,父类继承爷爷类,这就是多层继承
多继承
子类继承多个父类
如果子类和父类的方法名和属性名相同,则默认使用子类的
注意:如果多个父类中有同名的 属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性mro的顺序来查找)
多继承的初始化
在多继承时,如果继承的多个类同时继承同一个父类,那么这时会出现初始化问题,这个共同父类会被初始化多次.
-
super()执行过程:
在 self 这个对象的所属类中,通过 mro 找到方法解析顺序
在顺序中,找当前类名的下一个类来初始化或查找方法类名.__mro__
得到了一个元组,元组中的元素是当前类在继承关系上的一个顺序;这个顺序不是我们确定的,是由在确定某个类的继承关系关系后,由解释器来确定这个顺序
-
多继承调用指定父类中方法
父类名.方法() super().方法() # 方法2. super() 带参数版本,只支持新式类 super(Prentice, self).__init__() # 执行父类的 __init__方法 # super(Prentice, self).make_cake() # self.make_cake() # 方法3. super()的简化版,只支持新式类 super().__init__() # 执行父类的 __init__方法 super().make_cake() # 执行父类的 实例方法 self.make_cake() # 执行本类的实例方法
Mixin
在设计类的继承关系时,通常,主线都是单一继承下来的,但是,如果需要“混入”额外的功能,通过多重继承就可以实现。这种设计通常称之为MixIn。
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPServer, ForkingMixIn):
pass
多态
在需要使用父类对象的地方,也可以使用子类对象, 这种情况就叫多态.
比如, 在函数中,我需要调用 某一个父类对象的方法, 那么我们也可以在这个地方调用子类对象的方法.
类属性
类属性就是
类对象
所拥有的属性,它被所有类对象
的实例对象
所共有,类对象和实例对象均可访问,在内存中只存在一个副本类属性可以使用实例对象来引用,但是不能修改
一般情况下:类属性 都只使用 类对象 来调用class People(object): name = 'Tom' # 公有的类属性 __age = 12 # 私有的类属性 p = People() print(p.name) # 正确 print(People.name) # 正确 print(p.__age) # 错误,不能在类外通过实例对象访问私有的类属性 print(People.__age) # 错误,不能在类外通过类对象访问私有的类属性
实例属性(对象属性)
由于Python是动态语言,根据类创建的实例可以任意绑定属性。
给实例绑定属性的方法是通过实例变量,或者通过self
变量
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
- 以 对象名.xxx 的形式调用的都是实例的属性或实例的方法;
- 实例属性和实例方法只能由实例对象调用
类方法
是类对象所拥有的方法,需要用修饰器@classmethod
来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls
作为第一个参数
定义格式:
@classmethod
def 方法名(cls,...):
pass
调用格式:
类对象.类方法名
注意:在类方法中,不能使用self,但是可以使用 cls,该参数用来表示 当前类对象,这个参数也是自动传递的
@classmethod 是一个装饰 器,用来修饰一个方法成为类方法,当在执行该 类方法时,解释 会自动 将类对象传递到参数 cls中
-
类方法还有一个用途就是可以对类属性进行修改
class People(object): country = 'china' #类方法,用classmethod来进行修饰 @classmethod def get_country(cls): return cls.country @classmethod def set_country(cls,country): cls.country = country
静态方法
通过修饰器@staticmethod
来进行修饰,静态方法不需要多定义参数,可以通过对象和类来访问
格式:
@staticmethod
def 方法名(参数列表....):
pass
调用方式:
同类方法
类对象.静态方法名()
- 静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类实例对象来引用
使用@property
property属性就是负责把一个方法当做属性进行使用,这样做可以简化代码使用。
定义property属性有两种方式
- 装饰器方式
- 类属性方式
装饰器方式
class Person(object):
def __init__(self):
self.__age = 0
# 装饰器方式的property, 把age方法当做属性使用, 表示当获取属性时会执行下面修饰的方法
@property
def age(self):
return self.__age
# 把age方法当做属性使用, 表示当设置属性时会执行下面修饰的方法
@age.setter
def age(self, new_age):
if new_age >= 150:
print("成精了")
else:
self.__age = new_age
# 创建person
p = Person()
print(p.age)
p.age = 100
print(p.age)
p.age = 1000
>>>
0
100
成精了
@property
的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property
就可以了,此时,@property
本身又创建了另一个装饰器@age.setter
,负责把一个setter方法变成属性赋值
- @property 表示把方法当做属性使用, 表示当获取属性时会执行下面修饰的方法
- @方法名.setter 表示把方法当做属性使用,表示当设置属性时会执行下面修饰的方法
- 装饰器方式的property属性修饰的方法名一定要一样。
类属性方式
class Person(object):
def __init__(self):
self.__age = 0
def get_age(self):
"""当获取age属性的时候会执行该方法"""
return self.__age
def set_age(self, new_age):
"""当设置age属性的时候会执行该方法"""
if new_age >= 150:
print("成精了")
else:
self.__age = new_age
# 类属性方式的property属性
age = property(get_age, set_age)
# 创建person
p = Person()
print(p.age)
p.age = 100
print(p.age)
p.age = 1000
>>>
0
100
成精了
property的参数说明:
- 第一个参数是获取属性时要执行的方法
- 第二个参数是设置属性时要执行的方法
小结
# 数据 (属性)
# 区别: 类的每个对象(实例) 对于这个数据是独有的还是共享的,是不同的还是相同的
# 1. 对象属性 (实例属性)
# 类的每个对象 这个名字的属性数据 是独有的 ,每个对象不同
# 2. 类属性
# 类的每个对象 这个名字的属性数据 是共享,每个对象都相同
# 函数(方法)
# 区别:方法中能够直接使用的属性数据不同
# 1. 对象方法(实例方法) 可以直接读写对象属性,可以直接读类属性
# def obj_func(self):
# self.
# 2. 类方法
# @classmethod 可以直接读写类属性
# def class_func(cls, ..):
# cls.
# 3. 静态方法
# @staticmethod 虽然可以通过类名操作类属性,但是我们可以认为 不是直接操作属性
# def static_func():
# 类名.
# 选择:如果定义一个函数,这个函数中需要使用对象属性,定义对象方法
# 如果定义一个函数,这个函数中仅需要使用类属性,定义类方法
# 如果定义一个函数,这个函数不需要使用类的任何属性,从逻辑的角度考虑 应该是类中的一个处理方法,此时定义静态方法即可
总结
面向对象与面向过程
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
- 对于面向过程的思想: 需要实现一个功能的时候,看重的是开发的步骤和过程,每一个步骤都需要自己亲力亲为,需要自己编写代码(自己来做)。面向过程语言是一种基于功能分析的、以算法为中心的程序设计方法;
- 对于面向对象的思想:把一切都看成对象,而对象一般都由属性+方法组成;当需要实现一个功能的时候,看重的并不是过程和步骤,而是关心谁帮我做这件事(偷懒,找人帮我做)。而面向对象是一种基于结构分析的、以数据为中心的程序设计思想。三大特征有:封装性、继承性、多态性。
- 面向过程(手洗):脱衣服、找一个盆、加水、加洗衣粉、浸泡30分钟、搓洗、拧衣服、倒掉水、再加水、漂洗、拧衣服、倒掉水、晾衣服。
- 面向对象(机洗):脱衣服、放入洗衣机、按下开关、拿出衣服晾晒。
高级
I/O编程
文件读写
打开文件
- 以文本方式打开方式的模式
r -> read text; file = open('a.txt','r')
以文本方式打开文件读,文件存在,打开成功,文件不存在,打开失败
w -> write text; file = open('a.txt','w')
以文本方式打开文件写,不管文件是否存在,都会新创建一个新文件
a -> append text ;file = open('a.txt','w')
以文件方式打开文件追加,文件不存在,创建文件,文件存在,那么打开文件然后将光标移动到文件的最后
- 以二进制形式打开文件的模式
rb 以二进制形式打开文件读取
wb 以二进制形式打开文件写入
ab 以二进制形式打开文件追加
-
with语句
- Python提供了 with 语句的这种写法,既简单又安全,并且 with 语句执行完成以后自动调用关闭文件操作,即使出现异常也会自动调用关闭文件操作**。
# 1、以写的方式打开文件 with open("1.txt", "w") as f: # 2、读取文件内容 f.write("hello world")
读数据
- 默认读取全部文件内容;只适用于文件比较小的情况
file = open('a.txt','rt')
content = file.read()
file.close()
- 读取多行文件的方式
while True:
# 读取
content = file.read(4096) #从文件中读取的数据的长度(单位是字节)
# 如果在文件读取时,读取的结果为空串,说明文件读取完毕
# 根据这个条件 可以设置读取文件的结束条件
if content == '':
break
print(content,end='')
# 关闭文件
file.close()
- readline()以行读取
- readlines()以行的方式 读取整 个文件,并且返回的是一个列表,其中每一行的数据为一个元素
写数据
-
write()可以完成向文件写入数据
f = open('test.txt', 'w') f.write('hello world, i am here!') f.close()
如果文件不存在那么创建
操作文件和目录
导入 os 模块
-
os.rename(需要修改的文件名, 新的文件名)
import os os.rename("毕业论文.txt", "毕业论文-最终版.txt")
os.remove(待删除的文件名)
os.mkdir("张三")创建文件夹;如果当前目录 存在,会报错
os.getcwd()获取当前目录
os.chdir("../指定路径")改变当前目录 到指定 的路径 上去
-
os.listdir("./")获取目录下的文件名称,存在列表中
file_list = os.listdir('.') print(file_list) for file in file_list: print(file)
os.rmdir('路径') 删除一个空文件夹,当目录文件夹不为空,不能删除
模块
模块是一组Python代码的集合,可以使用其他模块,也可以被其他模块使用。
使用模块
import
在Python中用关键字import
来引入某个模块,比如要引用模块math,就可以在文件最开始的地方用import math来引入
在调用math模块中的函数时,必须这样引用:
模块名.函数名
from…import
Python的from语句让你从模块中导入一个指定的部分到当前命名空间中,此时可以用下面方法实现:
from 模块名 import 函数名1,函数名2....
- 通过这种方式引入的时候,调用函数时只能给出函数名,不能给出模块名,但是当两个模块中含有相同名称函数的时候,后面一次引入会覆盖前一次引入
- 如果想一次性引入math中所有的东西,还可以通过from math import *来实现
- 用as给引入起别名
- 在模块中的__all__变量就是为了限制或者指定能被导入到别的模块的函数,如果指定了那么只能是指定的那些可以被导入,没有指定默认就是全部可以导入,当然私有属性应该除外。
制作模块
编写一个hello
的模块:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '
__author__ = 'Michael Liao'
import sys
def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')
if __name__=='__main__':
test()
第1行和第2行是标准注释,第1行注释可以让这个hello.py
文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用__author__
变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。
定位模块
当你导入一个模块,Python解析器对模块位置的搜索顺序是:
- 当前目录
- 如果不在当前目录,Python则搜索在shell变量PYTHONPATH下的每个目录。
- 如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/
- 模块搜索路径存储在system模块的sys.path变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。
安装第三方模块
在Python中,安装第三方模块,是通过包管理工具pip完成的。
第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者pypi上搜索
异常
当Python检测到一个错误时,解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的"异常"
格式
try:
可能会出现异常问题的代码
except Exception as e:
当出现异常时,解决异常的代码
else:
当没有出现异常时,正常执行的代码
finally:
无论是否出现异常,都会执行这里的代码
自定义异常
格式:
class 异常名Error(Exception):
def __init__(self,msg=''):
self.__msg = msg
def __str__(self):
return self.__msg
class CustomError(Exception):
pass
调试与测试
断言assert
断言就是判断一个函数或对象的一个方法所产生的结果是否符合你期望的那个结果。 python中assert断言是声明布尔值为真的判定,如果表达式为假会发生异常。单元测试中,一般使用assert来断言结果。
def foo(s):
n = int(s)
assert n != 0, 'n is zero!' # 表达式为假,逗号后为自定义异常;也可不定义
return 10 / n
def main():
foo('0')
如果断言失败,assert
语句本身就会抛出AssertionError
:
程序中如果到处充斥着assert
,和print()
相比也好不到哪去。不过,启动Python解释器时可以用-O(是字母O)
参数来关闭assert,关闭后,你可以把所有的assert语句当成pass来看。
:
$ python -O err.py
常用的断言方法:
常用的断言方法:
assertEqual 如果两个值相等,则pass
assertNotEqual 如果两个值不相等,则pass
assertTrue 判断bool值为True,则pass
assertFalse 判断bool值为False,则pass
assertIsNone 不存在,则pass
assertIsNotNone 存在,则pass
logging
logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等;相比print,具备如下优点:
- 可以通过设置不同的日志等级,默认是warning级别,在release版本中只输出重要信息,而不必显示大量的调试信息;
- print将所有信息都输出到标准输出中,严重影响开发者从标准输出中查看其它数据;logging则可以由开发者决定将信息输出到什么地方,以及怎么输出;
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
-
记录信息的级别
- debug : 打印全部的日志,详细的信息,通常只出现在诊断问题上
- info : 打印info,warning,error,critical级别的日志,确认一切按预期运行
- warning : 打印warning,error,critical级别的日志,一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如。磁盘空间低”),这个软件还能按预期工作
- error : 打印error,critical级别的日志,更严重的问题,软件没能执行一些功能
- critical : 打印critical级别,一个严重的错误,这表明程序本身可能无法继续运行
-
logging.basicConfig函数各参数:
filename:指定日志文件名;
filemode:和file函数意义相同,指定日志文件的打开模式,'w'或者'a';
format:指定输出的格式和内容,format可以输出很多有用的信息,
参数:作用 %(levelno)s:打印日志级别的数值 %(levelname)s:打印日志级别的名称 %(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0] %(filename)s:打印当前执行程序名 %(funcName)s:打印日志的当前函数 %(lineno)d:打印日志的当前行号 %(asctime)s:打印日志的时间 %(thread)d:打印线程ID %(threadName)s:打印线程名称 %(process)d:打印进程ID %(message)s:打印日志信息
-
输出日志
import logging # 引入logging模块 import os.path import time # 第一步,创建一个logger logger = logging.getLogger() logger.setLevel(logging.INFO) # Log等级总开关 # 第二步,创建一个handler,用于写入日志文件 rq = time.strftime('%Y%m%d%H%M', time.localtime(time.time())) log_path = os.path.dirname(os.getcwd()) + '/Logs/' log_name = log_path + rq + '.log' logfile = log_name fh = logging.FileHandler(logfile, mode='w') fh.setLevel(logging.DEBUG) # 输出到file的log等级的开关 # 第三步,定义handler的输出格式 formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s") fh.setFormatter(formatter) # 第四步,将logger添加到handler里面 logger.addHandler(fh) # 日志 logger.debug('this is a logger debug message') logger.info('this is a logger info message') logger.warning('this is a logger warning message') logger.error('this is a logger error message') logger.critical('this is a logger critical message')
单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
import unittest
class TestClass(unittest.TestCase):
#该方法会首先执行,相当于做测试前的准备工作
def setUp(self):
pass
#该方法会在测试代码执行完后执行,相当于做测试后的扫尾工作
def tearDown(self):
pass
#测试代码
def test_app_exists(self):
pass
-
运行单元测试
-
一旦编写好单元测试,我们就可以运行单元测试。最简单的运行方式是在
mydict_test.py
的最后加上两行代码:if __name__ == '__main__': unittest.main()
这样就可以把
mydict_test.py
当做正常的python脚本运行:$ python mydict_test.py
-
另一种方法是在命令行通过参数
-m unittest
直接运行单元测试,这是推荐的做法:$ python -m unittest mydict_test ..... ---------------------------------------------------------------------- Ran 5 tests in 0.000s OK
-