guxh的python笔记二:函数基础
1,函数的参数
1.1,查看函数的参数类型
def run(a, *args, b, **kwargs): return a + b
可以通过如下方式查看参数类型:
import inspect k = inspect.signature(run) for i, j in k.parameters.items(): print('{:7}:'.format(i) , j.kind)
输出结果为:
a : POSITIONAL_OR_KEYWORD args : VAR_POSITIONAL b : KEYWORD_ONLY kwargs : VAR_KEYWORD
含义如下:
POSITIONAL_OR_KEYWORD:位置参数或者关键字参数,可以用位置参数或者关键字参数方式传参
VAR_POSITIONAL:可变位置参数,*args
KEYWORD_ONLY:仅限关键字参数,*args或者*后面的,只能用关键字参数方式传参
VAR_KEYWORD:可变关键字参数,**kwargs
POSITIONAL_ONLY:目前不支持
备注:
*args是最后一个位置参数,即*args后面的都是“仅限关键字参数”
**kwargs是最后一个参数,即**kwargs后面不允许再跟参数
1.2,传参方法
位置参数必须在关键字参数前面!
def test(x, y,z) .......
传参:
位置参数:test(a, b, c) 关键字接收:test(y=a, x=b, z=c) # 关键字参数,可颠倒次序 混合:test(1, z=2, y=6) # 位置参数必须在关键字参数前面,后面2个可以颠倒
1.3,*args,数量可变位置参数
args:序列
*args: 序列中的值
def test(x, *args): print(x, *args) # 1,2,3 print(x, args) # 1,(2,3)
test(1,2,3) # 或test(*[1,2,3])
1.4,**kwargs,数量可变的关键字参数
kwargs:字典
*kwargs: 字典的key
**kwargs:字典的键值对
def test(**kwargs): print(kwargs) # {'name': 'guxh', 'age': 18} print(*kwargs) # name age print(kwargs['name']) # 'guxh'
test(name='guxh', age=18) # 或test(**{'name':'guxh', 'age':18})
1.5,只接受仅限关键字参数
*后面的是仅限关键字参数:
def test(x, *, y): print(x, y) test('a', 'b') # TypeError test('a', y='b') # OK
*args后面的是仅限关键字参数:
def test(*args, y): print(*args, y) test('a', 'b') # TypeError test('a', y='b') # OK
1.6,特殊传参
组合传参,如果name用关键字参数传参,则age必须用关键字参数传参,因为位置参数必须在关键字参数前面。
def test(name, age=18, *args, **kwargs): print(name) # 位置参数或关键字参数,'guxh' print(age) # 位置参数或关键字参数,34 print(args) # 可变位置参数,() print(kwargs) # 可变关键字参数,{'sex': 'f', 'hobby': 'python'}
test('guxh', 34, sex='f',hobby='python') test(name='guxh', age=34, sex='f',hobby='python')
序列传参,字典传参:
def test(x, y):test(*序列),或test(**字典)
def test(*args) : test(*序列)
def test(**kwargs) :test(**字典)
2,防御可变参数
python的传参是按引用传参,但是函数对传入的参数进行修改以后:
可变参数:例如列表/字典,会就地修改,实际相当于传统的"按引用传参"
不可变参数:例如数值/字符串/远组,会重新开辟内存,结果相当于传统的"按值传参"
2.1,不可变参数传参
函数或类,在改变x之前,x的id与实参a的id完全一样,但是改变后就不一样了,也就是x发生改变时不会影响到实参a,效果相当于传统的按值传参。
函数传参:
def run(x): print(id(x)) # 1591569856 x += 1 print(id(x)) # 1591569888 a = 0 run(a) print(a, id(a)) # 0, 1591569856
类的实例化传参:
class Foo: def __init__(self, x): self.x = x print(id(self.x)) # 1591569856 self.x = self.x + 1 print(id(self.x)) # 1591569888 a = 0 print(id(a)) # 1591569856 f = Foo(a)
2.2,可变参数
函数或类,对x的改变会影响到实参a,效果相当于传统的按引用传参。
函数传参:
def run(x): x.append(1) a = [] run(a) print(a) # [1]
类的实例化传参:
class Foo: def __init__(self, x): self.x = x self.x.append(1) a = [] f = Foo(a) print(a) # [1]
因此如果不想改变原来的a,可以用浅拷贝或深拷贝:
class Foo: def __init__(self, x): self.x = list(x)
2.3,可变参数作为默认值的问题
如果想实现对一个输入列表添加一个1,当没有输入列表时,创建一个空列表并添加1,会发生如下问题:
函数:
def run(x=[]): x.append(1) return x a = [1, 2, 3] print(run(a)) # [1, 2, 3, 1],有参数时表现正常 print(run()) # [1],第一次run也正常 print(run()) # [1, 1],第二次run出错了,返回的是有2个1的列表
类的实例化传参:
class Foo: def __init__(self, x=[]): self.x = x self.x.append(1)
根据不同打印顺序,结果不一样:
f1 = Foo() f2 = Foo() print(f1.x) # [1, 1] print(f2.x) # [1, 1]
f1 = Foo() print(f1.x) # [1] f2 = Foo() print(f2.x) # [1, 1]
实际上f1和f2实例共享了同一个列表,所以都完成实例化后打印的值完全一样。
解决方法是函数或类有可变参数作为默认值时用None:
def run(x=None, y=None): x = [] if x is None else x y = {} if y is None else y # requests框架使用了这2句 x.append(1) y.update({'a': 1}) return x, y
3,高阶函数
3.1,高阶函数
满足下面两个条件之一的就叫高阶函数:
1)一个函数当作参数传给另外一个函数(参数有函数)
2)函数的返回值中包含函数(返回有函数)
3.2,回调函数
回调本质上就是高阶函数的第一个定义场景。
函数A作为参数传给另外一个函数B,涉及三个角色:中间函数A,回调函数B,中间函数A的调用者(一般是主程序)。
如果回调函数想保存额外状态,可以用基于类、闭包、协程、functools.partial、lambda等方式实现。
简单的回调函数:
def add(x, y): # 回调函数 print(x + y) def fun(x, y, callback): # 中间函数 return callback(x, y) if __name__ == '__main__': # 中间函数调用者 fun(1, 2, callback=add)
保存额外状态的回调 — 基于类:
class Foo: def __init__(self): self.s = 0 # 额外状态s def add(self, x, y): self.s += 1 print('第{}次相加结果:{}'.format(self.s, x + y)) def fun(x, y, callback): return callback(x, y) if __name__ == '__main__': # 中间函数调用者 f = Foo() fun(1, 2, callback=f.add) # 第1次相加结果:3 fun(3, 4, callback=f.add) # 第2次相加结果:7
保存额外状态的回调 — 基于类 + functools.partial / lambda:
class Seq: # 保存额外状态的类 def __init__(self): self.seq = 0 def fun(x, y, callback): return callback(x, y) def add(x, y, s): s.seq += 1 print('第{}次相加结果:{}'.format(s.seq, x + y)) if __name__ == '__main__': s = Seq() fun(1, 2, callback=partial(add, s=s)) fun(3, 4, callback=partial(add, s=s)) # 用lambda替代functools.partial # fun(1, 2, callback=lambda x, y: add(x, y, s)) # fun(3, 4, callback=lambda x, y: add(x, y, s))
保存额外状态的回调 — 基于闭包:
def make_add(): s = 0 def add(x, y): nonlocal s s += 1 print('第{}次相加结果:{}'.format(s, x + y)) return add def fun(x, y, callback): return callback(x, y) if __name__ == '__main__': add = make_add() fun(1, 2, callback=add) fun(3, 4, callback=add)
保存额外状态的回调 — 基于协程:
def make_add(): s = 0 while True: x, y = yield # 不产出值,只接收值(接收值在yield左边,产出值在yield右边) s += 1 print('第{}次相加结果:{}'.format(s, x + y)) def fun(x, y, callback): return callback((x, y)) # 协程只能接受一个参数,因此改为发送元组 if __name__ == '__main__': add = make_add() next(add) fun(1, 2, callback=add.send) # 往协程发送数据是send fun(3, 4, callback=add.send)
4,常用内置函数
4.1,匿名函数lambda
调用方法:
>>> fn = lambda x:x*3 >>> fn(3) 9 >>> (lambda x:x*3)(3) 9
lambda y: x + y,其中输入参数y可变,x在运行时才绑定,注意以下代码的区别:
funs1 = [lambda x: x + n for n in range(5)] for f in funs1: print(f(0), end=' ') # 4 4 4 4 4
funs2 = [lambda x, n=n: x + n for n in range(5)] for f in funs2: print(f(0), end=' ') # 0 1 2 3 4
4.2,计算
1)sum:求和,归约函数,归约函数可以直接结合生成器表达式,避免创建临时列表
nums = [1, 2, 3, 4, 5] s = sum(x*x for x in nums) # 55,如果nums很大,用列表推导会有很大开销
2)max:最大值,归约函数
3)min:最小值,归约函数
4)round:保留小数位数
5)divmond:相除,返回商和余
6)eval
7)exec
8)hash:哈希
9)id:获取ID
10)int / float / hex / oct / bytes / char / bin / chr / ord : 类型转换
11)pow:次方
4.3,检测
1)all:全部为真返回True,否则返回False,归约函数
2)any:任意为真返回Ture,否则返回False,归约函数
3)bool:判断True or False
4)callable:判断是否可调用
5)isinstance:判断类
6)issubclass:判断子类
7)type
class Human: pass class Male(Human): pass m = Male() print(isinstance(m, Human)) # True print(isinstance(m, Male)) # True print(issubclass(Male, Human)) # True print(type(m)) # <class '__main__.Male'>,如果是import,__main__显示module名
4.4,面向对象
1)super:继承父类
2)vars:返回所有属性名,相当于self.__dict__?
3)setattr / delattr:设置属性 / 删除属性
4)dir:看object部分方法
4.5,迭代
1)iter:获取iterator,iter()可以加哨符值取代while
2)zip:两个序列合起来
3)enumerate:获取index和value
4)map:返回iterator
m = map(lambda x: x * 2, [1, 2, 3]) print(list(m)) print(dir(m)) # 含有'__iter__', '__new__' print(type(m)) # <class 'map'> print(type(iter(m))) # <class 'map'>,iterator的__iter__是return self
5)filter:返回iterator
print(list(filter(lambda x: x > 1, [1, 2, 3]))) # [2, 3] print(list(filter(None, ['', None, 1, 'a']))) # [1, 'a']
6)range:返回iterable
r = range(10) print(dir(r)) # 含__iter__,但不含__next__ print(type(r)) # <class 'range'> print(type(iter(r))) # <class 'range_iterator'>,iterable的__iter__是return iterator
4.6,数据类型
1)tupple,list,dict,set,frozenset:转换为元组/列表/字典/集合/不可变集合
2)slice:切片
3)sort / sorted:排序
4)reversed:反向
4.7,文本
1)str
2)repr
3)replace:替换
4)strip:去除空格
5)format
name = 'guxh' age = 20 print("my name is " + name + ", age is " + str(age)) print("my name is %s, age is %s" % (name, age)) print("my name is {}, age is {}".format(name, age)) print("my name is {0}, age is {1}".format(name, age)) print("my name is {name}, age is {age}".format(name=name, age=age)) print("my name is {name}, age is {age}".format(**{'name': name, 'age': age}))
格式化参数:
^, <, > 分别是居中、左对齐、右对齐,后面带宽度,分别等效于center, ljust, rjust。
: 号后面带填充的字符,只能是一个字符,不指定则默认是用空格填充。
+ 表示在正数前显示 +,负数前显示 -; (空格)表示在正数前加空格
b、d、o、x 分别是二进制、十进制、八进制、十六进制
eg:>右对齐,@缺失部分用@填充,18固定宽度,','千分位,'.2'浮点精度2,f浮点数声明
'{:@>18,.2f}'.format(70305084.0) # 输出@@@@@70,305,084.00
6)文本对齐
>>>text = 'hello world' >>>text.center(20, '*') # 等效format(text, '*^20s'), '{:*^20s}'.format(text) '****hello world*****' >>>text.ljust(20, '*') # 等效format(text, '*>20s'), '{:*>20s}'.format(text) 'hello world*********' >>>text.rjust(20, '*') # 等效format(text, '*<20s'), '{:*<20s}'.format(text) '*********hello world'
4.8,显示交互
1)print:打印,end指定结束换行符
>>> for i in range(5): ... print(i, end=' ') 0 1 2 3 4
读取文件时有默认的换行符号,加上print自动产生的换行符,就会产生空行,可以用end去除空行:
with open('text.txt') as f: for line in f: print(line, end='')
sep指定间隔,join()只能合并同类型,print+sep可以合并不同类型
>>>print(*list('abcd'), sep='#') a#b#c#d
2)保存内容到f文件
print('hello world', file=f)
4.9,文件IO
打开文件:
open()函数返回的是个iterator
>>>from collections import abc >>>f = open('text.txt') >>>isinstance(f, abc.Iterator) True
参数说明:
- t:文本,默认
- r :读,默认
- w:写,有同名文件覆盖,无同名文件新建
- a :追加,有同名文件追加,无同名文件新建
- r+ : 读写(读模式 +写模式/追加模式),写入时和指针指向第几行没有关系,python3总是追加到最后面
- w+ :没什么用!写读(先创建文件再往里面写),文件不存在会创建,存在就覆盖,和w一样比较危险
- a+: 比较常用,追加模式 + 读模式,即附加了读的a追加模式(相当于r+a),可以读,也可以追加到最后
- rb,wb,ab :二进制的基本操作,python3里以rb打开以后无需encode到bytes类型
- rb+, wb+, ab+ : 二进制的读写操作
- x:文件不存在时才写入,可以省略os.path.exists判断
- encoding:编码
- newline:python会自动将系统换行符(unix是\n,windows是\r\n)转换成\n,设置newline=‘’可以取消自动转换
常用方法:
f:是iteraotr,可以逐行打印,但无法获取下标
for i in f: print(i)
f.read() :一次性全部读取所有内容,注意只能读一次,读第二次会为空
f.readline() :逐行读取,读5行可以:
for i in range(5): print(f.readline())
f.readlines() :逐行读取所有内容,可以打印所有,f.readlines()和list(f)结果是一样的
for i in f.readlines() : print(i)
f.tell():返回文件句柄当前指针指向的位置,指针位置是根据字符串确定的,不是根据行数确定的。
f.seek():返回到指针位置,例如f.seek(0),回到指针0的位置,f.seek()一般配合f.tell()使用,f.tell()确定指针位置,f.seek()回到指针位置。
f.encoding:获取文件编码
f.name():获取文件名
f.buffer
f.fileno():打印文件句柄在内存中的位置
f.isatty():判断是否是一个终端设备
f.seekable():判断文件是否可以移回去
f.writeable():判断文件是否可写
f.flush():刷新,写一句语句后不一定马上写到硬盘上,实际是等积满一定内容后再写到硬盘上,flush可以实时刷到硬盘上
# CMD写文件刷新 f = open('test.txt', 'w') f.write('hello world 1 \n') #此时去打开文件发现还没有写入 f.flush() # 此时去打开发现已写入 f.close() # 打印进度条: import(sys) import(time) for i in range(50): sys.stdout.write('#') sys.stdout.flush() time.sleep(0.1)
f.truncate():不写参数就清空
f.truncate(10) # 从开头截断至10
f.seek(10); # 从第10个字符开始,往后截断至10 f.truncate(10)
f.writelines(s):s是个iterable,相当于:
for i in s: f.write(i)
f.readinto(buff):将二进制数读取到可变缓冲区中
修改文件内容
- 方法一,读到内存里修改
- 方法二,修改内容,写到新文件中
读写压缩文件
- gzip.open:gz
- bz2.open:bz2
4.10,其他函数
1)globals:返回所有key,value
5,其他
5.1,函数注释
可以对函数的参数类型,返回类型,做注释:
def fun(x: int, y: int) -> int: return x + y
5.2,exec和eval
exec:执行复杂的python代码,无返回结果(相当于JavaScript中的eval)
eval:计算单个表达式的值,有返回结果