Python-函数、参数及参数解构-返回值作用域-递归函数-匿名函数-生成器-学习笔记
序
欠4年前的自己一份笔记,献给今后的自己 。
函数、参数及参数解构
函数
-
函数
数学定义:y=f(x),y是x的函数,x是自变量
Python函数
由若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元
完成一定的功能 -
函数的作用
-
函数
结构化编程对代码的最基本的封装,一般按照功能组织一段代码口封装的目的为了复用,减少冗余代码
代码更加简洁美观、可读易懂 -
函数的分类
-
函数
内建函数,如max()、reversed()等 -
函数
库函数,如math.ceil()等
函数定义、调用
-
def语句定义函数
-
def 函数名(参数列表):
函数体(代码块)
[return 返回值]
函数名就是标识符,命名要求一样
语句块必须缩进,约定4个空格
Python的函数没有return语句,隐式会返回一个None值
定义中的参数列表成为形式参数,只是一种符号表达,简称形参 -
调用
函数定义,只是声明了一个函数,它不会被执行,需要调用
调用的方式,就是函数名加上小括号,括号内写上参数
调用时写的参数是实际参数,是实实在在传入的值,简称实参 -
函数举例
def add(x, y):result = x + yreturn resultout = add(4, 5)
print(out) # 9
-
上面只是一个函数的定义,有一个函数叫做add,接收2个参数口计算的结果,通过返回值返回
-
调用通过函数名add加2个参数,返回值可使用变量接收
-
定义需要在调用前,也就是说调用时,已经被定义过了,否则抛NameError异常口函数是可调用的对象,callable()
-
看看这个函数是不是通用的?体会一下函数的好处
函数参数
参数调用时传入的参数要和定义的个数相匹配(可变参数例外)
-
位置参数
def f(x, y, z)调用使用f(1,3,5)
按照参数定义顺序传入实参 -
关键字参数
def f(x, y, z) 调用使用 f(x=1, y =3, z=5) -
使用形参的名字来出入实参的方式,如果使用了形参名字,那么传参顺序就可和定义顺序不同
-
传参
f(z= None, y =10, x=[1])
f((1,), z= 6, y =4.1)
f(y =5, z= 6, 2) # -
要求位置参数必须在关键字参数之前传入,位置参数是按位置对应的
函数参数默认值
- 参数默认值
定义时,在形参后跟上一个值
def add (x=4,y=5):return x+y
测试调用 add(6, 10)、add(6,y=7)、add(×=5) add()。 add (y=7)。 add(x=5, 6), add(y =8, 4)。 add (x=5, y =6), add (y = 5, x=6)
测试 def add (x=4,y)
-
作用
参数的默认值可以在未传入足够的实参的时候,对没有给定的参数赋值为默认值
参数非常多的时候,并不需要用户每次都输入所有的参数,简化函数调用 -
举例
定义一个函数login,参数名称 host、port、 username、password
def login(host='127.0.0.1',port='8080',username='wayne',password ='magedu'):print('{}:{}@{}/{}'.format(host, port, username, password))login() # 127.0.0.1:8080@wayne/magedulogin ('127.0.0.1', 80, 'tom', 'tom') # 127.0.0.1:80@tom/tomlogin('127.0.0.1', username='root') # 127.0.0.1:8080@root/magedulogin('localhost', port= 80, password = 'com') # localhost:80@wayne/comlogin(port=80, password = 'magedu', host= 'www') #www:80@wayne/magedu
可变参数
- 问题
有多个数,需要累加求和
def add(nums):sum = 0for x in nums:sum += xreturn sumprint(add([1, 3, 5])) # 9
print(add((2, 4, 6))) # 12
传入一个可迭代对象,迭代元素求和
- 可变参数
一个形参可以匹配任意个参数 - 位置参数的可变参数
有多个数,需要累加求和
def add(*nums):sum = 0print(type(nums)) # <class 'tuple'>for x in nums:sum += xprint(sum)add(3, 6, 9) # 18
-
在形参前使用*表示该形参是可变参数, 可以接收多个实参
-
收集多个实参为一个tuple
-
思考:关键字参数能否也能传递任意多个吗?
-
关键字参数的可变参数
配置信息打印
def showconfig(**kwargs):for k, v in kwargs.items():print('{}={}'.format(k, v))showconfig(host='127.0.0.1', port='8080',username='wayne', password = 'magedu')# host=127.0.0.1
# port=8080
# username=wayne
# password=magedu
-
形参前使用**符号,表示可以接收多个关键字参数
-
收集的实参名称和值组成一个字典
-
可变参数混合使用
配置信息打印
def showconfig(username, password, **kwargs):print(username, username, kwargs)def showconfig(username, *args, **kwargs):print(username, args, kwargs)# 下面代码会报错
def showconfig(username, password, **kwargs, *args):print(username, password, kwargs, args)
-
总结
1、有位置可变参数和关键字可变参数
2、位置可变参数在形参前使用一个星号*
3、关键字可变参数在形参前使用两个星号** 浅院
4、位置可变参数和关键字可变参数都可以收集若干个实参,位置可变参数收集形成一个tuple,关键字可变参数收集形成一个dict
5、混合使用参数的时候,可变参数要放到参数列表的最后,普通参数需要放到参数列表前面,位置可变参数需要在关键字可变参数之前 -
举例
def fn(x, y, *args, **kwargs):print(x)print(y)print(args)print(kwargs)fn(3, 5, 7, 9, 10, a=1, b='python')
print('*'*60)
fn(3, 5)
print('-'*60)fn(3, 5, 7)
print('~'*60)fn(3, 5, a=1, b='python')
print('|'*60)# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 18, in <module>
# fn(7, 9, y=5, x=3, a=1, b='python')
# ~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# TypeError: fn() got multiple values for argument 'y'
fn(7, 9, y=5, x=3, a=1, b='python') # 错误,7和9分别赋给了x,y,又y=5、X=3,重复了输出:
3
5
(7, 9, 10)
{'a': 1, 'b': 'python'}
************************************************************
3
5
()
{}
------------------------------------------------------------
3
5
(7,)
{}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3
5
()
{'a': 1, 'b': 'python'}
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Traceback (most recent call last):File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 27, in <module>fn(7, 9, y=5, x=3, a=1, b='python')~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: fn() got multiple values for argument 'y'
举例
def fn(*args, x, y, **kwargs):print(x)print(y)print(args)print(kwargs)# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 11, in <module>
# fn(3, 5)
# ~~^^^^^^
# TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
# fn(3, 5)# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 18, in <module>
# fn(3, 5, 7)
# ~~^^^^^^^^^
# TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
# fn(3, 5, 7)# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 26, in <module>
# fn(3, 5, a=1, b='python')
# ~~^^^^^^^^^^^^^^^^^^^^^^^
# TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
# fn(3, 5, a=1, b='python')fn(7, 9, y=5, x=3, a=1, b='python')# 输出:
# 3
# 5
# (7, 9)
# {'a': 1, 'b': 'python'}
keyword-only参数
- keyword-only参数(Python3加入)
如果在一个星号参数后,或者一个位置可变参数后,出现的普通参数,实际上已经不是普通的参数了,而是keyword-only参数
def fn(*args, x):print(x)print(args)# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 7, in <module>
# fn (3,5)
# ~~~^^^^^
# TypeError: fn() missing 1 required keyword-only argument: 'x'
# fn(3, 5)# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 14, in <module>
# fn(3, 5, 7)
# ~~^^^^^^^^^
# TypeError: fn() missing 1 required keyword-only argument: 'x'
# fn(3, 5, 7)fn(3, 5, x=7)# 输出:
# 7
# (3, 5)
- 举例
def(**kwargs, x):print(x)print(kwargs)
直接报语法错误
可以理解为kwargs会截获所有的关键字参数,就算你写了x=5,x也永远得不到这个值,所以语法
- keyword-only 参数另一种形式
def fn(*, x, y):print(x, y) # 5 6 fn(x=5, y=6)
* 号之后,普通形参都变成了必须给出的keyword-Only 参数
可变参数和参数默认值
- 举例一
def fn(*args, x=5):print(x)print(args)fn() # 等价于fn(x=5) fn(5)print('*' * 30 )
fn(x=6)
print('~' * 30 )
fn(1, 2, 3, x=10)
输出:
5
()
******************************
6
()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10
(1, 2, 3)
- 举例二
def fn(y, *args, x=5):print('x={}, y ={}'.format(x, y))print(args)# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 6, in <module>
# fn()
# ~~^^
# TypeError: fn() missing 1 required positional argument: 'y'
# fn()fn(5)
print('|'*30)
# 输出:
# x=5, y =5
# ()# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 16, in <module>
# fn(x=6) #
# ~~^^^^^
# TypeError: fn() missing 1 required positional argument: 'y'
# fn(x=6) #fn(1, 2, 3, x=10)
# 输出:
# x=10, y =1
# (2, 3)print('~' * 30 )
# 编译报错
# fn(y=17, 2, 3, x=10) #print('*'* 30 )
# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 42, in <module>
# fn(1, 2, y=3, x=10) #
# ~~^^^^^^^^^^^^^^^^^
# TypeError: fn() got multiple values for argument 'y'
fn(1, 2, y=3, x=10) #
- 举例三
def fn(x=5, **kwargs):print('x=0'.format(x))print(kwargs)fn()print('*'*60)
fn(5)
print('-'*60)fn(x=6) #
print('!'*60)fn(y=3, x=10)
print('~'*60)fn(3, y=10)输出:x=0
{}
************************************************************
x=0
{}
------------------------------------------------------------
x=0
{}
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
x=0
{'y': 3}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
x=0
{'y': 10}
函数参数
-
参数规则
参数列表参数一般顺序是,普通参数、缺省参数、可变位置参数、keyword-only参数(可带缺省值)、可变关键字参数
-
注意
代码应该易读易懂,而不是为难别人
请按照书写习惯定义函数参数 -
参数规则举例
参数列表参数一般顺序是,普通参数、缺省参数、可变位置参数、keyword-only参数(可带缺省值)、可变关键字参数
def connect(host='localhost', port='3306', user='admin', password='admin', **kwargs):print(host, port)print(user, password)print(kwargs)connect(db='cmdb')print('*'*60)connect(host='192.168.1.123', db='cmdb')print('~'*60)connect(host='192.168.1.123', db='cmdb', password='mysql')输出:
localhost 3306
admin admin
{'db': 'cmdb'}
************************************************************
192.168.1.123 3306
admin admin
{'db': 'cmdb'}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
192.168.1.123 3306
admin mysql
{'db': 'cmdb'}
参数解构
举例
加法函数
def add(x, y):return x + yprint(add(4, 5)) # 9# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 8, in <module>
# print(add((4, 5)))
# ~~~^^^^^^^^
# TypeError: add() missing 1 required positional argument: 'y'
# print(add((4, 5)))t = (4, 5)print(add(t[0], t[1])) # 9print(add(*t)) # 9
print(add(*(4, 5))) # 9
print('*' * 30 , add(*range(1, 3))) # 3 print(add(*[4, 5])) # 9 print(add(*{4, 6})) # 10
参数解构
- 给函数提供实参的时候,可以在集合类型前使用*或者**,把集合类型的结构解开,提取出所有元素作为函数的实参
- 非字典类型使用 ** 解构成位置参数
- 字典类型使用**解构成关键字参数
- 提取出来的元素数目要和参数的要求匹配,也要和参数的类型匹配
def add(x, y):return x + yprint(add(*(4, 5))) # 9print('*' * 30 )print(add(*[4, 5])) # 9
print('~' * 30 )print(add(*{4, 6})) # 10print('-' * 30 )d = {'x': 5, 'y': 6}print(add(**d)) # 11 # Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 25, in <module>
# print(add(**{'a': 5, 'b': 6}))
# ~~~^^^^^^^^^^^^^^^^^^^^
# TypeError: add() got an unexpected keyword argument 'a'
# print(add(**{'a': 5, 'b': 6}))
- 参数解构和可变参数
给函数提供实参的时候,可以在集合类型前使用*或者**,把集合类型的结构解开,提取出所有元素作为函数的实参
def add(*iterable):result = 0for x in iterable:result += xreturn resultprint(add(1, 2, 3)) # 6 print(add(*[1, 2, 3])) # 6 print(add(*range(10))) # 45
练习
- 编写一个函数,能够接受至少2个参数,返回最小值和最大值
def fn(x, y ):if x > y:return xelse :return yprint(fn(1,2)) # 2
print(fn(3,2)) # 3
- 编写一个函数,接受一个参数n,n为正整数,左右两种打印方式。要求数字必须对齐
上三角
def triangle_print(n):for i in range(1, n + 1):for j in range(n, 0, -1):if i < j:print(' ' * len(str(j)), end=' ') # 因为随着数字位数不同,宽度不同# ‘12 ’ ‘100 ’ ‘1000 ’else:print(j, end=' ')print()triangle_print(12)
下三角
def triangle_print(n):for i in range(1, n+ 1):for j in range(1, n + 1):if i > j:print(' ' * len(str(j)), end=' ')else:print(n + 1 - j, end=' ')print()triangle_print(12)
函数返回值、作用域
函数的返回值
- 举例一
def showplus(x):print(x) # 5 return x + 1print(showplus(5) ) # 6
- 举例二
def showplus(x):print(x) # 5return x + 1# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 7# print(x+1)# 会执行吗?# ^# SyntaxError: invalid character '(' (U+FF08)#print(x+1)# 会执行吗?showplus(5) # 6
函数的返回值
- 多条return语句 def guess(x):
def guess(x):if x > 3:return "> 3"else:return "< = 3"print(guess(10)) # > 3
- 多条return语句
def showplus(x):print(x) # 5 return x + 1return x + 2print(showplus(5)) # 6
- 举例
def fn(x):for i in range(x):if i > 3:return ielse:print("O is not greater than 3".format(x))print(fn(5)) # 打印什么?print('*' * 60)print(fn(3)) # 打印什么?
输出:
O is not greater than 3
O is not greater than 3
O is not greater than 3
O is not greater than 3
4
************************************************************
O is not greater than 3
O is not greater than 3
O is not greater than 3
None
总结
-
Python函数使用return语句返回“返回值”
-
所有函数都有返回值,如果没有return语句,隐式调用return None
-
return 语句并不一定是函数的语句块的最后—条语句
-
一个函数可以存在多个return语句,但是只有一条可以被执行。如果没有一条return语句被执行到,隐式调用return None
-
如果有必要,可以显示调用return None,可以简写为return
-
如果函数执行了return语句,函数就会返回,当前被执行的return语句之后的其它语句就不会被执行了
-
作用:结束函数调用、返回值
-
返回多个值
def showlist():return [1, 3,5]print(showlist()) # [1, 3, 5]
print(type(showlist())) # <class 'list'>
showlist函数是返回了多个值吗?
这次showlist函数是否返回了多个值呢?
def showlist():return 1, 3, 5print(showlist()) # (1, 3, 5)
print(type(showlist())) # <class 'tuple'>
-
返回多个值
-
函数不能同时返回多个值
-
return [1, 3, 5] 是指明返回一个列表,是一个列表对象
-
return 1, 3. 5 看似返回多个值,隐式的被python封装成了一个元组 def
x, y, z = showlist()# 使用解构提取更为方便
def showlist():return 1, 3, 5x, y, z = showlist()
print(x, y, z) # 1 ,3, 5
函数嵌套
- 函数嵌套
在一个函数中定义了另外一个函数
def outer():def inner():print("inner")print("outer")inner()outer()# 输出:
# outer
# inner# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 9, in <module>
# inner()
# ^^^^^
# NameError: name 'inner' is not defined. Did you mean: 'iter'?
# inner()
- 函数有可见范围,这就是作用域的概念
- 内部函数不能被外部直接使用,会抛NameError异常
作用域***
- 作用域
一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域口举例,对比下面2个函数
x = 5def foo():print(x)foo() # 5
x到底可见还是不可见?
- 全局作用域
在整个程序运行环境中都可见 - 局部作用域
在函数、类等内部可见
局部变量使用范围不能超过其所在的局部作用域
嵌套结构
def outer1(): #o = 65def inner():print("inner {}".format(o)) # inner 65print(chr(o)) # Aprint("outer {}".format(o)) # outer 65inner()outer1()
def outer2(): #o = 65def inner():o = 97print("inner {}".format(o)) # inner 97print(chr(o)) # aprint("outer {}".format(o)) # outer 65inner()outer2()
区别上面o 的区别
-
从嵌套结构例子看出
1、外层变量作用域在内层作用域可见
2、内层作用域inner中,如果定义了0=97,相当于当前作用域中重新定义了一个新的变量。,但是这个o并没有覆盖外层作用域outer中的o
3、再看下面代码
-
代码
-
x+=1其实是×=×+1
-
相当于在foo内部定义一个局部变量X,那么foo内部所有x都是这个局部变量X了
-
但是这个x还没有完成赋值,就被右边拿来做加1操作了
-
如何解决这个问题?
-
全局变量global
x = 5
def foo():global xx += 1print(x) # 6 foo()
-
使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x
-
全局作用域中必须有x的定义
-
如果全局作用域中没有x定义会怎样?
-
全局变量global
# x = 5def foo():global xx = 10x += 1print(x) # 11# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 11, in <module>
# print(x)
# ^
# NameError: name 'x' is not defined
# print(x)foo()
# 做这些实验建议不要使用ipython、jupyter,因为它会上下文中有x定义,可能测试不出效果
- 使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x
- 但是,x = 10赋值即定义,x在内部作用域为一个外部作用域的变量赋值,所以x+=1不会报错。注意,这里x的作用域还是全局的
- global总结
x+=1这种是特殊形式产生的错误的原因?先引用后赋值,而python动态语言是赋值才算定义, 才能被引用。解决办法,在这条语句前增加×=0之类的赋值语句,或者使用global告诉内部作用域,去全局作用域查找变量定义涔悦
内部作用域使用x=5之类的赋值语句会重新定义局部作用域使用的变量x,但是,一旦这个作用域中使用global声明x为全局的,那么X=5相当于在为全局作用域的变量x赋值
global使用原则
外部作用域变量会内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
如果函数需要使用外部全局变量,请使用函数的形参传参解决
一句话:不用global。学习它就是为了深入理解变量作用域
闭包*
- 自由变量:未在本地作用域中定义的变量。例如定义在内存函数外的外层函数的作用域中的变量
- 闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是JavaScript
- 先看右边一段代码
第4行会报错吗?为什么
第8行打印什么结果?
第10行打印什么结果?
-
代码解析
第4行会报错吗?为什么
不会报错,c已经在counter函数中定义过了。而且inc中的使用方式是为c的元素修改值,而不是重新定义变量
第8行打印什么结果?
打印 12
第10行打印什么结果?
打印 3
第9行的c和counter中的c不一样,而inc引用的是自由变量正式counter的变量c
这是Python2中实现闭包的方式,Python3还可以使用nonlocal 关键字 -
下面这段代码会报错吗?为什么?
-
使用globa能否解決?
-
使用global可以解决,但是这使用的是全局变量,而不是闭包
-
如果要对普通变量的闭包,Python3中可以使用nonlocal
nonlocal关键字
- 使用了nonlocal关键字,将变量标记为不在本地作用域定义,而在上级的某一级局部作用域中定义,但不能是全局作用域中定义
def counter():count = 0def inc():nonlocal countcount += 1return countreturn incfoo = counter()print(foo()) # 1print(foo()) # 2
a = 50def counter():# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 5# nonlocal a # 上一级作用域是全局作用域# ^^^^^^^^^^# SyntaxError: no binding for nonlocal 'a' foundnonlocal a # 上一级作用域是全局作用域a += 1print(a)count = 0def inc():nonlocal countcount += 1return countreturn incfoo = counter()
foo()
foo()
- count 是外层函数的局部变量,被内部函数引用
- 内部函数使用nonlocal关键字声明count变量在上级作用域而非本地作用域中定义
- 左边代码可以正常使用,且形成闭包
- 右边代码不能正常运行,变量a 不能在全局作用域中
默认值的作用域
- 默认值举例一
def foo(xyz=1):print(xyz)foo() # 打印什么? 1 foo() # 打印什么? 1 # Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 9, in <module>
# print(xyz) # 打印什么?
# ^^^
# NameError: name 'xyz' is not defined
print(xyz) # 打印什么?
- 默认值举例二
def foo (xyz = []):xyz.append (1)print(xyz)# 打印什么?foo()#打印什么? [1]foo() # #打印什么? [1, 1]
- 为什么第二次调用foo函数打印的是[1,1]?
因为函数也是对象,python把函数的默认值放在了属性中,这个属性就伴随着这个函数对象的整个生命周期
查看foo.__defaults__属性
- 运行这个例子
def foo(xyz=[], u='abc', z=123):xyz.append(1)return xyzprint(foo(), id(foo)) # [1] 4373267616print(foo.__defaults__) # ([1], 'abc', 123)print(foo(), id(foo)) # [1, 1] 4373267616 print(foo.__defaults__) # ([1, 1], 'abc', 123)
-
函数地址并没有变,就是说函数这个对象的没有变,调用它,它的属性 __defaults__中使用元组保存默认值
-
xyz默认值是引用类型,引用类型的元素变动,并不是元组的变化
-
非引用类型例子
def foo(w, u='abc', z=123):u = 'xyz'z = 789print(w, u, z)print(foo.__defaults__) # ('abc', 123)foo('magedu') # magedu xyz 789print(foo.__defaults__) # ('abc', 123)
属性__defaults__中使用元组保存所有位置参数默认值,它不会因为在函数体内使用了它而发生改变
- 举例
def foo(w, u='abc', *, z=123, zz=[456]):u = 'xyz'z = 789zz.append(1)print(w, u, z, zz) # magedu xyz 789 [456, 1]print(foo.__defaults__) # ('abc',)foo('magedu')print(foo.__kwdefaults__) # {'z': 123, 'zz': [456, 1]}
-
属性__defaults__ 中使用元组保存所有位置参数默认值
-
__kwdefaults__中使用字典保存所有keyword-only参数的默认值
-
使用可变类型作为默认值,就可能修改这个默认值
-
有时候这个特性是好的,有的时候这种特性是不好的,有副作用
-
如何做到按需改变呢?看下面的2种方法
def foo(xyz=[], u='abc', z=123):xyz = xyz[:] # 影子拷贝xyz.append(1)print(xyz)foo() # [1]
print('*' * 60)print('1、', foo.__defaults__) # 1、 ([], 'abc', 123)print('-' * 60)foo() # [1]print('2、', foo.__defaults__) # 2、 ([], 'abc', 123)foo([10]) # [10, 1]print('3、', foo.__defaults__) # 3、 ([], 'abc', 123)foo([10, 5]) # [10, 5, 1]print('4、', foo.__defaults__) # 4、 ([], 'abc', 123)
1、函数体内,不改变默认值
xyz都是传入参数或者默认参数的副本,如果就想修改原参数,无能为力
-
第一种方法
使用影子拷贝创建一个新的对象,永远不能改变传入的参数 -
第二种方法
通过值的判断就可以灵活的选择创建或者修改传入对象
这种方式灵活,应用广泛
很多函数的定义,都可以看到使用None这个不可变的值作为默认参数,可以说这是一种惯用法
变量名解析原则LEGB
-
Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡
-
Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
-
Global,全局作用域,即一个模块的命名空间。模块被impor 创建,解释器退出时消亡
-
Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如 print(open),print和open都是内置的变量
-
所以一个名词的查找顺序就是LEGB
函数的销毁
全局函数
def foo(xyz=[], u='abc', z=123):xyz.append(1)return xyzprint(foo(), id(foo), foo.__defaults__) # [1] 4374004896 ([1], 'abc', 123)def foo(xyz=[], u='abc', z=123):xyz.append(1)return xyzprint(foo(), id(foo), foo.__defaults__) # [1] 4374282304 ([1], 'abc', 123)del foo# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 19, in <module>
# print(foo(), id(foo), foo.__defaults__)
# ^^^
# NameError: name 'foo' is not defined
print(foo(), id(foo), foo.__defaults__)
- 全局函数销毁
重新定义同名函数
del 语句删除函数对象
程序结束时
局部函数
- 局部函数销毁
重新在上级作用域定义同名函数
del 语句删除函数对象
上级作用域销毁时
def foo(xyz=[], u='abc', z=123):xyz.append(1)def inner(a=10):passprint('1'*30,inner) # <function foo.<locals>.inner at 0x103108040>def inner(a=100):print('3'*30,xyz)print('2'*30,inner) # <function foo.<locals>.inner at 0x1032979c0>return innerbar = foo()print('*' * 60)
print(id(foo), id(bar), foo.__defaults__, bar.__defaults__) # 4368516256 4370856384 ([1], 'abc', 123) (100,)del barprint('~' * 60)
# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 25, in <module>
# print(id(foo), id(bar), foo.__defaults__, bar.__defaults__)
# ^^^
# NameError: name 'bar' is not defined
print(id(foo), id(bar), foo.__defaults__, bar.__defaults__)
递归函数
函数执行流程
- 全局帧中生成foo1、foo2、foo3、main函数对象口
- main函数调用
- main中查找内建函数print压栈,将常量字符串压栈,调用函数,弹出栈顶
- main中全局查找函数foo1压栈,将常量100、101压栈,调用函数foo1,创建栈帧。Print函数压栈,字符串和变量b、b1 压栈,调用函数,弹出栈顶,返回值。
- main中全局查找foo2函数压栈,将常量200压栈,调用foo2, 创建栈帧。foo3函数压栈,变量c引用压栈,调用foo3,创建栈帧。foo3完成print函数调用后返回。foo2恢复调用,执行print后,返回值。main中foo2调用结束弹出栈顶人的a继续执行print函数调用,弹出栈顶。main函数返回
# http://pythontutor.com/visualize.html#mode=edit def foo 1(b, b1=3):def foo1(b, b1=3):print("foo1 called", b, b1)def foo2(c):foo3(c)print("foo2 called ", c)def foo3(d):print("foo3 called ", d)def main():print("main called")foo1(100, 101)foo2(200)print("main ending")main()
输出:
main called
foo1 called 100 101
foo3 called 200
foo2 called 200
main ending
函数执行流程
# http://pythontutor.com/visualize.html#mode=edit def foo 1(b, b1=3):def foo1(b, b1=3):print("foo1 called", b, b1)def foo2(c):pass
递归Recursion
-
函数直接或者间接调用自身就是递归
-
递归需要有边界条件、递归前进段、递归返回段口递归一定要有边界条件
-
当边界条件不满足的时候,递归前进口当边界条件满足的时候,递归返回
- 斐波那契数列Fibonacci number: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89,144,
- 如果设F(n)为该数列的第n项(n E N*),那么这句话可以写成如下形式::F(n)=F(n-1)+F(n-2)
- F(0)=0 , F(1)=1, F(n)= F(n-1) + F(n-2)
pre = 0cur = 1 # No1print(pre, cur, end='') # 0 1print('~'*30)n = 4# 100pfor i in range(n - 1):pre, cur = cur, pre + curprint(cur, end=' ') # 1 2 3
- F(0)=0 , F(1)=1, F(n)= F(n-1)+ F(n-2)
def fib(n):return 1 if n < 2 else fib(n - 1) + fib(n - 2)for i in range(5):print(fib(i), end='') # 11235解析:fib(3) + fib(2)
fib(3)调用 fib(3), fib(2), fib(1)
fib(2)调用fib(2)、fib(1)
fib(1)是边界
递归要求
递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用,就是无限调用、
递归调用的深度不宜过深
Python对递归调用的深度做了限制,以保护解释器
超过递归深度限制,抛出RecursionError: maxinum recursion depth exceeded 超出最大深度
sys.getrecursionlimit()
递归的性能
for 循环
import datetimestart = datetime.datetime.now()
pre = 0cur = 1 # No1print(pre, cur, end=' ')n = 35for i in range(n - 1):pre, cur = cur, pre + curprint(cur, end=' ')print()delta = (datetime.datetime.now() - start).total_seconds()print('time=',delta)
输出:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465
time= 5.7e-05
递归
import datetimen = 35start = datetime.datetime.now()def fib(n):return 1 if n < 2 else fib(n - 1) + fib(n - 2)for i in range(n):print(fib(i), end=' ')print()delta = (datetime.datetime.now() - start).total_seconds()print('time=',delta)输出:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465
time= 1.562472
-
循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
-
fib函数代码极简易懂,但是只能获取到最外层的函数调用,内部递归结果都是中间结果。而且给定一个n 都要进行近2n次递归,深度越深,效率越低。为了获取斐波那契数列需要外面在套一个n次的循环,效率就更低了
-
递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就溢出了
-
思考:这个极简的递归代码能否提高性能呢?
-
斐波那契数列的改进
pre = 0cur = 1 # No1print(pre, cur, end=' ')def fib(n, pre=0, cur=1): # recursionpre, cur = cur, pre + curprint(cur, end=' ')if n == 2:returnfib(n - 1, pre, cur)fib(35)
输出:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465
-
改进
-
左边的fib函数和循环的思想类似
-
参数n是边界条件,用n来计数
-
上一次的计算结果直接作为函数的实参口效率很高
-
和循环比较,性能相近。所以并不是说递归一定效率低下。但是递归有深度限制
-
对比一下三个fib函数的性能
def foo1():foo2()def foo2():foo1()foo1()
间接递归,是通过别的函数调用了函数自身
但是,如果构成了循环递归调用是非常危险的,但是往往这种情况在代码复杂的情况下,还是可能发生这种调用。要用代码的规范来避免这种递归调用的发生
递归总结
-
递归是一种很自然的表达,符合逻辑思维
-
递归相对运行效率低,每一次调用函数都要开辟栈帧
-
递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了
-
如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
-
绝大多数递归,都可以使用循环实现
-
即使递归代码很简洁,但是能不用则不用递归
递归调用对比
import datetime# Fib Seq
start = datetime.datetime.now()
pre = 0
cur = 1 # No1
print(pre, cur, end=' ')
n = 35
# loop
for i in range(n - 1):pre, cur = cur, pre + curprint(cur, end=' ')
delta = (datetime.datetime.now() - start).total_seconds()
print(delta)# Fib Seq
start = datetime.datetime.now()
pre = 0
cur = 1 # No1
print(pre, cur, end=' ')# recursion
def fib1(n, pre=0, cur=1):pre, cur = cur, pre + curprint(cur, end=' ')if n == 2:returnfib1(n - 1, pre, cur)fib1(n)
delta = (datetime.datetime.now() - start).total_seconds()print(delta)start = datetime.datetime.now()def fib2(n):if n < 2:return 1return fib2(n - 1) + fib2(n - 2)for i in range(n):print(fib2(i), end=' ')
delta = (datetime.datetime.now() - start).total_seconds()
print(delta)输出:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 4.5e-05
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 3.6e-05
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 1.467969
练习
求n的阶乘
def fac(n):if n == 1:return 1return n * fac(n - 1)def fac1(n, p=1):if n == 1:return pp *= nprint(p)fac1(n - 1, p)return pdef fac2(n, p=None):if p is None:p = [1]if n == 1:return p[0]p[0] *= nprint(p[0])fac2(n - 1, p)return pn = 10print('1' * 30, fac(n))print('2' * 30, fac1(n))print('3' * 30, fac2(n))输出:
111111111111111111111111111111 3628800
10
90
720
5040
30240
151200
604800
1814400
3628800
222222222222222222222222222222 10
10
90
720
5040
30240
151200
604800
1814400
3628800
333333333333333333333333333333 [3628800]
fac函数性能最好,因为时间复杂度是相同的,fac最简单
将一个数逆序放入列表中,例如1234=>[4,3,2,1]
- 方法一
data = str(1234)def reversal(x):if x == -1:return ''return data[x] + reversal(x - 1)print(reversal(len(data) - 1))
- 方法二
def revert(n, lst=None):if lst is None:lst = []x, y = divmod(n, 10)lst.append(y)if x == 0:return lstreturn revert(x, lst)print(revert(12345))
- 方法三
num = 1234def revert(num, target=[]):if num:target.append(num[len(num) - 1]) # target.append(num[-1:])revert(num[: len(num) - 1])return targetprint(revert(str(num)))
解决猴子吃桃问题
- 猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想吃时,只剩下一个桃子了。求第一天共摘多少个桃子
def peach(days=10):if days == 1:return 1return (peach(days - 1) + 1) * 2print(peach())
注意这里必须是10,因为return (peach(days-1)+1)*2立即拿不到结果,必须通过再一次进入函数时判断是不是到了最后一天。也就是当前使用的值是由下一次函数调用得到,所以要执行10次函数调用换种方式表达
def peach(days=1):if days == 10:return 1return (peach(days + 1) + 1) * 2print(peach())
匿名函数
-
匿名,即没有名字
-
匿名函数,即没有名字的函数
没有名字如何定义
没有名字如何调用
如果能调用,如何使用 -
Python借助Lambda表达式构建匿名函数
-
格式
lambda 参数列表:表达式
lambda x: x ** 2(lambda x: x ** 2)(4) # 调用foo = lambda x, y: (x + y) ** 2 # 不推荐这么用print(foo(2, 1)) # 9 def foo(x, y): # 建议使用普通函数return (x + y) ** 2print(foo(2, 1)) # 9
-
使用lambda关键字来定义匿名函数
-
参数列表不需要小括号
-
冒号是用来分割参数列表和表达式的薪职业学院口不需要使用return,表达式的值,就是匿名函数返回值口 lambda表达式(匿名函数)只能写在一行上,被称为单行函数
-
用途
在高阶函数传参时,使用lam bda表达式,往往能简化代码
print((lambda: 0)()) # 0print((lambda x, y=3: x + y)(5)) #8print((lambda x, y=3: x + y)(5, 6)) #11print((lambda x, *, y=30: x + y)(5)) #35print((lambda x, *, y=30: x + y)(5, y=10)) # 15print((lambda *args: (x for x in args))(*range(5))) #<generator object <lambda>.<locals>.<genexpr> at 0x1046cda80>print((lambda *args: [x + 1 for x in args])(*range(5))) #[1, 2, 3, 4, 5]print((lambda *args: {x + 2 for x in args})(*range(5))) # {2, 3, 4, 5, 6}print([x for x in (lambda *args: map(lambda x: x + 1, args))(*range(5))]) # [1, 2, 3, 4, 5]print([x for x in (lambda *args: map(lambda x: (x + 1, args), args))(*range(5))]) # [(1, (0, 1, 2, 3, 4)), (2, (0, 1, 2, 3, 4)), (3, (0, 1, 2, 3, 4)), (4, (0, 1, 2, 3, 4)), (5, (0, 1, 2, 3, 4))]
生成器***
-
生成器generator
-
生成器指的是生成器对象,可以由生成器表达式得到,也可以使用yield关键字得到一个生成器
-
生成器函数
函数体中包含yield语句的函数,返回生成器对象
生成器对象,是一个可迭代对象,是一个迭代器
生成器对象,是延迟计算、惰性求值的
生成器
举例一
def inc():for i in range(5):yield iprint(type(inc)) # <class 'function'>
print(type(inc())) # <class 'generator'>x = inc()print(type(x)) # <class 'generator'>print(next(x)) # 0print('*'*30)
for m in x:print(m, 1 * 1)# 输出:
# 1 1
# 2 1
# 3 1
# 4 1print('-------------------------')for m in x:print(m, '**')
- 普通的函数调用fn(),函数会立即执行完毕,但是生成器函数可以使用next函数多次执行
- 生成器函数等价于生成器表达式,只不过生成器函数可以更加的复杂
y = (i for i in range(5))
print(type(y)) # <class 'generator'>print(next(y)) # 0 print(next(y)) # 1
举例
def gen():print('line 1')yield 1print('line 2')yield 2print('line 3')return 3next(gen()) # line 1print('-'*30)
next(gen()) # line 1g = gen()print('*'*30)
print(next(g)) # line 1print(next(g)) # line 2
print('~'*30)print(next(g)) # StopIteration# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 27, in <module>
# print(next(g)) # StopIteration
# ~~~~^^^
# StopIteration: 3print(next(g, 'End')) # 没有元素给个缺省值
-
在生成器函数中,使用多个yield语句,执行一次后会暂停执行,把yield表达式的值返回口再次执行会执行到下一个yield语句
-
return语句依然可以终止函数运行,但return语句的返回值不能被获取到
-
return会导致无法继续获取下一个值,抛出 Stoplteration异常
-
如果函数没有显示的return语句,如果生成器函数执行到结尾,一样会抛出Stoplteration异常
生成器函数
-
包含yield语句的生成器函数生成生成器对象的时候,生成器函数的函数体不会立即执行口 next(generator) 会从函数的当前位置向后执行到之后碰到的第一个yield语句,会弹出值,并暂停函数执行
-
再次调用next函数,和上一条一样的处理过程冬创
-
没有多余的yield语句能被执行,继续调用next函数,会抛出StopIteration异常
-
无限循环
def counter():i = 0while True:i += 1yield idef inc(c):return next(c)c = counter()print(inc(c)) # 1print(inc(c)) # 2print('end')
def counter():i = 0while True:i += 1yield idef inc():c = counter()return next(c)print(inc()) # 1
print(inc()) # 1
print(inc()) # 1
生成器应用
- 计算器
def inc():def counter():i = 0while True:i += 1yield ic = counter()return lambda: next(c)foo = incprint(foo()) # <function inc.<locals>.<lambda> at 0x10114c040>print(foo()) # <function inc.<locals>.<lambda> at 0x1011084a0>
- lambda表达式是匿名函数
- return返回的是一个匿名函数
- 等价于下面的代码
def inc():def counter():i = 0while True:i += 1yield ic = counter()def _inc():return next(c)return _incfoo = inc()print(foo()) # 1print(foo()) # 2 print(foo()) # 3
- 处理递归问题
def fib():x = 0y = 1while True:yield yx, y = y, x + yfoo = fib()print('-'*30)
for _ in range(5):print(next(foo))print('*'*30)for _ in range(100):next(foo)print('~'*30)print(next(foo))
输出:
------------------------------
1
1
2
3
5
******************************
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6356306993006846248183
- 等价于下面的代码
pre = 0cur = 1 # Nolprint(pre, cur, end=' ')# récursiondef fib1(n, pre=0, cur=1):pre, cur = cur, pre + curprint(cur, end=' ')if n == 2:returnfib1(n - 1, pre, cur)
生成器应用
- 协程coroutine
生成器的高级用法
比进程、线程轻量级
是在用户空间调度函数的一种实现市职业学院
Python3 asyncio就是协程实现,已经加入到标准库
Python3.5 使用async、await关键字直接原生支持协程
协程调度器实现思路
有2个生成器A、B
next(A)后,A执行到了yield语句暂停,然后去执行next(B),B执行到yield语句也暂停,然后再次调用next(A),再调用next(B)在,周而复始,就实现了调度的效果
可以引入调度的策略来实现切换的方式
协程是一种非抢占式调度
yield from
- 举例
def inc():for x in range(1000):yield xfoo = inc()print(next(foo)) # 0print(next(foo)) # 1print(next(foo)) # 2
- 等价于下面的代码
def inc():yield from range(1000)foo = inc()print(next(foo)) # 0 print(next(foo)) # 1 print(next(foo)) # 2
-
yield from是Python 3.3出现的新的语法
-
yield from iterable 是 for item in iterable: yield item 形式的语法糖口从可迭代对象中一个个拿元素
def counter(n): # 生成器,迭代器for x in range(n):yield xdef inc(n):yield from counter(n)foo = inc(10)print(next(foo)) # 0 print(next(foo)) # 1
把一个子典扁平化
源字典 {‘a’:{‘b’:1,‘c’:2},‘d’:{‘e’:3,f:{‘g’:4}}}
目标字典:{‘a.c’:2,‘d.e’:3,‘d.f.g’:4,‘a.b’:1}
source = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}
target = {}# recursiondef flatmap(src, prefix=''):for k, v in src.items():if isinstance(v, (list, tuple, set, dict)):flatmap(v, prefix=prefix + k + '.') # 递归调用else:target[prefix + k] = vflatmap(source)print(target)# 输出
# {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}
一般这种函数都会生成一个新的字典,因此改造一下 dest字典可以由内部创建,也可以有外部提供
source = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}# recursiondef flatmap(src, dest=None, prefix=''):if dest == None:dest = {}for k, v in src.items():if isinstance(v, (list, tuple, set, dict)):flatmap(v, dest, prefix=prefix + k + '.') # 递归调用else:dest[prefix + k] = vreturn destprint(flatmap(source))# 输出:
# {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}
能否不暴露给外界内部的字典呢?
能否函数就提供一个参数源字典,返回一个新的扁平化字典呢?
递归的时候要把目标字典的引用传递多层,怎么处理?
source = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}# recursiondef flatmap(src):def _flatmap(src, dest=None, prefix=''):for k, v in src.items():key = prefix + kif isinstance(v, (list, tuple, set, dict)):_flatmap(v, dest, key + '.') # 递归调用else:dest[key] = vdest = {}_flatmap(src, dest)return destprint(flatmap(source))# 输出:
# {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}
实现Base64编码
要求自己实现算法,不用库
将输入每3个字节断开,拿出一个3个字节,每6个bit断开成4段。
2**6=64,因此有了base64的编码表。
每一段当做一个8bit看它的值,这个值就是Base64编码表的索引值,找到对应字符。
再取3个字节,同样处理,直到最后。
举例:
abc对应的ASCII码为:0×61 0×62 0×63
01100001 01100010 01100011 # abc
011000 010110 001001 100011
00011000 00010110 00001001 00100011 #每6位补齐为8位
24 22 9 35
末尾的处理?
1、正好3个字节,处理方式同上
2、剩1个字节或2个字节,用0补满3个字节。
3、补0的字节用=表示
# 自己实现对一段字符串进行base64编码
# 自己实现对一段字符串进行base64编码alphabet = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkImnopqrstuvwxyz0123456789+/"teststr = "abcd"teststr = "ManMa"def base64(src):ret = bytearray()length = len(src)# r记录补O的个数r = 0for offset in range(0, length, 3):if offset + 3 <= length:triple = src[offset:offset + 3]else:triple = src[offset:]r = 3 - len(triple)triple = triple + '\x00' * r # 补几个0# print(triple, r)# 将3个字节看成一个整体转成字节bytes,大端模式# abc => 0x616263b = int.from_bytes(triple.encode(), 'big') # /#‘littleprint(hex(b))# 01100001 01100010 01100011 # abc# 011000 010110 001001 100011 # 每6位断开for i in range(18, -1, -6):if i == 18:index = b >> ielse:index = b >> i & 0x3F # 0b0011 1111ret.append(alphabet[index]) # 得到base64编码的列表# 策略是不管是不是补零,都填满,只有最后一次可能出现补零的# 在最后替换掉就是了,代码清晰,而且替换至多2次# 在上一个循环中判断r!=Q,效率可能会高些for i in range(1, r + 1): # 1到r,补几个e替换几个=ret[-i] = 0x3Dreturn retprint(base64(teststr)) # bytearray(b'TWFuTWE=')# base64实现import base64print(base64.b64encode(teststr.encode())) # b'TWFuTWE='
求2个字符串的最长公共子串
思考:
s1 = ‘abcdefg‘
s2 = ‘defabcd‘
方法一:矩阵算法
让s2的每一个元素,去分别和81的每一个元素比较,相同就是1,不同就是0,有下面的矩阵
上面都是s1的索引。
看与斜对角线平行的线,这个线是穿过1的,那么最长的就是最长子串。
print(s1[3:3+3])
print(s1[0:0+4]) 最长
矩阵求法还需要一个字符扫描最长子串的过程,扫描的过程就是len(S1) len(S2)次,O(n m)。
有办法一遍循环就找出最长的子串吗?
0001000第一行,索引为3,0。
第二行的时候如果4,1是1,就判断3,0是否为1,为1就把3,0加1。
第二行的时候如果5,2是1,就判断4,1是否为1,是1就加1,再就判断3,0是否为1,为1就把3,叻1。
上面的方法是个递归问题,不好。
最后在矩阵中找到最大的元素,从它开始就能写出最长的子串了。
但是这个不好算,因为是逆推的,改为顺推。
顺推的意思,就是如果找到一个就看前一个的数字是几,然后在它的基础上加1。
s1 = 'abcdefg's2 = 'defabcd's2 = 'defabcdoabcdeftw's3 = '1234a's4 = "5678"s5 = 'abcdd'def findit(str1, str2):matrix = []# 从x轴或者y轴取都可以,选择x轴,xmax和xindexxmax = 0xindex = 0for i, x in enumerate(str2):matrix.append([])for j, y in enumerate(str1):if x != y: # 若两个字符不相等,matrix[i].append(0)else:if i == 0 or j == 0: # 两个字符相等,有字符在边上的matrix[i].append(1)else: # 不在边上matrix[i].append(matrix[i - 1][j - 1] + 1)if matrix[i][j] > xmax: # 判断当前加入的值和记录的最大值比较xmax = matrix[i][j] # 记录最大值,用于下次比较xindex = j # 记录当前值的x轴偏移量,和str2[xindex+1-xmax:xindex+1]匹配xindex += 1 # 只是为了计算的需要才+1,和str1[xindex - xmax:xindex]匹配# return str1[xindex+1-xmax:xindex+1]return str1[xindex - xmax: xindex]print(findit(s1, s2)) # abcdef
print(findit(s1, s3)) # a
print(findit(s1, s4)) # 空串
print(findit(s1, s5)) # abcd
s1 = ' abcdefg 's5 = '304abcdd'print(findit(s1, s5)) # abcd
方法二:
可不可以这样思考?
字符串都是连续的字符,所以才有了下面的思路。
思路一:
第—轮
从s1中依次取1个字符,在s2查找,看是否能够找到子串。
如果没有一个字符在$2中找到,说明就没有公共子串,直接退出。如果找到了至少一个公共子串,则很有可能还有更长的公共子串,可以进入下一轮。
第二轮
然后从s1中取连续的2个字符,在s2中查找,看看能否找到公共的子串。如果没找到,说明最大公共子串就是上一轮的随便的哪一个就行了。如果找到至少一个,则说明公共子串可能还可以再长一些。可以进入下一轮。
改进,其实只要找到第一轮的公共子串的索引,最长公共子串也是从它开始的,所以以后的轮次都从这些索引位置开始,可以減少比较的次数。
思路二:
既然是求最大子串,我先看s1全长作为子串。
在s2中搜索,是否返回正常的index,正常就找到了最长子串。
没有找到,把s1按照length-1取多个子串。
在$2中搜索,是否能返回正常的index。
注意:
不要一次把s1的所有子串生成,用不了,也不要从最短开始,因为题目要最长的。
但是也要注意,万一他们的公共子串就只有一个字符,或者很少字符的,思路一就会占优势。
s1 = 'abcdefg's2 = 'defabcdoabcdeftw's3 = '1234a'def findit(str1, str2):count = 0 # 看看效率,计数length = len(str1)for sublen in range(length, 0, -1):for start in range(0, length - sublen + 1):substr = str1[start:start + sublen]count += 1if str2.find(substr) > -1: # foundprint("count={}, substrlen={}".format(count, sublen))return substrprint(findit(s1, s2))print('*'*30)
print(findit(s1, s3))
print('~'*30)
输出:
count=2, substrlen=6
abcdef
******************************
count=22, substrlen=1
a
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~