1、函数和传实参
1.1、Python函数
1.1.1、函数的三分类
数学定义
y=f(x) ,y是x的函数,x是自变量。y=f(x0, x1, ..., xn)
Python函数
由若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元
完成一定的功能
函数的作用
结构化编程对代码的最基本的封装,一般按照功能组织一段代码
封装的目的为了复用,减少冗余代码
代码更加简洁美观、可读易懂
函数的三分类:
内建函数,如max()、reversed()等
库函数,如math.ceil()等
自定义函数,使用def关键字定义
1.1.2、函数定义
def 函数名(参数列表):
函数体(代码块)
[return 返回值]
函数名就是标识符,命名要求一样
语句块必须缩进,约定4个空格
Python的函数若没有return语句,会隐式返回一个None值
定义中的参数列表称为形式参数,只是一种符号表达(标识符),简称形参
#def #关键字,用来定义函数,称为定义时
def add(x,y): #add是标识符,函数的名称也叫"add".
# x、y由于是在def定义时使用的标识符,称为形式参数,简称形参,用来占位用的,表示未来你要提供的参数个数
return x + y #return 反回值
add #函数也是对象,add是一个标识符,指代一个具体的函数对象,程序员通过标识符操作函数
<function __main__.add(x, y)>
add.__name__ #特殊属性,来展示函数的名称
'add'
1.1.3、函数调用
函数定义,只是声明了一个函数,它不能被执行,需要调用执行
调用的方式,就是函数名后加上小括号,如有必要在括号内填写上参数
调用时写的参数是实际参数,是实实在在传入的值,简称实参
def add(x, y): # 函数定义
result = x + y # 函数体
return result # 返回值
out = add(4,5) # 函数调用,可能有返回值,使用变量接收这个返回值
print(out) # print函数加上括号也是调用
上面代码解释:
定义一个函数add,及函数名是add,能接受2个参数
该函数计算的结果,通过返回值返回,需要return语句
调用时,通过函数名add后加2个参数,返回值可使用变量接收
函数名也是标识符
返回值也是值
定义需要在调用前,也就是说调用时,已经被定义过了,否则抛NameError异常
函数是可调用的对象,callable(add)返回True
看看这个函数是不是通用的?体会一下Python函数的好处
1.1、调用add
1.1.1、callable可调用的
callable(add)
True
1.1.2、添加实际参数
add(1,2) #调用并送入实实在在的参数,称为实际参数,简称实参
add(1,2) #函数标识符后面使用小括号就是调用或执行,称为调用时,调用时必须提供符合形参列表要求的参数,否则报错
3
执行结果
result = add(1,2)
result
3
result = add(1,2) + 100
result
103
add('a','b'),add([1],[2]),add(1,2) #拼接在一起
('ab', [1, 2], 3)
# python是动态类型,本来就没办法约定变量的类型,项目规模扩大,很多人要协同工作
1.1.4、函数两种参数
函数在定义是要定义好形式参数,调用时也提供足够的实际参数,一般来说,形参和实参个数要一致(可变参数除外)。
两种传参:
形参(5种): 是在定义的时候 add(x=2,y=3)
实参: 是在调用的时候,送入的实实在在的参数 add(2,4)
1.1.4.1、实参传参方式
1、位置传参
定义时def f(x, y, z), 调用使用 f(1, 3, 5),按照参数定义顺序传入实参
2、关键字传参
定义时def f(x, y, z),调用使用 f(x=1, y=3, z=5),使用形参的名字来传入实参的方式,如果使用了形参名字,那么传参顺序就可和定义顺序不同
要求位置参数必须在关键字参数之前传入,位置参数是按位置对应的
1.0、传参介绍
# 调用时的实参,实参的传参方式是发生在调用时,到目前为止,python发明的传参方式只有这2种
# 实参按顺序一一对应形参
1.1、按位置传入实际参数,简称(位置传参)
def add(x,y):
result = x + y
return result
add(4,5),add(10,11)
(9, 21)
1.2、按照名称对应,按照名称对应顺序可以不一致。这种方式称为(关键字传参keyword)
add(x=5,y=6),add(y=7,x=100)
(11, 107)
1.2.1、位置传参与关键字传参混用
add(2, y=4)
6
1.2.2、位置传参不能在关键字传参后面,否则会报错
10 ---->位置传参 x=4 ---->关键字传参
add(x=4,10)
SyntaxError: positional argument follows keyword argument (Temp/ipykernel_11188/2615036514.py, line 1)
File "C:\Users\jackie\AppData\Local\Temp/ipykernel_11188/2615036514.py", line 1
add(x=4,10)
^
SyntaxError: positional argument follows keyword argument
2、形参1
2.1、传参不同方式
2.1、传参不同方法
def add (x=4, y=5): #定义时,未来要使用形参x,y,如果实参不提供,那么就用缺省值顶
print(x,y)
return x + y
2.1.1、缺省值传参
2.1.1、缺省值传参
add(),add(10)
4 5
10 5
(9, 15)
2.1.2、单关键字传参
2.1.2、单关键字传参
add(),add(10),add(8,y=6),add(y=23)
4 5
10 5
8 6
4 23
(9, 15, 14, 27)
2.1.3、按名称对应传参
2.1.3、按名称对应传参
add(y=5,x=3)
3 5
8
2.1.3.1、按位置传参报错
2.1.3.1、按位置传参报错
add(11,x=9) ------>11就传给了x ,所以会报错
TypeError: add() got multiple values for argument 'x'
------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_11188/3981599427.py in <module>
----> 1 add(11,x=9)
TypeError: add() got multiple values for argument 'x'
add(1,2, y=4) ------->1传递给x,2传递给y, 关键字再去传参就会报错
TypeError: add() got multiple values for argument 'y'
------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_19248/2676440.py in <module>
----> 1 add(1,2, y=4)
TypeError: add() got multiple values for argument 'y'
add(x=4,10) ------->位置传参不能在关键字传参后面,否则会报错
SyntaxError: positional argument follows keyword argument (Te nel_11188/2615036514.py, line 1)
"C:\Users\jackie\AppData\Local\Temp/ipykernel_11188/2615036514.py", line
add(x=4,10)
SyntaxError: positional argument follows keyword argument
2.2、形参缺省值
缺省值也称为默认值,可以在函数定义时,为形参增加一个缺省值。其作用:
参数的默认值可以在未传入足够的实参的时候,对没有给定的参数赋值为默认值
参数非常多的时候,并不需要用户每次都输入所有的参数,简化函数调用
def add(x=4, y=5):
return x+y
测试调用 add()、add(x=5)、add(y=7)、add(6, 10)、add(6, y=7)、add(x=5, y=6)、
add(y=5, x=6)、add(x=5, 6)、add(y=8, 4)、add(11, x=20)
能否这样定义 def add(x, y=5) 或 def add(x=4,y) ?
# 定义一个函数login,参数名称为host、port、username、password
def login(host='localhost', port=3306, username='root', password='root'):
print('mysql://{2}:{3}@{0}:{1}/'.format(host, port, username, password))
login()
login('127.0.0.1')
login('127.0.0.1', 3361, 'wayne', 'wayne')
login('127.0.0.1', username='wayne')
login(username='wayne', password='wayne', host='www.zhgedu.com')
2.2.1、缺省值计算:
1.1、缺省值计算:
def add(x, y=6):
return x + y
add(x=10),add(1,7)
(16, 8)
报错:
def add(x=4,y): #缺省值应该尽量放在参数中靠后的位置
return x + y
正常执行:
def add(x,y=4): #缺省值应该尽量放在参数中靠后的位置
return x + y
2.2.1.1、传参连接数据库
1.1.1、各种传参方法
add(1),add(1,7),add(x=10),add(6,y=16),add(x=20,y=30),add(y=21,x=11)
(7, 8, 16, 22, 50, 32)
传参连接数据库
def connect(host='localhost',port=3306, user='root',password='root'):
print("mysql://{2}:{3}@{0}:{1}/".format(host,port,user,password))
connect(),connect('192.168.142.111'),connect(password='zhgedu',user='zhgedu')
mysql://root:root@localhost:3306/
mysql://root:[email protected]:3306/
mysql://zhgedu:zhgedu@localhost:3306/
(None, None, None)
2.3、可变参数
需求:写一个函数,可以对多个数累加求和
def sum(iterable):
s = 0
for x in iterable:
s += x
return s
print(sum([1,3,5]))
print(sum(range(4)))
上例,传入可迭代对象,并累加每一个元素。
也可以使用可变参数完成上面的函数。
def sum(*nums):
sum = 0
for x in nums:
sum += x
return sum
print(sum(1, 3, 5))
print(sum(1, 2, 3))
1、可变位置参数
在形参前使用 * 表示该形参是可变位置参数,可以接受多个实参
它将收集来的实参组织到一个tuple中
2、可变关键字参数
在形参前使用 ** 表示该形参是可变关键字参数,可以接受多个关键字参数
它将收集来的实参的名称和值,组织到一个dict中
def showconfig(**kwargs):
for k,v in kwargs.items():
print('{}={}'.format(k,v), end=', ')
showconfig(host='127.0.0.1', port=8080, username='wayne', password='zhgedu')
混合使用
可以定义为下列方式吗?
def showconfig(username, password, **kwargs)
def showconfig(username, *args, **kwargs)
def showconfig(username, password, **kwargs, *args) # ?
总结:
有可变位置参数和可变关键字参数
可变位置参数在形参前使用一个星号*
可变关键字参数在形参前使用两个星号**
可变位置参数和可变关键字参数都可以收集若干个实参,可变位置参数收集形成一个tuple,可变关键字参数收集形成一个dict
混合使用参数的时候,普通参数需要放到参数列表前面,可变参数要放到参数列表的后面,可变位置参数需要在可变关键字参数之前
使用举例
def fn(x, y, *args, **kwargs):
print(x, y, args, kwargs, sep='\n', end='\n\n')
fn(3, 5, 7, 9, 10, a=1, b='abc')
fn(3, 5)
fn(3, 5, 7)
fn(3, 5, a=1, b='abc')
fn(x=1, y=2, z=3) ----->z=3传到**kwargs中
fn(x=3, y=8, 7, 9, a=1, b='abc') # ? ------>关键字传参必须在位置传参后面
fn(7, 9, y=5, x=3, a=1, b='abc') # ? ------>y=5与9冲突了
fn(x=3, y=8, 7, 9, a=1, b='abc'),错在位置传参必须在关键字传参之前
fn(7, 9, y=5, x=3, a=1, b='abc'),错在7和9已经按照位置传参了,x=3、y=5有重复传参了
2.3.1、iterable可迭代对象传参
1.1.2、iterable可迭代对象传参
def s(iterable):
result = 0
for x in iterable:
result +=x
return result
s(range(5)),s([1,3,5]) -----> s(range(5)),s([1,3,5])都为一个对象
(10, 9)
2.3.2、*args剩余可变位置形参
2.3.2、*args可变的参数
def s(*args):
print(type(args), args)
s(1,2,3) ---->元组,不可变
<class 'tuple'> (1, 2, 3)
2.3.2.1、*arges可变参数的错误输出
2.3.2.1、*arges可变参数的错误输出
def s(*args):
print(type(args), args)
s = 0
for x in args: ----->(range(1,6)) 0 range(1,6)
s += x -----> 0 + range(1,6)
return s
s(range(1,6)) --->报错
TypeError: unsupported operand type(s) for +=: 'int' and 'range'
---------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_18816/2437433324.py in <module>
----> 1 s(1,2,3,4,5),s(range(1,6))
~\AppData\Local\Temp/ipykernel_18816/672214113.py in s(*args)
3 s = 0
4 for x in args:
----> 5 s += x
6 return s
TypeError: unsupported operand type(s) for +=: 'int' and 'range'
s(x=1,y=2,z=3) --->报错
NameError: name 's' is not defined
------------------------------------------------------------
NameError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_16960/3695981793.py in <module>
----> 1 s(x=1,y=2,z=3)
NameError: name 's' is not defined
s('a','b','c') --->报错
2.3.3、**kwargs剩余可变关键字形参
1.1、可变关键字形参:
def fn(**kwargs):
print(type(kwargs),kwargs)
fn(x=1,z=20,y=35,m=6,t=23) --->封装到字典中,可变
<class 'dict'> {'x': 1, 'z': 20, 'y': 35, 'm': 6, 't': 23}
1.2、*args随意位置传参
def fn(user,password,*args):
print(user,password,args)
fn(1,2,3)
1 2 (3,)
1.3、*args随意位置传参,**kwargs关键字传参
def fn(user,*args,**kwargs):
print(user,args,kwargs)
fn(1,2,3)
1 (2, 3) {}
1.4、x,y传参,*args随意位置传参,**kwargs关键字传参
def fn (x,y,*args,**kwargs):
print(x,y,args,kwargs)
fn(1,2,3,4,z=5) -----> *args可变剩余位置传参(3,4)
1 2 (3, 4) {'z': 5}
fn(4,5) -----> ()空元组 {}空字典
4 5 () {}
fn(1,2,3,4,range(6)) ----->*args位置参数收集
1 2 (3, 4, range(0, 6)) {}
fn(1,2,3,4,a=1,b='abc') ----->**kwargs可变关键字参数收集
1 2 (3, 4) {'a': 1, 'b': 'abc'}
fn(a=1,b='abc',x=100,y=200) ----->**kwargs剩余的可变关键字参数收集
100 200 () {'a': 1, 'b': 'abc'}
1.4.1、x=20关键字传参错误
def fn (x,y,*args,**kwargs):
print(x,y,args,kwargs)
fn(1,2,3,4,a=1,b=200,x=20,y=30)
TypeError: fn() got multiple values for argument 'x'
---
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_13644/3517840218.py in <module>
----> 1 fn(1,2,3,4,a=1,b=200,x=20,y=30)
TypeError: fn() got multiple values for argument 'x'
3、形参2
3.0、实参介绍
实参,发生在调用时,2种
1、位置传参
2、关键字传参
可以混用,但是要求位置传参在前
形参,定义时确定,写在哟数的参数列表中
1、普通参数,接受2种传参,可以位置传参,也可以关键字传参
2、可变位置参数,剩余位置参数,别人都先按照位置对应完了,剩下的没人要的它拿走,*args,收集到一个元组中去,可以为0个。
3、**kwargs,可变关键字形参,剩余关键字参数。别人都先使用关键字优先对应,剩下的没人要的关键字参数被它收集到一个字典中去
4、keyword-only,仅仅只能使用关键字传实参才能把参数对应上的形参。
必须提供,除非它有缺省值
5、positional-only,仅仅接受位置传参的形参。3.8之前没有。
必须提供,除非它有缺省值
缺省值和普通参数缺省值一起看,要求有缺省值的必须靠后
3.1、keyword-only参数
先看一段代码
def fn(*args, x, y, **kwargs):
print(x, y, args, kwargs, sep='\n', end='\n\n')
fn(3, 5) # --->x
fn(3, 5, 7) # --->x
fn(3, 5, a=1, b='abc') # --->x
fn(3, 5, y=6, x=7, a=1, b='abc') -----√
在Python3之后,新增了keyword-only参数。
keyword-only参数:在形参定义时,在一个*星号之后,或一个可变位置参数之后,出现的普通参数,就已经不是普通的参数了,称为keyword-only参数。
def fn(*args, x):
print(x, args, sep='\n', end='\n\n')
fn(3, 5) #
fn(3, 5, 7) #
fn(3, 5, x=7)
7
(3, 5)
keyword-only参数,言下之意就是这个参数必须采用关键字传参。
可以认为,上例中,args可变位置参数已经截获了所有位置参数,其后的变量x不可能通过位置传参传入了。
思考:def fn(**kwargs, x) 可以吗?
def fn(**kwargs, x):
print(x, kwargs, sep='\n', end='\n\n')
直接语法错误了。
可以认为,kwargs会截获所有关键字传参,就算写了x=5,x也没有机会得到这个值,所以这种语法不存在。
keyword-only参数另一种形式
* 星号后所有的普通参数都成了keyword-only参数。
def fn(*, x, y): ------->*之后看着像普通参数的形参都是keyword-only参数
print(x, y)
fn(x=6, y=7)
fn(y=8, x=9)
x y 仅关键字参数
def fn(*args, x , y): #----> x y 仅关键字参数
print(args,x, y)
fn(1, 2, 4, 5, y=5, x=3)
(1, 2, 4, 5) 3 5
def fn(*args,x,y,**kwargs):
print(args,x,y,kwargs)
fn(x=5, a=1,y=7)
() 5 7 {'a': 1}
3.2、Positional-only仅位置’‘/’’参数
Python 3.8 开始,增加了最后一种形参类型的定义:Positional-only参数。(2019年10月发布3.8.0)
def fn(a, /):
print(a, sep='\n')
fn(3)
fn(a=4) # 错误,仅位置参数,不可以使用关键字传参
def foo(a,/,b):
print(a,b)
foo(23,5)
foo(12,b=20)
23 5
12 20
inspect检查
signature签名对象
parameters参数
import inspect
def foo(a,b,/,c,d,e=20,*args,m=4,n,**kwargs):
print(a,b,c,d,e,m,n)
print(args)
print(kwargs)
sig = inspect.signature(foo)
print(sig)
print(sig.parameters)
for k,v in sig.parameters.items():
print(k,v.name,v.kind,v.default)
(a, b, /, c, d, e=20, *args, m=4, n, **kwargs)
OrderedDict([('a', <Parameter "a">), ('b', <Parameter "b">), ('c', <Parameter "c">), ('d', <Parameter "d">), ('e', <Parameter "e=20">), ('args', <Parameter "*args">), ('m', <Parameter "m=4">), ('n', <Parameter "n">), ('kwargs', <Parameter "**kwargs">)])
positional位置
keyword关键字
a a POSITIONAL_ONLY <class 'inspect._empty'>
b b POSITIONAL_ONLY <class 'inspect._empty'>
c c POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
d d POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
e e POSITIONAL_OR_KEYWORD 20
args args VAR_POSITIONAL <class 'inspect._empty'>
m m KEYWORD_ONLY 4
n n KEYWORD_ONLY <class 'inspect._empty'>
kwargs kwargs VAR_KEYWORD <class 'inspect._empty'>
进程已结束,退出代码为 0
def foo(a,b,/,c,d,e=20,*args,m=4,n,**kwargs):
print(a,b,c,d,e,m,n)
print(args,kwargs)
print('-'* 30)
foo(1,2,3,4,n=30)
foo(1,2,m=20,n=30,x=40,y=50,d=60,c=70)
------------------------------
1 2 70 60 20 20 30
() {'x': 40, 'y': 50}
------------------------------
foo(1,2,3,4,5,6,7,8,n=10,m=20,x=200)
------------------------------
1 2 3 4 5 20 10
(6, 7, 8) {'x': 200}
------------------------------
foo(0,1,x=2,y=3,e=4,d=5,c=6,n=7,a=0,b=1)
------------------------------
0 1 6 5 4 4 7
() {'x': 2, 'y': 3, 'a': 0, 'b': 1}
------------------------------
3.3、参数的混合使用
# 可变位置参数、keyword-only参数、缺省值
def fn(*args, x=5):
print(x)
print(args)
fn() # 等价于fn(x=5)
fn(5)
fn(x=6)
fn(1,2,3,x=10)
# 普通参数、可变位置参数、keyword-only参数、缺省值
def fn(y, *args, x=5):
print('x={}, y={}'.format(x, y))
print(args)
fn() #--->×
fn(5) #--->y=5 () x=5 √
fn(5, 6) #--->y=5 *args=(6) x=5 √
fn(x=6) #--->×
fn(1, 2, 3, x=10) #--->y=1 *args=(2,3) x=10 √
fn(y=17, 2, 3, x=10) #--->×,位置传参不能再关键字之后
fn(1, 2, y=3, x=10) #--->×
fn(y=20, x=30) #--->y=20 *args=() x=30 √
# 普通参数、缺省值、可变关键字参数
def fn(x=5, **kwargs):
print('x={}'.format(x))
print(kwargs)
fn() #--->√
fn(5) #--->√
fn(x=6) #--->√
fn(y=3, x=10) #--->√
fn(3, y=10) #--->√
fn(y=3, z=20) #--->√
3.4、参数规则(连接数据库)
参数列表参数一般顺序是:positional-only参数、普通参数、缺省参数、可变位置参数、keyword-only参数(可带缺省值)、可变关键字参数。
注意:
代码应该易读易懂,而不是为难别人
请按照书写习惯定义函数参数
def fn(a, b, /, x, y, z=3, *args, m=4, n, **kwargs):
print(a, b)
print(x, y, z)
print(m, n)
print(args)
print(kwargs)
print('-' * 30)
def connect(host='localhost', user='admin', password='admin', port='3306',db,**kwargs):
print(host, port)
print(user, password)
print(kwargs)
connect(db='cmdb') # 参数的缺省值把最常用的缺省值都写好了
connect(host='192.168.1.123', db='cmdb')
connect(host='192.168.1.123', 'zhgedu','zhgedu' db='cmdb', password='mysql')
定义最常用参数为普通参数,可不提供缺省值,必须由用户提供。注意这些参数的顺序,最常用的先定义
将必须使用名称的才能使用的参数,定义为keyword-only参数,要求必须使用关键字传参
如果函数有很多参数,无法逐一定义,可使用可变参数。如果需要知道这些参数的意义,则使用可、变关键字参数收集
4、参数解构和返回值
4.0、简介
def add(x, y):
print(x, y)
return x + y
add(4, 5)
add((4, 5)) # 可以吗?
t = 4, 5
add(t[0], t[1])
add(*t)
add(*(4, 5))
add(*[4, 5])
add(*{4, 5}) # 注意有顺序吗?
add(*range(4, 6))
add(*{'a':10, 'b':11}) # 可以吗?
add(**{'a':10, 'b':11}) # 可以吗?
add(**{'x':100, 'y':110}) # 可以吗?
参数解构:
在给函数提供实参的时候,可以在可迭代对象前使用 * 或者 ** 来进行结构的解构,提取出其中所有元素作为函数的实参
使用 * 解构成位置传参
使用 ** 解构成关键字传参
提取出来的元素数目要和参数的要求匹配
#实参解构,结构解开了。
# 一颗星* 表示把线性结构(一维)解开成按位置传参
# **后面Python中只能一种情况mapping字典,**{'a':100,'b':200}解构后就变成了关键字传参
4.1、参数解构
4.1.1、参数解构
1.0、参数解构
def add(*iterable): #----->*iterable可迭代的
result = 0
for x in iterable:
result += x
return result
add(1, 2, 3)
6
add(*[1, 3, 5])
9
add(*range(5))
10
# 3.8以后,下面就不可以使用字典解构后的关键字传参了
def add(x, y, /): # 仅位置形参
print(x, y)
return x + y
add(**{'x':10, 'y':11})
4.1.2、函数解构
1.1、函数解构
def add(x,y):
print(x,y)
return x + y
t = (4,5)
add(t[0],t[1])
4 5
9
4.1.2.1、一颗星*解构
4.1.2.1、一颗星*,表示把线性结构解开成按位置传参
add(*t) ----->实参解构,结构解开了
4 5
9
add(*[4,5]),add(*(4,5)),add(*range(4,6)),add(*{'a','b'})
4 5
4 5
4 5
a b
(9, 9, 9, 'ab')
add(*{'a':100, 'b':200}) ---->互相等价
a b
'ab'
add(*{'a':100,'b':200}.keys()) ---->互相等价
a b
'ab'
1.1.1.1、元组拼合
add(*{'a':100,'b':200}.items()) ----> #两颗星怎么理解
('a', 100) ('b', 200)
('a', 100, 'b', 200)
(1,) + (1,)
(1,) + (1,)
(1, 1)
4.1.2.2、两颗星**解构
4.1.2.2、两颗星**后面Python中只能一种情况mapping字典
add(**[4,5]) #**后面python只能一种情况mapping映射,**必须是字典
def add(x,y):
print(x,y)
return x + y
add(**{'x':100,'y':200})
100 200
300
import inspect
def foo(a,b,/): #----->仅位置传参
print(a,b)
d = {'a':100,'b':200}
foo(*d) #foo(a-100,b-200)
a b
4.2、函数返回值
先看几个例子
# return语句之后可以执行吗?
def showplus(x):
print(x)
return x + 1
print('~~end~~') # return之后会执行吗?
showplus(5)
# 多条return语句都会执行吗
def showplus(x):
print(x)
return x + 1
return x + 2
showplus(5)
# 下例多个return可以执行吗?
def guess(x):
if x > 3:
return "> 3"
else:
return "<= 3"
print(guess(10))
# 下面函数执行的结果是什么
def fn(x):
for i in range(x):
if i > 3:
return i
else:
print("{} is not greater than 3".format(x))
4
3 is not greater than 3
None
print(fn(5)) # 打印什么?
print(fn(3)) # 打印什么?
总结
Python函数使用return语句返回“返回值”
所有函数都有返回值,如果没有return语句,隐式调用return None
return 语句并不一定是函数的语句块的最后一条语句
一个函数可以存在多个return语句,但是只有一条可以被执行。如果没有一条return语句被执行到,隐式调用return None
如果有必要,可以显示调用return None,可以简写为return
如果函数执行了return语句,函数就会返回,当前被执行的return语句之后的其它语句就不会被执行了
返回值的作用:结束函数调用、返回“返回值”
4.2.1、return函数返回值
1.1、return返回值结束
def add(x, y): # 仅位置形参
print(x, y)
print('-------------') #一个函数执行,碰到了return语句,此函数执行完成
return x + y
add(4,5)
4 5
-------------
9
1.1.1、return使用1
def add(x,y):
return 1
return 2
add(4,8)
1
1.1.2、return使用2
def guess(x):
if x > 0:
print(x, 'is positive')
return 'positive'
elif x == 0:
print('zero')
return 0
else:
print(x, 'is negative')
return 'negative'
guess(-1),guess(0),guess(5)
-1 is negative
'negative'
zero
0
5 is positive
'positive'
1.1.3、return使用3
def fn(x):
for i in range(x):
if i > 3:
return i
else:
print("{} is not greater than 3".format(x))
print(fn(5))
print(fn(3))
4
3 is not greater than 3
None ---->正常执行不返回None,如果返回None,里面没有正常执行,或者里面计算不出来
1.1.4、return使用4
def foo():
return 1, True, 'abc'
foo() #返回了什么,多个值吗? 不是,返回了一个对象元组,返回的依然是单个值,组中有三个元素
(1, True, 'abc')
x,y,z = foo()
x,y,z
(1, True, 'abc')
5、作用域和嵌套函数
5.1、函数作用域***
作用域
一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域
def foo():
x = 100
print(x) # 可以访问到吗
上例中x不可以访问到,会抛出异常(NameError: name 'x' is not defined),原因在于函数是一个封装,它会开辟一个作用域,x变量被限制在这个作用域中,所以在函数外部x变量不可见。
注意:每一个函数都会开辟一个作用域
1.1、函数作用域1
def foo():
return 1, True, 'abc'
x,y,z
(1, True, 'abc')
del x ----->删除标识符x
x ----->报错
NameError: name 'x' is not defined
1.2、 函数作用域2
def foo():
x = 100
print(x)
foo() ---->自己的函数可以输出
print(x) ---->外部环境不可以输出
100
NameError: name 'x' is not defined
1.3、函数作用域3
def foo():
x = 100
print(x)
def fool():
print(x)
print(x)
NameError: name 'x' is not defined ---->print(x)在外环境不能访问内部环境
foo(),fool()
100 ---->foo()自己的函数可以定义
NameError: name 'x' is not defined ---->fool()找不到外面的值
image-20210924161427432
image-20210924162409568
5.2、作用域分类
5.2.1、全局作用域
在整个程序运行环境中都可见
全局作用域中的变量称为全局变量global
# 全局变量
x = 5 # 全局变量,也在函数外定义
def foo():
print(x) # 可见吗?为什么?
foo()
5.2.1.1、全局作用域图示
5.2.1.1、全局作用域
x = 123 #全局变量x
def foo():
x = 100
print(x)
def fool(): ----->此函数自己没有x
print(x)
print(x),fool() ----->fool()外部向内穿透,内部变量对外不可见
123
123
一般来讲外部作用域变量可以在函数内部可见,可以使用, 向内部穿透
反过来,函数内部的局部变量,不能在函数外部看到, 对外不可见
image-20210924162942742
image-20210924163358715
5.2.2、局部作用域
在函数、类等内部可见
局部作用域中的变量称为局部变量,其使用范围不能超过其所在局部作用域
也称为本地作用域local
# 局部变量
def fn1():
x = 1 # 局部作用域,x为局部变量,使用范围在fn1内
def fn2():
print(x) # x能打印吗?可见吗?为什么?
print(x) # x能打印吗?可见吗?为什么?
5.2.3、函数嵌套
1.1、outer与inner函数嵌套1
在一个函数中定义了另外一个函数
def outer():
def inner():
print("inner")
inner()
print("outer")
outer() # 可以吗?
inner() # 可以吗?
内部函数inner不能在外部直接使用,会抛NameError异常,因为它在函数外部不可见。
其实,inner不过就是一个标识符,就是一个函数outer内部定义的变量而已。
1.1、函数嵌套1
def outer():
def inner(): #不是执行了innor,定义了一个内部嵌套的函数对象,inner就是一个局部变量
pass
#inter() #此为inter函数执行
print('outer----')
outer() #couter不执行,inter函数对象不创建
outer----
1.2、局部变量inner()创建函数对象
1.2、局部变量inner()创建函数对象
def outer():
print('----------')
def inner(): #inner就是一个局部变量,访问不了内部的inner
pass
print(inner)
print('outer-------')
outer()
----------
<function outer.<locals>.inner at 0x00000229D87C4D38> #内存中创建的inner函数对象位置改变
outer-------
outer()
----------
<function outer.<locals>.inner at 0x00000229D87C49D8> #内存中创建的inner函数对象位置改变
outer-------
1.2.1、局部变量inner定义并使用
1.2.1、局部变量inner定义并使用
def outer():
print('----------')
x = list(range(10))
def inner():
print('inner++++')
inner()
print(id(x))
print(inner)
print('outer-------')
outer()
----------
inner++++
2232515727816
<function outer.<locals>.inner at 0x00000207CB95F708>
outer-------
1.3、局部变量x创建列表对象
1.3、局部变量x创建列表对象
def outer():
print('----------')
#x = []
x = list(range(10))
def inner():
pass
print(id(x))
print(inner)
print('outer-------')
outer()
----------
2378749101384
<function outer.<locals>.inner at 0x00000229D87E70D8> #内存地址不同
outer-------
outer()
----------
2378748961416
<function outer.<locals>.inner at 0x00000229D87E7438> #内存地址不同
outer-------
1.4、局部作用域的内部穿透
1.4、局部作用域的内部穿透
def outer():
o = 65 ----->局部作用域的内部穿透
def inner():
print('inner++++',o)
inner()
print('outer-------',o)
outer()
inner++++ 65
outer------- 65
1.4.1、局部作用域优先用自己的o
1.4.1、局部作用域优先用自己的o
def outer():
o = 65
def inner():
o = 97
print('inner++++',o)
inner()
print('outer-------',o)
outer()
inner++++ 97
outer------- 65
1.4.2、局部作用域就近原则
1.4.2、局部作用域就近原则
o = 49
def outer():
o = 65
def inner():
o = 97
print('inner++++',o)
inner()
print('outer-------',o)
outer()
print(o)
inner++++ 97
outer------- 65
49
o = 49
def outer():
o = 65
def inner():
o = 97
print('inner++++',o,chr(o))
inner()
print('outer-------',o,chr(o))
chr(65),chr(0x41),chr(0x61) #给整数返回字符串
(65, 97, 49)
ord('A'),ord('a'),ord('1') #给的数值出来字符串
(65, 97, 49)
hex(49) #整数转换为16进制
'0x31'
ord('啊') ----->互为转换
21834
chr(21834),chr(0x554a) ----->互为转换
('啊', '啊')
'啊'.encode(),'啊'.encode('gbk') ----->两种字节编码
(b'\xe5\x95\x8a', b'\xb0\xa1')
从执行的结果来看:
外层变量在内部作用域可见内层作用域inner中,如果定义了o = 97 ,相当于在当前函数inner作用域中重新定义了一个新的变量o,但是,这个o并不能覆盖掉外部作用域outer2中的变量o。只不过对于inner函数来说,其只能可见自己作用域中定义的变量o了
内建函数 函数签名 说明
chr chr(i) 通过unicode编码返回对应字符
ord ord(c) 获得字符对应的unicode
print(ord('中'), hex(ord('中')), '中'.encode(), '中'.encode('gbk'))
chr(20013) # '中'
chr(97)
6、闭包和LEGB
6.1、global语句
x = 5
def foo():
global x # 全局变量
x += 1
print(x)
foo()
使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x
全局作用域中必须有x的定义
如果全局作用域中没有x定义会怎样?
注意,下面试验如果在ipython、jupyter中做,上下文运行环境中有可能有x的定义,稍微不注意,就
测试不出效果
# 有错吗?
def foo():
global x
x += 1
print(x)
foo()
# 有错吗?
def foo():
global x
x = 10
x += 1
print(x)
foo()
print(x) # 可以吗
使用global关键字定义的变量,虽然在foo函数中声明,但是这将告诉当前foo函数作用域,这个x变量
将使用外部全局作用域中的x。
即使是在foo中又写了x = 10 ,也不会在foo这个局部作用域中定义局部变量x了。
使用了global,foo中的x不再是局部变量了,它是全局变量。
总结
x+=1 这种是特殊形式产生的错误的原因?先引用后赋值,而python动态语言是赋值才算定义,才
能被引用。解决办法,在这条语句前增加x=0之类的赋值语句,或者使用global 告诉内部作用域,
去全局作用域查找变量定义内部作用域使用x = 10 之类的赋值语句会重新定义局部作用域使用的变量x,但是,一旦这个作用域中使用 global 声明x为全局的,那么x=5相当于在为全局作用域的变量x赋值
global使用原则
外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的
目的就是为了封装,尽量与外界隔离
如果函数需要使用外部全局变量,请尽量使用函数的形参定义,并在调用传实参解决一句话:不用global。学习它就是为了深入理解变量作用域
6.1.1、函数作用域
1.1、函数作用域
x = 10
def foo():
print(x)
foo()
x = 100
def foo():
y = x + 1
print(y)
foo()
x = 200
def foo():
x = 300
y = x + 1
print(y)
10
101
301
1.1.1、解析式报错
x = 400
def foo():
#x = 200 #x需要在x += 1之前赋值
x += 1
print(x)
foo()
local variable 'x' referenced before assignment ----->局部域的报错
本地变量在局部作用域里还未被赋值:
x = 200
def foo():
#x = 300 #x需要在y之前赋值
y = x + 1
print(y)
x = 300 #只要出现了x=,在python中就认为x为当前作用域变量
print(x)
foo()
6.1.2、global全局作用域
1.2、global全局作用域
x = 200
def foo():
global x #global优先,导致x=无效, 声明x为全局变量
y = x + 1
x = 300
print(y)
foo()
print(x)
201
300
x = 200
def fn():
global x
x = 500
x = x + 1
print(x)
fn()
print(x)
500
500
6.1、闭包***
6.1.1、闭包函数1
自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量
闭包: 就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是JavaScript
def counter():
c = [0]
def inc():
c[0] += 1 # 报错吗? 为什么 # line 4
return c[0]
return inc
foo = counter() # line 8
print(foo(), foo()) # line 9
c = 100
print(foo()) # line 11
6.1.1、闭包函数1
def counter():
c = [0]
def inc():
c[0] += 1
return c
return inc # return函数,函数对象的地址返回
foo = counter() #foo是什么,foo指向inc所指向的函数对象
#counter执行完,局部变量c和inc还存在吗?
#python回收内存是靠垃圾回收,什么是垃圾,看引用计数,引用计数不为0不回收
print(foo(),foo())
[2] [2]
c = 100
print(foo())
[3]
6.1.2、inc闭包函数2
1.2、inc闭包函数2
def counter():
c = [0] #要使c给记住,因为inc()内部要用
def inc(): # inc使用外部的局部变量c
c[0] += 1 # 用到了c,c是counter局部变量,c = [0]等于c,c为外部函数的自由变量
return c
return inc # return函数,inc:为闭包函数
foo = counter()
foo,foo()
(<function __main__.counter.<locals>.inc()>, [2])
print(foo(),foo())
c = 100
print(foo())
1 2
3
1.2.1、闭包错误示例1
def counter():
c = [0]
def inc():
c[0] += 1
return c[0]
return inc() # return函数 ----->主要错误配置()
foo = counter()
foo
1
1.2.2、闭包错误示例2
def counter():
c = 0
def inc():
global c
c += 1
return c
return inc # return函数
fool = counter()
print(fool(),fool()) ----->结果报错
6.1.3、闭包函数引用global3
1.3、闭包函数3
def counter():
global c
c = 0
def inc():
global c ------>当你使用全局变量c的时候,就没有闭包
c += 1
return c
return inc # return函数
fool = counter()
print(c)
print(fool(),fool())
0
1 2
image-20210926005647371
image-20210926010454555
image-20210926011559521
6.2、nonlocal闭包语句
6.2.1、nonlocal语句
nonlocal:将变量标记为不在本地作用域定义,而是在上级的某一级局部作用域中定义,但不能是全局
作用域中定义。
def counter():
count = 0
def inc():
nonlocal count # 声明变量count不是本地变量
count += 1
return count
return inc
foo = counter()
print(foo(), foo())
count 是外层函数的局部变量,被内部函数引用。
内部函数使用nonlocal关键字声明count变量在上级作用域而非本地作用域中定义。
代码中内层函数引用外部局部作用域中的自由变量,形成闭包。
6.2.2、nonlocal闭包函数
6.2、nonlocal闭包函数
def counter():
c = 0
def inc():
nonlocal c #---->不是本地inc内部变量,是外部变量c
c += 1
return c
return inc # return函数
foo2 = counter()
foo2(),foo2(),foo2()
(1, 2, 3)
6.3、函数的销毁
定义一个函数就是生成一个函数对象,函数名指向的就是函数对象。
可以使用del语句删除函数,使其引用计数减1。
可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减1。
Python程序结束时,所有对象销毁。
函数也是对象,也不例外,是否销毁,还是看引用计数是否减为0。
引用计数:
def fn():
pass
f = fn
a = [f] ---->列表引用计数
del fn ---->删除fn
f ---->f的引用计数依然存在
a[0] = 100 ---->f变成100,以前的数据丢失
del f ---->删除引用计数f
f ---->f的引用计数不存在
6.4、变量名解析原则LEGB***
Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡
Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如print(open),print和open都是内置的变量所以一个名词的查找顺序就是LEGB
image-20210926112556939
内建函数 函数签名 说明
iter iter(iterable) 把一个可迭代对象包装成迭代器
next next(iterable[, default]) 取迭代器下一个元素,如果已经取完,继续取抛Stoplteration异常
reversed reversed(seq) 返回一个翻转元素的迭代器,迭代一个可迭代对象,返回一个迭代器
enumerate enumerate(seq, start=O) 每一个元素都是数字和元素构成的二元组
7、生成器函数***
7.1、迭代器表
内建函数 函数签名 说明
iter iter(iterable) 把一个可迭代对象包装成迭代器
next next(iterable[, default]) 取迭代器下一个元素,如果已经取完,继续取抛Stoplteration异常
reversed reversed(seq) 返回一个翻转元素的迭代器,迭代一个可迭代对象,返回一个迭代器
enumerate enumerate(seq, start=O) 每一个元素都是数字和元素构成的二元组s
7.1.1、迭代器
1.1、iter迭代器使用
items = [1,3,5,7]
i1 = iter(items)
i1,next(i1)
(<list_iterator at 0x21147c37f88>,1) ---->列表迭代器
i2 = iter(range(5))
next(i2,'end') ----->设置缺省值'end'
'end'
1.2、迭代器next
items = [1,3,5,7]
r = reversed(items) #迭代器都是惰性求值的
r,next(r),next(r) ---->不可以在走了,不然会报错
(<list_reverseiterator at 0x2114704c848>, 7, 5)
items = [1,3,5,7]
for i,v in enumerate(items):
print(i,v)
0 1
1 3
2 5
3 7
for x in [1,1,1,1]: ----->输出的是不同的四个元素
print(x)
1
1
1
1
1.3、reversed反转使用报错
s1 = {1,3,5,7}
reversed(s1) ----->无序反转,不能迭代
TypeError: 'set' object is not reversible
1.4、enumerate二元组
s1 = {1,3,5,7}
g = enumerate(s1) #返回一个迭代器,二元组(数字,元素)
next(g),next(g),next(g)
((1, 3), (2, 5), (3, 7))
g = enumerate(s1,100) #指定从100开始
next(g),next(g),next(g)
((100, 1), (101, 3), (102, 5))
for i in g:
print(i)
(103, 7)
7.2、生成器函数
生成器generator
生成器指的是生成器对象,可以由生成器表达式得到,也可以使用yield关键字编写一个生成器函
数,调用这个函数得到一个生成器对象
生成器对象,是一个可迭代对象,是一个迭代器
生成器对象,是延迟计算、惰性求值的
生成器函数
函数体中包含yield语句的函数,就是生成器函数,调用后返回生成器对象
m = (i for i in range(5)) # 生成器表达式
print(type(m))
print(next(m))
print(next(m))
def inc(): # 生成器函数
for i in range(5):
yield i
print(type(inc))
print(type(inc()))
g = inc()
print(type(g))
print(next(g))
for x in g:
print(x)
print('--------------------')
for y in g: # 还有元素吗
print(y)
普通函数调用,函数会立即执行直到执行完毕。
生成器函数调用,并不会立即执行函数体,而是返回一个生成器对象,需要使用next函数来驱动这个生成器对象,或者使用循环来驱动。
生成器表达式和生成器函数都可以得到生成器对象,只不过生成器函数可以写更加复杂的逻辑。
7.2.1、生成器函数的执行
def gen():
print(1)
yield 2
print(3)
yield 4
print(5)
return 6
yield 7
next(gen()) # ?
next(gen()) # ?
g = gen()
print(next(g)) # ?
print(next(g)) # ?
print(next(g)) # StopIteration
print(next(g, 'End')) # 没有元素给个缺省值
在生成器函数中,可以多次yield,每执行一次yield后会暂停执行,把yield表达式的值返回
再次执行会执行到下一个yield语句又会暂停执行
函数返回
return语句依然可以终止函数运行,但return语句的返回值不能被获取到
return会导致当前函数返回,无法继续执行,也无法继续获取下一个值,抛出StopIteration异常
如果函数没有显式的return语句,如果生成器函数执行到结尾(相当于执行了return None),一样会抛出StopIteration异常
生成器函数
包含yield语句的生成器函数调用后,生成 生成器对象 的时候,生成器函数的函数体不会立即执行
next(generator) 会从函数的当前位置向后执行到之后碰到的第一个yield语句,会弹出值,并暂停函数执行
再次调用next函数,和上一条一样的处理过程
继续调用next函数,生成器函数如果结束执行了(显式或隐式调用了return语句),会抛出StopIteration异常
7.2.2、生成器函数
1.1、生成器函数
生成器就是
x = (i for i in range(5))
print(type(x))
print(x)
print(next(x))
print(next(x))
for i in x:
print(x)
<class 'generator'>
<generator object <genexpr> at 0x0000021147CEC7C8>
0
1
<generator object <genexpr> at 0x0000021147CEC7C8>
<generator object <genexpr> at 0x0000021147CEC7C8>
<generator object <genexpr> at 0x0000021147CEC7C8>
7.2.3、yied1生成器函数
7.2.3.1、生成器函数1
1.2、yied1生成器函数
生成器就是特殊的迭代器,可以使用next,当然也可以使用For
迭代器特点,可以使用next,还有到头绝不回头
通过iter可以把任意的可迭代对象包装出一个新的迭代器
1.2.1、生成器函数1
def inc(): #只要这个函数有了yied1,就是生成器函数,生成器函数调用返回不再是返回值,返回一个生成器对象
for i in range(5):
yield i
y = inc() ----->生成器对象
print((type(y),y))
(<class 'generator'>, <generator object inc at 0x000002BA1DE22EC8>)
for i in y:
print(i)
0
1
2
3
4
7.2.3.2、生成器函数2
1.2.2、生成器函数2
def test():
print(1)
yield 2 ----->yield生成器也会打印出结果
print(3)
yield 4
print(5)
return 6
yield 7
z = test()#z 此函数有没有yield?调用后拿到什么
next(z) #每驱动一下,就会在yield处暂停函数执行,并且把yield后的值抛出来
1
2
next(z)
3
4
next(z)
5
StopIteration: 6 ----->从这条之后就停止了
def test():
print(1)
yield 2 ----->yield生成器也会打印出结果
print(3)
yield 4
print(5)
return 6
yield 7
z = test()
for i in z:
print(i)
print('------------')
print(i,'++')
1 #print(1)打印1
------------ #'-----'
2 ++ #yield 2 打印2
3 #print(3)打印3
------------
4 ++ #yield 4 打印4
5 #print(5)打印5
[i for i in test()], list(test())
1
3
5
1
3
5
([2, 4], [2, 4])
7.2.3.3、生成器函数3
1.2.3、生成器函数3
def inc():
for i in range(5):
yield i
m = inc() ----->m是生成器对象
next(m)
0
next(m)
1
next(m),next(m),next(m)
(1, 2, 3)
for i in m: #for判断如果出现了stopiteration认为到头了,结束循环,不抛异常
print(i)
0
1
2
3
4
for i in inc():
print(i)
inc(),inc(),inc()
(<generator object inc at 0x000001C08A908B48>,
<generator object inc at 0x000001C08A908D48>,
<generator object inc at 0x000001C08A908F48>)
[inc()] #一个元素 ----->生成器对象被塞入列表中
[<generator object inc at 0x000001C08A8690C8>]
list(inc())
[0, 1, 2, 3, 4]
tuple(inc())
(0, 1, 2, 3, 4)
set(inc())
{0, 1, 2, 3, 4}
{i for i in inc()}
{0, 1, 2, 3, 4})
7.3、斐波那契数列
def fib():
a = b = 1
yield b
while True:
a, b = b, a + b
yield a
f = fib()
for i in range(101):
print(i+1, next(f))
斐波那契数列1
斐波那契数列1
#F(1)-1, F(2)-1,F(n)-F(n - 1)+F(n - 2) (n>2 3,n EN*)
#0、1、1、2、、3、5、8、13、21、34
1 1 a + b
1 2 3
2 3 5
3 5 8
5 8 13
a = b = 1
for i in range(5-2):
a, b = b, a + b #---a,b-1, 1 + 1-1,2
#---a,b - 2, 1 + 2-2,3
#---a,b - 3, 2+3-3,5
a, b
(3, 5)
斐波那契数列2
斐波那契数列2
def fib(n=5):
a = b = 1
for i in range(n-2):
a,b = b, a + b
print(a,b)
return b
fib()
1 2
2 3
3 5
5
fib(10)
1 2
2 3
3 5
5 8
8 13
13 21
21 34
34 55
斐波那契数列3
斐波那契数列3
def fib(n=5):
a = b = 1
yield a ----->第一次拿到yield 1
for i in range(n-2): -----> #fib(3-2),range(1) 0,迭代一下
a,b = b, a + b
#print(a,b)
#return
yield a ----->第二次拿到yield 1
#return
f = fib(3)
for index ,v in enumerate(f,1): #从1开始
print(index,v)
1 1
2 1
list(fib(3))
[1, 1]
创建生成器对象fib(3)
for index ,v in enumerate(fib(3),1): #从1开始
print(index,v)
1 1
2 1
斐波那契数列4
斐波那契数列4
def fib():
a = b = 1
yield a
while True:
a,b = b , a + b
yield a
m = fib() #生成器函数,就可以得到无限可迭代对象了
for i in range(5): ------>走5下
print(i,next(m))
0 1
1 1
2 2
3 3
4 5
7.4、yield from语法
从Python 3.3开始增加了yield from语法,使得yield from iterable 等价于for item initerable: yield item 。
yield from就是一种简化语法的语法糖。
def inc():
for x in range(1000):
yield x
# 使用yield from 简化
def inc():
yield from range(1000) # 注意这个函数出现了yield,也是生成器函数
foo = inc()
print(next(foo))
print(next(foo))
print(next(foo))
def foo():
yield from range(5)
m = foo()
print(next(m)),print(next(m))
0
1
(None, None)
本质上yield from的意思就是,从from后面的可迭代对象中拿元素一个个yield出去。
8、函数调用原理
8.1、函数执行流程
函数执行流程 函数的活动和栈有关。
栈是后进先出的数据结构。
栈是从底向顶端生长,栈中插入数据称为压栈、入栈,从栈顶弹出数据称为出栈。
C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。
—-> 函数的每一次调用都会创建一个独立的栈帧(Stack Frame)入栈。 因此,可以得到这样一句不准确的话:哪怕是同一个函数两次调用,每一次调用都是独立的,这两次调用没什么关系。
注:函数执行过程辅助网站:http://pythontutor.com/visualize.html#mode=edit,请不要过分依赖这个网站动态效果,不要放弃自己脑子的想象力。
形参都是局部变量,局部变量都要压栈
x = [1,2,3,4]
def fool():
x = [1,2,3]
x.append(100)
return x
fool()
fool()
[1, 2, 3, 100]
y = [1,2,3,4]
def fool(x):
x[0] += 1
#x.append(100)
return x
fool(y)
fool(y)
[3, 2, 3, 4]
9、递归函数
9.1、return递归函数
函数直接或者间接调用自身就是 递归
递归需要有边界条件、递归前进段、递归返回段
递归一定要有边界条件
当边界条件不满足的时候,递归前进
当边界条件满足的时候,递归返回
能不用则不用递归
斐波那契数列递归0
def fib(n=3): #------>效率最高1
a = b = 1
for i in range(n-2):
a,b = b, a + b
return b
%timeit fib(40)
5
5.81 µs ± 503 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
递归函数1 #----->效率较高2
def fib1(n):
if n < 3:
return 1
return fib1(n-1) + fib(n-2)
%timeit fib1(3)
2
%timeit fib1(40)
1134903170
150 µs ± 5.65 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
递归函数2 #----->效率特别低3,函数的反复压栈,还有递归范围
def fib1(n):
return 1 if n < 3 else fib1(n-1) + fib(n-2)
%timeit fib1(40)
102334155
递归函数3 ---->循环调用
def fib2(n=3, a=1, b=1):
if n < 3:
return b #----->累加的结果给了b
a,b = b, a + b
return fib2(n-1,a,b)
%timeit fib2(10)
55
%timeit fib2(3100) #------->超出递归的限制范围,
#import sys #----->函数最多落3000层
print(sys.getrecursionlimit())
3000
image-20210930143643378
image-20210930171847733
image-20210930174543591
9.2、递归要求
递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用,就是无
限调用
递归调用的深度不宜过深
Python对递归调用的深度做了限制,以保护解释器
超过递归深度限制,抛出RecursionError: maxinum recursion depth exceeded 超出最大深度
sys.getrecursionlimit()
9.3、递归效率
使用时间差或者%%timeit来测试一下这两个版本斐波那契数列的效率。很明显循环版效率高。
难道递归函数实现,就意味着效率低吗?
能否改进一下fib_v2函数?
# 递归
def fib_v3(n, a=1, b=1):
if n < 3:
return b
a, b = b, a + b
print(n, a, b)
return fib_v3(n-1, a, b) # 函数调用次数就成了循环次数,将上次的计算结果代入下次函数调用
fib_v3(101) # fib_v3(35)
思考时,也比较简单,思考fib_v3(3)来编写递归版本代码。
经过比较,发现fib_v3性能不错,和fib_v1循环版接近。但是递归函数有深度限制,函数调用开销较大。
9.4、间接递归
def foo1():
foo2()
def foo2():
foo1()
foo1()
间接递归调用,是函数通过别的函数调用了自己,这一样是递归。
只要是递归调用,不管是直接还是间接,都要注意边界返回问题。但是间接递归调用有时候是非常不明显,代码调用复杂时,很难发现出现了递归调用,这是非常危险的。
所有,使用良好的代码规范来避免这种递归的发生。
总结
递归是一种很自然的表达,符合逻辑思维
递归相对运行效率低,每一次调用函数都要开辟栈帧
递归有深度限制,如果递归层次太深,函数连续压栈,栈内存很快就溢出了
如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一些,但是只
要不是死循环,可以多次迭代直至算出结果
绝大多数递归,都可以使用循环实现
即使递归代码很简洁,但是能不用则不用递归
10、匿名函数和排序
10.1、匿名函数
Python中,匿名函数也叫lambda表达式。
匿名:隐藏名字,即没有名称
匿名函数:没有名字的函数。
函数没有名字该如何定义?函数没有名字如何调用?
10.1.1、Lambda表达式
Lambda表达式
Python中,使用Lambda表达式构建匿名函数。
def foo(x):
return x ** 2
lambda x: x ** 2 # 定义
(lambda x: x ** 2)(4) # 调用
foo = lambda x,y: (x+y) ** 2 # 定义函数
foo(1, 2)
# 等价于
def foo(x,y):
return (x+y) ** 2
使用lambda关键字定义匿名函数,格式为lambda [参数列表]: 表达式
参数列表不需要小括号。无参就不写参数
冒号用来分割参数列表和表达式部分
不需要使用return。表达式的值,就是匿名函数的返回值。表达式中不能出现等号
Python的lambda表达式(匿名函数)只能写在一行上,也称为单行函数
匿名函数往往用在为高阶函数传参时,使用lambda表达式,往往能简化代码
# 返回常量的函数
print((lambda :0)())
# 加法匿名函数,带缺省值
print((lambda x, y=3: x + y)(5))
print((lambda x, y=3: x + y)(5, 6))
# keyword-only参数
print((lambda x, *, y=30: x + y)(5))
print((lambda x, *, y=30: x + y)(5, y=10))
# 可变参数
print((lambda *args: (x for x in args))(*range(5))) # 生成器
print((lambda *args: [x+1 for x in args])(*range(5))) # 列表
print((lambda *args: {x%2 for x in args})(*range(5))) # 集合
print((lambda *args: {str(x):x for x in args})(*range(5))) # 字典
print(dict(map(lambda x: (chr(65+x), 10-x), range(5)))) # 高阶函数,构建字典
d = dict(map(lambda x: (chr(65+x), 10-x), range(5))) # 高阶函数
sorted(d.items(), key=lambda x:x[1])
10.1.2、Lambda表达式2
def add(x, y): ----->互为等价
#add标识符是全局,形参x,y 形参x, y add指向一个函数对象,函数的名字,函数的名字'add'
return x + y
add = lambda x, y: x + y ----->互为等价
lambda函数表达式1:
lambda x: x ** 2 #------->使用lambda(函数)表达的单形参的函数,x的2次方
<function __main__.<lambda>(x)>
fn = _ #------->_表示为out
fn(5)
25
lambda函数表达式2:
#创建函数表达式
def guess(x):
if x >= 0:
return 'positive'
else:
return 'negative'
guess(5)
'positive'
def guess(x):
return 'positive' if x >= 0 else 'negative'
lambda x: 'positive' if x >=0 else 'negative'
guess = lambda x: 'positive' if x >=0 else 'negative' #---->创建函数表达式
guess(5)
'positive'
guess(-5)
'negative'
lambda函数表达式3:
def guess(x):
return 'positive' if x >= 0 else 'negative'
l1 = [lambda x: 'positive' if x >=0 else 'negative']
l1[0](6) #----->l1[0]表达一个函数,(6)表达一个元素
'positive'
lambda函数表达式4:
lambda :0
<function __main__.<lambda>()>
lambda x: 0
<function __main__.<lambda>(x)>
lambda x: []
<function __main__.<lambda>(x)>
l1 = lambda x: 0
l1(5)
0
lambda函数表达式5:
#函数表达式列表、元组、集合使用
lambda x: x+1, lambda x: x**2
(<function __main__.<lambda>(x)>, <function __main__.<lambda>(x)>)
(lambda x: [x])(5)
[5] ----->表达出列表
(lambda x: (x,x+1,x+2))(5)
(5, 6, 7) ----->表达出元组
(lambda x: {x, x+1})(5)
{5, 6} ----->表达出集合
(lambda x: {x:x+1 for x in range(x)})(5)
{0: 1, 1: 2, 2: 3, 3: 4, 4: 5} ----->表达出字典
(lambda x: {str(x):x+1 for x in range(x)})(5)
{'0': 1, '1': 2, '2': 3, '3': 4, '4': 5} ----->表达出字符串
lambda函数表达式6:
#创建生成器对象
g = (lambda x: (i**2 for i in range(5)))(5) #---->创建生成器对象
next(g)
9
(lambda x: [i for i in range(5)])(5)
[0, 1, 2, 3, 4]
(lambda x: [i**2 for i in range(5)])(5)
[0, 1, 4, 9, 16]
(lambda x: {i**2 for i in range(5)})(5)
{0, 1, 4, 9, 16}
(lambda x: {i%2 for i in range(5)})(5)
{0, 1}
lambda函数表达式7:
#设置元组
(lambda x, y: (x,y+10))(3,4) #---->设置两个参数变成元组
(3, 14)
(lambda x=5, y=6: (x,y))(3)
(3, 6) ----->设置x传参数
(lambda x=5, y=6: (x,y))(3,y=7)
(3, 7)
(lambda *args: list(args))(3,4,5) #---->将arg中所有元素拿出来装到list里面去
[3, 4, 5] ---->相同
(lambda *args: [i for i in args])(3,5,7)
[3, 5, 7] ---->相同
(lambda *args, x: (x ,[i for i in args]))(3,5,7,x=10)
(10, [3, 5, 7])
lambda函数表达式8:
#lambda作为比较
sorted([1,7,4,2],key=lambda x: x+10)
[1, 2, 4, 7] ------>做降序
sorted([1,7,4,2],key=lambda x: 10-x)
[7, 4, 2, 1] ------>做升序
lambda函数表达式9:
#比较做升序
def fn(x):
if isinstance(x,str):
return int(x,16)
else:
return x
sorted([1,7,3,'a',12,4], key=lambda x: int(x, 16) if isinstance(x, str) else x)
[1, 3, 4, 7, 'a', 12]
sorted([('a',100),('b',50),('c',1)], key = lambda x: x[1])
[('c', 1), ('b', 50), ('a', 100)] ------>做升序
d = dict([('a',100),('b',50),('c',1)])
sorted(d.items(),key = lambda item: item[1])
[('c', 1), ('b', 50), ('a', 100)] ------>做升序
image-20211007223115815
image-20211007234054116
11、高阶函数和时间模块
11.1、一等公民
函数在Python是一等公民(First-Class Object)
函数也是对象,是可调用对象
函数可以作为普通变量,也可以作为函数的参数、返回值
11.2、高阶函数
高阶函数(High-order Function)
数学概念 y = f(g(x))
在数学和计算机科学中,高阶函数应当是至少满足下面一个条件的函数
接受一个或多个函数作为参数
输出一个函数
观察下面的函数定义,回答问题
def counter(base):
def inc(step=1):
nonlocal base
base += step
return base
return inc
请问counter是不是高阶函数?
上面代码有没有问题?如果有,如何改?
如何调用以完成计数功能?
f1 = counter(5)和f2=counter(5),请问f1和f2相等吗?
def counter(base):
def inc(step=1): # 有没有闭包?
nonlocal base # 形参base也是外部函数counter的local变量
base += step
return base
return inc
c1 = counter(5)
print(c1())
print(c1())
print(c1())
f1 = counter(5)
f2 = counter(5)
print(f1 == f2) # 相等吗?
11.2.1、高阶函数使用
高阶函数1:
def counter(base): #---->base是局部变量(在外面的),是counter局部变量
def inc(step=1):
nonlocal base #---->非本地的局部变量base,而是外层的局部变量
base += step #---->这个base是局部变量
return base #---->返回base这个局部变量
return inc #返回一个函数对象
foo = counter(100)
foo()
101
foo()
102
f1 = counter(5) #各自在内存的不同位置
f2 = counter(5) #各自在内存的不同位置
print(f1 is f2) #每次调用的内存地址不同
print(f1 == f2) #比较内存地址
False
False
11.2.2、时间模块
datetime模块
datetime类是时间高级类
类方法,即使用类调用的方法,由类方法获得一个时间对象
now(tz=None) 返回当前时间的datetime对象,时间到微秒,如果tz为-
-None,返回当前时区的不带时区信息的时间
utcnow() 不带时区的0时区时间
fromtimestamp(timestamp, tz=None) 从一个时间戳返回一个datetime-
-对象
时间对象方法
timestamp() 返回一个到微秒的时间戳
时间戳:格林威治时间1970年1月1日0点到现在的秒数
构造方法 datetime.datetime(2016, 12, 6, 16, 29, 43, 79043)
year、month、day、hour、minute、second、microsecond,取datetime-
-对象的年月日
时分秒及微秒
weekday() 返回星期的天,周一0,周日6
isoweekday() 返回星期的天,周一1,周日7
date() 返回日期date对象
time() 返回时间time对象
import datetime
# 类方法获得时间对象
print(datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8)))) #
时区时间
print(datetime.datetime.now()) # 无时区时间
print(datetime.datetime.utcnow()) # UTC时间,可以认为是GMT或0时区时间
# 时间戳操作
stamp = datetime.datetime.now().timestamp() # 获得时间戳
print(stamp)
dt = datetime.datetime.fromtimestamp(stamp) # 从时间戳获得时间对象
print(dt)
print(type(dt.date()), dt.date())
print(type(dt.time()), dt.time())
1.1、import datetime #模块
#时间也是对象。 datetime类,timedalta类(增量)
datetime.datetime.now() #now返回当前时间的实列,对象
datetime.datetime(2021, 10, 8, 10, 56, 25, 440947)
datetime.datetime.utcnow() #简单认为,格林威治时间,0时区
datetime.datetime(2021, 10, 8, 3, 4, 45, 353669)
datetime.timezone(datetime.timedelta(hours=8)) #相对于utc的一个时区
datetime.timezone(datetime.timedelta(seconds=28800))
1.2、计算同类型时间差(不同类型时间差报错):
1.2.1、当前时间
d1 = datetime.datetime.now()
d1
datetime.datetime(2021, 10, 8, 14, 41, 3, 669716)
1.2.2、不带时区的0时区时间
d2 = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8)))
d2
datetime.datetime(2021, 10, 8, 14, 42, 44, 758063, tzinfo=datetime.timezone(datetime.timedelta(seconds=28800)))
1.2.3、同类型时间差计算
d3 = datetime.datetime.now()
d2 - d1 #---->报错
d3 - d1 #---->时间差
1.3、时间戳:
datetime.timedelta(seconds=158, microseconds=980028)#timedelta时间增量
d2.timestamp() #---->1970到现在的时间戳
1633663092.041996
d3.timestamp() #---->1970到现在的时间戳
1633663244.301467
1.4、构造时间对象的方式:
d4 = datetime.datetime(2021, 10, 8, 3, 12, 45, 291699)
d4
datetime.datetime(2021, 10, 8, 3, 12, 45, 291699)
d5 = datetime.datetime.fromtimestamp(1633663085)
d5
datetime.datetime(2021, 10, 8, 11, 18, 5)
1.5、构建时间对象:
1.5.1、解析字符串
d6 = datetime.datetime.strptime('2018716 20:30:15','%Y%m%d %H:%M:%S' ) #parse(解析字符串) %Y=年 %m=月 %d=日 %H=小时 %M=分钟 %S=秒 %z=时区
d6
datetime.datetime(2018, 7, 16, 20, 30, 15)
1.5.2、timedelta时间增量使用
d6 + datetime.timedelta(2, hours=1) #------>增加2天一小时
datetime.datetime(2018, 7, 18, 21, 30, 15)
(d3 - d1).total_seconds() #------>d3与d1时间差
158.980028
1.5.3、显示年月日
d6.strftime('%Y-%m-%d %H:%M:%S') #显示年月日
'2018-07-16 20:30:15'
d2.strftime('%Y-%m-%d %H:%M:%S %z') #显示年月日(显示时区)
'2021-10-08 11:18:12 +0800'
"{} {}".format(d6.year,"年") #显示年月日
'2018 年'
"{} 年{}月{}日 {}时{}分{} 秒".format(d6.year,d6.month,d6.day,d6.hour,d6.minute,d6.second)
'2018 年7月16日 20时30分15秒' #显示年月日
1.6、time时间模块
import time
time.sleep(10) #阻塞当前线程,睡10s
print('------------------')
print(123)
------------------
123
import time
while True:
time.sleep(1)
print('___________________')
___________________
___________________
___________________
___________________
___________________
1.6.1、rsplit时间拆分
d7 = datetime.datetime.strptime('20200909 20:10:30 +0800', '%Y%m%d %H:%M:%S %z')
d7
datetime.datetime(2020, 9, 9, 20, 10, 30, tzinfo=datetime.timezone(datetime.timedelta(seconds=28800)))
'20200909 20:10:30 +0800'.rsplit(maxsplit=1)[0] #-->rsplit时间拆分
'20200909 20:10:30'
datetime.datetime.strptime('20200909 20:10:30 +0800'.rpartition(' ')[0], '%Y%m%d %H:%M:%S ') #-->rspartiton时间拆分
datetime.datetime(2020, 9, 9, 20, 10, 30)
image-20211008143137749
image-20211009115737911
12、内建高阶函数
12.1、排序sorted
排序sorted
定义sorted(iterable, *, key=None, reverse=False) ->list
sorted(lst, key=lambda x:6-x) # 返回新列表
list.sort(key=lambda x: 6-x) # 就地修改
12.1.1、sorted排序
sorted([1,2,3,5,'7'],key=str)
[1, 2, 3, 5, '7']
sorted([12,1,2,'a'],key=lambda x: str(x), reverse=True) #---->reverse反转
['a', 2, 12, 1]
升序
sorted([12,1,2,'a'],key=lambda x: x if isinstance(x,int) else int(x, 16)) #----->isinstance(实列)
[1, 2, 'a', 12] #----->16为16进制
降序
sorted([12,1,2,'a'],key=lambda x: x-6 if isinstance(x,int) else int(x, 16))
[1, 2, 12, 'a']
12.2、过滤filter
定义filter(function, iterable)
对可迭代对象进行遍历,返回一个迭代器
function参数是一个参数的函数,且返回值应当是bool类型,或其返回值等效布尔值。
function参数如果是None,可迭代对象的每一个元素自身等效布尔值
list(filter(lambda x: x%3==0, [1,9,55,150,-3,78,28,123]))
list(filter(None, range(5)))
list(filter(None, range(-5, 5)))
12.2.1、filter过滤
next(filter(None,[1,2,3])) #---->None表示filter使用每一个元素自身等效True或False(过滤掉),True通过
1
list(filter(None,[1,2,3]))
[1, 2, 3]
list(filter(None,range(-4,4)))
[-4, -3, -2, -1, 1, 2, 3]
list(filter(None,[0,1,-1,True,False,'','abc',[],(),{},set()]))
[1, -1, True, 'abc']
list(filter(lambda x: x,range(10))) #filter如果写函数,单个参数
[1, 2, 3, 4, 5, 6, 7, 8, 9]
list(filter(lambda x: [x],range(10))) #filter如果写函数,单个参数
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list(filter(lambda x: x%2 == 0, range(10))) #-->互为等价
[0, 2, 4, 6, 8]
[x for x in range(10) if x % 2 == 0] #-->互为等价
[0, 2, 4, 6, 8]
12.3、映射map
定义map(function, *iterables) -> map object
对多个可迭代对象的元素,按照指定的函数进行映射
返回一个迭代器
list(map(lambda x: 2*x+1, range(10)))
dict(map(lambda x: (x%5, x), range(500)))
dict(map(lambda x,y: (x,y), 'abcde', range(10)))
12.3.1、map映射
list(map(lambda x: x+1,range(5)))
[1, 2, 3, 4, 5]
list(map(lambda x: (x,x+1),range(5))) #--->生成字典
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
dict(map(lambda x: (str(x),x+1),range(5))) #--->生成字典
{'0': 1, '1': 2, '2': 3, '3': 4, '4': 5}
dict(map(lambda x: (x+0x30,x+1),range(5))) #--->0x30为48
{48: 1, 49: 2, 50: 3, 51: 4, 52: 5}
dict(map(lambda x: (chr(x+97),x+1),range(5))) #--->生成字典
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
list(map(lambda x,y:(x,y),[1,2,3,4],'abcd'))
[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
dict(map(lambda x,y:(x,y),[1,2,3,4],'abcd')) #--->生成字典
{1: 'a', 2: 'b', 3: 'c', 4: 'd'}
12.4、拉链函数zip
zip(*iterables)
像拉链一样,把多个可迭代对象合并在一起,返回一个迭代器
将每次从不同对象中取到的元素合并成一个元组
list(zip(range(10),range(10)))
list(zip(range(10),range(10),range(5),range(10)))
dict(zip(range(10),range(10)))
{str(x):y for x,y in zip(range(10),range(10))}
12.4.1、zip拉链函数
list(zip(range(5)),range(5),range(5),'abc') #---->zip从每个range中拿去一个元素形成三元组
(0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4)]
list(zip(range(5),range(5),range(5),'abc'))
[(0, 0, 0, 'a'), (1, 1, 1, 'b'), (2, 2, 2, 'c')]
构建字典(常用方式)
dict(zip('abc',[1,2,3,4]))
{'a': 1, 'b': 2, 'c': 3}
标签:return,函数,04,python,add,fn,print,def
From: https://www.cnblogs.com/zikang/p/17103897.html