一、函数基础
函数就是将一些语句组合在一起的组件,从而让他们能够不止一次的在程序中运行。
- 定义函数
def name(arg1, arg2, ......argN):
statements
def的头部定义了被赋值函数对象的函数名,并在圆括号中包含了0个或者以上的参数(有时候称为形式参数,简称形参)。在函数调用的时候,括号中传入的对象将赋值给头部的形式参数。函数体通常包含一条return语句:
def name(arg1,arg2,......,argN):
...
return value
return 语句可以出现在函数体的任何地方,当return 被调用的时候,它将结束函数的调用并将结果返回至函数调用处。如果没有return语句,那么函数将在控制流执行完函数时结束,并自动返回了None对象。
python的def语句是一条真正可执行的语句:当它运行的时候会创建一个新的函数对象并将其赋值给一个变量名。因为函数是在运行时才定义的,所以函数名并没有什么特别之处。关键在于函数名所引用的那个对象:
def func():
print("python")
othername = func #这里,函数被赋值给一个不同的变量名,并通过新的变量名进行调用。
othername()
第一个示例:定义和调用
函数主要包括两个方面:定义(def创建了一个函数)以及调用(表达式告诉python去运行函数体)
定义函数:
def times(x,y):
return x * y
当python运行到这里并执行def语句后,它将创建一个新的函数对象来封装这个函数的代码并将这个对象赋值给变量名tiems。
调用函数:
value = times(2,4)
print(value)
# 8
value = times('Ni',4)
print(value)
# 'NiNiNiNi'
同样的函数,在一个实例下执行的是乘法,在另一实例下执行的却是重复。python把对某一对象在某种语法下的合理性交给那个对象自身来判断,这种行为就被称为多态。
二、函数——作用域
在源代码中变量名被赋值的位置决定了这个变量名能被访问到的范围,就是作用域。
- 在def内赋值的变量名只能够在def内的代码使用。不能在函数的外部引用该变量名。
- 在def内赋值的变量与def外赋值的变量名并不冲突,即使是相同的变量名。在def外被赋值的变量民X和在def内赋值的变量名X是两个完全不同的变量。
- 在任何情况下,一个变量的作用域总是由它在代码中被赋值的位置决定的,而与函数调用无关。
变量可以在3个不同的地方赋值,分别对应三种不同的作用域:
- 如果一个变量在def内赋值,它对该函数而言是局部的
- 如果一个变量在一个外层的def中赋值,对于内层函数来说,它是非局部的。
- 如果一个变量在所有的def外赋值,它对整个文件来说是全局的。
例如:
X = 99 #创建一个名为X的全局变量
def func():
X = 88 #创建一个名为X的局部变量
注意: 要注意原位置改变对象并不会把变量划分为局部变量,实际上只有对变量名赋值才可以。例如,如果变量名L在模块的顶层被赋值为一个列表,在一个函数内类似L.append(X),这样的语句并不会将L划分为局部变量,而L = X却可以。
- 变量名解析:LEGB规则
当你在函数中使用未限定的变量名时,python将查找4个作用域并在第一次找到该变量名的地方停下来:首先是局部作用域(L),其次是向外一层的def或者lambda的局部作用域(E),之后是全局作用域(G),最后是内置作用域(B)。如果没有找到,就会报错。
作用域实例:
X = 99
def func(Y):
Z = X + Y
return Z
func(1)
全局变量名:X,func
因为X是在模块文件顶层赋值的,所以它是全局变量;它能够在函数内部作为一个简单的未限定变量进行引用而不需要特意声明为全局变量。def语句在这个模块文件顶层将一个函数对象赋值给了变量名func,因此func也是全局变量。
局部变量名:Y, Z
Y和Z都是在函数定义内部进行赋值的:Z是通过=语句赋值的,而Y是由于参数总是通过赋值来进行传递的。
- global 语句
global语句告诉python函数计划生成一个或者多个全局变量名——存在于整个模块内部作用域的变量名。global语句包含了关键字global,其后跟着一个或者多个由逗号分开的变量名。当在函数主体被赋值或者引用的时,所列出的变量名将被映射到整个模块的作用域内。
- 全局变量是在外层模块文件的顶层被赋值的变量名。
- 全局变量如果是在函数内被赋值的话,必须经过global声明。
- 全局变量名在函数内部不经过声明也可以被引用。
x = 88
def func():
global x
x = 99
func()
print(x)
- 作用域和嵌套函数
嵌套的函数作用域查找规则
一个 引用(X) 首先在局部(函数内)作用域查找变量名X;之后会在代码句法上的外层函数的局部作用域,从内到外查找;之后查找当前的全局作用域(模块文件),最后查找内置作用域(模块builtins)。然而global声明会直接从全局(模块文件)作用域进行查找。在默认情况下,一个 赋值(X=value) 会创建或改变当前作用域的变量名X。如果X在函数内部声明为全局变量,它会创建或者改变整个模块的作用域中变量名X。
X = 99
def f1():
X = 88
def f2():
print(X)
f2()
f1()
代码解析:
def 是一个间的可执行语句,可以出现在任意其他语句能够出现的地方,包括嵌套在另一个def中。这里,嵌套的def在函数f1调用时运行;这个def将生成一个函数,将其赋值给变量f2,f2是f1的局部作用域内的一个局部变量。可以说,f2是一个临时函数,仅在f1内部执行的过程中存在。
f2内部当打印变量X时,X引用了存在与外层函数f1的局部作用域内的X的值。因为函数能够访问在物理上处于外层def声明的变量名,通过LEGB查找规则,f2内的X自动映射为f1中的x。
这种嵌套作用域查找即便在外层函数已经返回后也是有效的。例如,下面的代码定义一个函数,该函数创建并返回另外一个函数:
def f1():
X = 88
def f2():
print(X)
return f2
action = f1()
action()
# 88
当调用action()时,f1已经不处于激活状态,但f2记住了f1嵌套作用域中的X。
- 工厂函数:闭包
一个函数用来创建并返回另外一个函数,而被创建的函数彼此之间可能带有不同的信息。
def maker(N):
def action(X):
return x ** N
return action
f = maker(2)
f(3)
# 9
f(4)
# 16
这里虽然在调用action时maker已经返回并退出,但是内嵌的函数记住了整数2,即maker内部变量N的值,实际上在外层嵌套作用域内的N被作为执行的状态信息被保留下来,并附加到生成的action函数上,这也是当它被调用时返回其平方的原因。
f = maker(2)
g = maker(3)
f(4)
# 16
g(4)
# 64
因为每次对这样的工厂函数调用,都将得到属于自己的状态信息,所以名称g的函数记住了3,而名称f函数记住了2。
注意: 在没有嵌套作用域的岁月(python2.2之前的版本),需要借助默认参数从上层作用域传递值给嵌套函数。
def f1():
X = 88
def f2(X=X):
print(X)
f2()
f1()
def func():
x = 4
action = (lambda n, x=x: x ** n)
return action
- 循环变量可能需要默认参数值,而不是作用域
问题: 如果在函数中定义的def或者lambda嵌套在一个循环中,而这个嵌套函数又引用了一个外层作用于的变量,该变量被循环所改变,那么所有在这个循环中产生的函数会有相同的值——也就是最后一次循环完成时被引用的变量的值。
>>> def makeActions():
... acts = []
... for i in range(5):
... acts.append(lambda x: i ** x)
... return acts
...
>>> acts = makeActions()
>>> acts[0](2)
16
>>> acts[1](2)
16
>>> acts[2](2)
16
>>> acts[3](2)
16
>>> acts[4](2)
16
原因: 因为外层作用域中的变量在嵌套的函数调用时才进行查找,所以它们实际记住的是同样的值,也就是最后一次循环迭代中循环变量的值。即每个函数的结果都是i=4,就是4的平方16。
上述是唯一一种还需要动态默认值参数来显式保持外层作用域中值的情况,而不是简单的外层作用域的引用。
>>> def makeActions():
... acts = []
... for i in range(5):
... acts.append(lambda x,i=i: i ** x)
... return acts
...
>>> acts = makeActions()
>>> acts[0](2)
0
>>> acts[1](2)
1
>>> acts[2](2)
4
>>> acts[3](2)
9
>>> acts[4](2)
16
为了让这类代码工作,必须使用默认参数传入当前作用域中的值。因为默认参数的求值实在嵌套函数创建的时候就发生的(而不是函数调用时),所以每个函数记住了属于自己的变量i的值。
- nonlocal语句
在内嵌的def中用nonlocal声明变量,就可以对外层的函数中的名称进行读取和写入访问。和global的不同之处在于nonlocal作用于外层函数的作用域的一个名称,而不是多有def之外的全局作用域;而且在声明nonlocal名称的时候,它必须已经存在于该外层函数的作用域中,而global不需要。
>>> def tester(start):
... state = start
... def nested(label):
... print(label,state)
... return nested
...
>>> F = tester(0)
>>> F('spam')
spam 0
默认不允许修改def作用域的名称
>>> def tester(start):
... state = start
... def nested(label):
... print(label,start)
... state += 1
... return nested
...
>>> F = tester(0)
>>> F('spam')
spam 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in nested
UnboundLocalError: local variable 'state' referenced before assignment
使用nonlocal进行修改
>>> def tester(start):
... state = start
... def nested(label):
... nonlocal state
... print(label,state)
... state += 1
... return nested
>>> F = tester(0)
>>> F('spam')
spam 0
>>> F('ham')
ham 1
>>> F('eggs')
eggs 2
多次调用tester工厂函数,以便在内存中获取其状态多个副本。外层作用域的state对象本质上被附加到返回的nested函数对象中,每次调用都产生一个新的state对象,从而不会影响到其他的。
>>> G = teser(30)
>>> G('spam')
spam 30
>>> G('ham')
ham 31
>>> G('eggs')
eggs 32
注意: nonlocal仅是使用于python3
三、函数——参数
参数也就是对象作为输入传递给函数的方式。参数传递要点:
- 参数的传递是通过自动将对象赋值给局部变量名来实现的,所有参数实际上都是通过指针传入的,作为参数传递时对象从来不会自动复制。
- 在函数内部赋值参数名不会影响调用者。在函数运行时,函数头部的参数名是一个新的局部变量名,这个变量名实在函数的局部作用域内的,函数参数名和调用者作用域中的变量名是没有关联的。
- 改变函数的可变对象参数的值也许会对调用者有影响。因为是直接把传入的对象赋值给参数,函数能够原位置改变传入的可变对象,因此结果可能会影响调用者。
python的参数传递模式:它只是把对象赋值给变量名,并且无论可变对象或不可变对象都是如此。
- 不可变参数本质上传入了“值”。像整数和字符串这样的对象是通过对象引用而不是复制传入的,但是因为你无论如何都不可能在原位置修改不可变对象,最终结果就像是创建了一份副本。
- 可变对象本质上传入了“引用”。类似于列表和字典这样的对象也是通过对象引用传入的,可变对象能够在函数内部做原位置的改变。
- 参数与共享引用
>>> def changer(a,b):
... a = 2
... b[0] = 'spam'
...
>>> X = 1
>>> L = [1,2]
>>> changer(X,L)
>>> X,L
(1, ['spam', 2])
- 因为a是在函数作用域内的局部变量,第一个赋值对函数调用者没有影响,它仅把局部变量a修改为引用一个完全不同的对象,而并没有改变调用者作用域中的名称X的绑定。
- 参数b也是局部变量名,但是它被传入了一个可变对象。因为第二个赋值是一个原位置发生的对象改变,对函数中b[0]赋值的结果会在函数返回后影响L的值。
引用:参数。因为参数是通过赋值传递的,函数中的参数名可以在调用时与调用者作用域中的变量实现共享对象。因此函数中原位置修改可变对象参数能够影响调用者。这里函数中的a和b在函数一开始调用时与变量X和L引用了相同的对象,通过变量b对列表的修改,在函数调用返回后使L发生了改变。
可以通过一定技巧避免修改可变参数:1、在调用时复制列表 2、在函数内部进行复制 3、可变对象转换为不可变对象。
L = [1,2]
changer(X,L[:])
def changer(a,b):
b = b[:]
a = 2
b[0] = 'spam'
L = [1,2]
changer(X,tuple(L))
- 特殊的参数匹配模式
参数在python中总是通过赋值传入的。传入的对象被赋值给了在def头部的变量名。默认情况下,参数按照从左到右的位置进行匹配,而且你必须精确地传入和函数头部参数名一样多的参数。也可以通过形式参数名、提供默认的参数值以及额外参数使用容器三种方式来指定匹配。
位置次序:从左至右进行匹配
>>> def func(a,b,c):
... print(a,b,c)
...
>>> func(1,2,3)
1 2 3
关键字参数:通过参数名进行匹配
关键字参数允许我们通过名称匹配,而不是基于位置
>>> func(c=3,b=2,a=1)
1 2 3
在调用的时候,c=3意味着将3传递给名为c的参数。
默认值参数:为没有传入值的可选参数指定参数的值
默认值参数允许我们让特定的参数变为可选,如果没有传入值的话,在函数运行前参数就会被赋予默认值
>>> def func(a,b=2,c=3):
... print(a,b,c)
>>> func(1)
1 2 3
>>> func(1,c=4)
1 2 4
可变长参数收集:收集任意多的基于位置或者关键字的参数
函数能够用一个星号'*'或者两个星号"**"开头的特殊参数,来收集任意多的额外参数
函数定义中:收集参数
其中第一种方法,是在函数定义中把不能匹配的基于位置的参数收集到一个元组中:
>>> def func(*args):print(args)
当函数调用时,python将所有基于位置的参数收集到一个新的元组中,并将这个元组赋值给变量args
>>> func()
()
>>> func(1)
(1,)
>>> func(1,2,3,4)
(1, 2, 3, 4)
"**"的特性与之类似,但是它只对关键字参数有效。它将关键字参数收集到一个新的字典中,这个字典之后能够用一般的字典工具处理。
>>> def func(**kwargs):print(kwargs)
...
>>> func(a=1,b=2)
{'a': 1, 'b': 2}
name=value形式在调用时表示关键字参数,在函数定义时表示默认值参数。
可变长参数解包:传入任意多的基于位置或关键字的参数
调用者也能使用*语法去将参数集合解包成单个参数。在函数头部"*"意味着收集任意多的参数,而在调用者中意味着解包任意多的参数。
函数调用中:解包参数
def func(a,b,c,d):print(a,b,c,d)
在函数调用时我们给函数传递四个参数的元组,python会自动解包
>>> args = (1,2,3,4)
>>> func(*args)
1 2 3 4
类似地,在函数调用时,"**"会以键值对的形式把一个字典解包为独立的关键字参数:
>>> args = {'a':1, 'b':2,'c':3, 'd':4}
>>> func(**args)
1 2 3 4
一个星号代表基于位置的参数,两个星号代表关键字参数
keyword-only参数:必须按照名称传递的参数。即必须只按照关键字传入并且永远不会被基于位置参数来填充的参数。
>>> def kwonly(a, *b, c):
... print(a,b,c)
...
>>> kwonly(1,2,c=3)
1 (2,) 3
>>> kwonly(a=1,c=3)
1 () 3
>>> kwonly(1,2,3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwonly() missing 1 required keyword-only argument: 'c'
"*"后面的所有参数都作为关键字参数传入。
注意: 匹配规则
- 在函数调用时,参数必须按此顺序出现:所有基于位置的参数(value),之后是所有的关键参数(name=value)和*iterable形式的组合,之后是**dict形式。
- 在函数定义时,参数必须按照此顺序出现:所有的一般参数(name),之后是默认值参数(name=value),之后是*name(或者py3中的*)形式,之后是所有的name或者name=value的keyword-only参数(py3),之后是**name形式。
内部匹配顺序:
(1).通过位置匹配无关键字参数。
(2).通过匹配名称分配关键字参数。
(3).将剩下的非关键字参数分配到name元组中。
(4).将剩下的关键字参数分配到*name字典中。
(5).把默认值分配给头部未得到匹配的参数。
函数———高级话题
函数相关的话题:递归函数、函数属性和注解、lambda表达式,函数式编程工具例如map和filter
- 递归函数
对数字列表求和
写法一:
>>> def mysum(L):
... if not L:
... return 0
... else:
... return L[0]+mysum(L[1:])
...
>>> mysum([1,2,3,4,5])
15
写法二:
>>> def mysum(L):
... return 0 if not L else L[0] + mysum(L[1:])
...
>>> mysum([1,2,3])
6
在每一层,这个函数都递归地调用自己来计算列表的剩余值的和,这个和随后加到前面的一项中。
循环语句
>>> L = [1,2,3,4,5]
>>> sum = 0
>>> while L:
... sum += L[0]
... L = L[1:]
...
>>> sum
15
加大难度:计算一个嵌套的子列表结构中所有数字的总和。
[1,[2,[3,4],5],6,[7,8]]
循环语句在这里不容易实现,但递归就可以简单的解决。
>>> def sumtree(L):
... tot = 0
... for x in L:
... if not isinstance(x, list):
... tot += x
... else:
... tot += sumtree(x)
... return tot
...
>>> print(sumtree(L))
36
用先进先出的队列来实现:
def sumtree(L):
tot = 0
items = list(L)
while items:
front = items.pop(0)
if not isinstance(front,list):
tot += front
else:
items.extend(front)
return tot
用后进先出的栈实现:
def sumtree(L):
tot = 0
items = list(L)
while items:
front = items.pop(0)
if not isinstance(front,list):
tot += front
else:
print(front)
items[:0]=front
return tot
- 函数对象:属性和注解
python函数是对象,函数对象可以赋值给其他的名称、传递给其他函数、嵌入到数据结构中。def语句中的名称并没有特殊的含义:它只是当前作用域中的变量赋值,就好像它出现在 = 符号的左边一样。在def运行之后,函数名仅仅是一个对象的引用。
间接函数调用
>>> def echo(message):
... print(message)
...
>>> x = echo
>>> x("echo")
echo
函数属性
>>> def func():
... pass
...
>>> func.count = 0
>>> func.count += 1
>>> func.count
1
python3.x的函数注解
注解:即给函数对象附加注解信息,即与函数的参数和结果相关的任意用户定义的数据。从语法上将,注解编写在def头部行,作为与参数和返回值相关的任意表达式。对于参数,它们出现在紧随参数名之后的冒号之后;对于返回值,它们编写于紧跟在参数列表之后的一个 -> 之后。
def add(x:int, y:int) -> int:
return x + y
注解和默认值可以同时存在。注解(以及:字符)出现在默认值(以及=字符)之前。
>>> def add(x:int = 4, y:int = 5) -> int:
... return x + y
...
>>> add(3,6)
9
- 匿名函数:lambda
与def一样,这个表达式创建了一个之后能够调用的函数,但是它返回该函数本身而不是将其赋值给一个变量名。这也是lambda被称为匿名函数的原因
- lambda表达式基础
lambda的一般形式是关键字lambda后面跟上一个或者多个参数(与def头部内用括号括起来的参数列表极其相似),之后是一个冒号,再之后是一个表达式。
lambda argument1,argument2,...argumentN:expression using argument
lambda x,y,z:x+y+z
作为一个表达式,lambda返回一个值(一个新的函数),可以选择性的被赋值给一个变量名。而def语句总是需要在头部将新的函数赋值给一个名称,而不是将这个函数作为结果返回。
>>>f = lambda x, y, z:x + y + z
>>>f(1,2,3)
6
- 函数式编程工具
函数式编程工具包括在一个可迭代对象的各项上调用的函数工具(map),使用一个测试函数来过滤项的工具(filter),还有函数作用在成对的项上来运行结果的工具(reduce),三者返回一个可迭代对象,因此需要一个list调用来显式它的所有结果。
- 在可迭代对象上映射函数:map。程序对列表和其他序列常常要做的一件事,就是对每一个元素都进行操作并把结果收集起来:map函数将被传入的函数作用到一个可迭代对象的每一个元素上,并且返回包含了所有这些函数调用结果的一个列表。
>>> def inc(x):
... return x + 10
...
>>> counters = [1,2,3,4]
>>> list(map(inc,counters)) # map对列表中的每个元素都调用了inc函数,并将所有的返回值收集到一个新的列表中。
[11, 12, 13, 14]
更简单的写法:
>>> list(map(lambda x:x + 3,counters))
[4, 5, 6, 7]
给定多个序列参数,map会按照顺序,并行的从各个序列中逐项取出一组又一组参数,然后传入函数中。
>>> list(map(pow,[1,2,3],[2,3,4]))
[1, 8, 81]
改用列表推导式实现
>>> [inc(x) for x in [1,2,3,4]]
[11, 12, 13, 14]
- 选择可迭代对象中的元素:"filter"
filter实现了基于一个测试函数选择可迭代对象的元素,以及向"元素对"应用函数的功能。
>>> list(filter((lambda x: x > 0),range(-5,5))) # filter挑出一个序列中大于零的元素。
[1, 2, 3, 4]
当调用filter的时候,对于序列或可迭代对象中的元素,如果函数对该元素返回了True值,这个元素就会被加入结果列表中。
利用列表推导语法来模拟
>>> [x for x in range(-5,5) if x > 0]
[1, 2, 3, 4]
- 合并可迭代对象中的元素:reduce
>>> from functools import reduce
>>> reduce((lambda x, y: x + y),[1,2,3,4])
10
>>> reduce((lambda x, y: x * y),[1,2,3,4])
24
每一步,reduce将当前的和或者乘积以及列表中的下一个元素传给列出的lambda函数
>>> L = [1,2,3,4]
>>> res = L[0]
>>> for x in L[1:]:
... res = res + x
...
>>> res
10
- 生成器函数与表达式
- 生成器函数:使用常规的def语句进行编写,但是使用yield语句一次返回一个结果,在每次结果产生之间挂起和恢复它们的状态。
- 生成器表达式: 类似于列表推导,但是,它是包括在一个圆括号内,并且返回按需产生结果的一个对象,而不是创建一个结果列表。
迭代协议: 所有带有next方法的对象会前进到下一个结果,而当到达一系列结果的末尾时,_next_会引发StopIteration异常,这种对象在python种也被称为迭代器。
迭代工具: for,推导,map for循环在开始时,会首先把可迭代对象传入内置函数iter,并由此拿到一个迭代器;而iter函数调用返回的迭代器对象有着所需的next方法
可迭代对象: 迭代的被调对象,其iter方法被iter函数所调用。
迭代器对象: 可迭代对象的返回结果,在迭代过程中实际提供值的对象。它的next方法被next运行,并在结束时触发StopIteration异常。
>>> L = [1,2,3]
>>> I = iter(L)
>>> while True:
... try:
... X = next(I)
... except StopIteration:
... break
... print(X ** 2, end=' ')
...
1 4 9
生成器函数和常规函数一样,事实上也是用常规的def语句编写的。然而,当创建时,因为函数包含一条yield语句,它们被特殊地编译成一个支持迭代协议的对象。并且在调用的时候它们不会返回一个结果,它们返回一个可以出现在任何迭代上下文(迭代工具)中的结果生成器(生成器对象)。该对象支持用一个自动创建的名为next的方法接口,来开始或者恢复执行。
>>> def gensquares(N):
... for i in range(N):
... yield i ** 2
...
>>> for i in gensquares(5):
... print(i, end=":")
...
0:1:4:9:16:
生成器表达式
>>> list(x ** 2 for x in range(4))
[0, 1, 4, 9]
通过手动调用来观察
>>> G = (x**2 for x in range(4))
>>> iter(G)
<generator object <genexpr> at 0x000001F5CF7C9B88>
>>> next(G)
0
>>> next(G)
1
>>> next(G)
4
>>> next(G)
9
>>> next(G)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
实例:生成乱序序列
利用分片和拼接将一个序列重新排序,在每次循环中中将第一个元素移到序列的末尾;分片使用“+”操作任意的序列类型,而不是用下标索引元素:
>>> L,S = [1,2,3],"spam"
>>> for i in range(len(S)):
... S = S[1:] + S[:1]
... print(S,end="")
...
pams amsp mspa spam
>>> for i in range(len(L)):
... L = L[1:]+ L[:1]
... print(L,end=' ' )
...
[2, 3, 1] [3, 1, 2] [1, 2, 3]
替代方案,将前面的部分移到末尾
>>> for i in range(len(S)):
... X = S[i:] + S[:i]
... print(X,end=" ")
...
spam pams amsp mspa
简单函数
上面的代码只能操作固定名称的变量,为了一般化,可以改写成一个简单函数。
>>> def scramble(seq):
... res = []
... for i in range(len(seq)):
... res.append(seq[i:]+seq[:i])
... return res
...
>>> scramble("spam")
['spam', 'pams', 'amsp', 'mspa']
>>> def scramble(seq):
... return [seq[i:] + seq[:i] for i in range(len(seq))]
...
>>> scramble("spam")
['spam', 'pams', 'amsp', 'mspa']
生成器函数
上面的简单方法虽然奏效,但是必须在内存中立即创建一个完整的结果列表,而且需要调用者等待到整个列表被完全建好。
>>> def scramble(seq):
... for i in range(len(seq)):
... seq = seq[1:] + seq[:1]
... yield seq
...
>>> list(scramble("spam"))
['pams', 'amsp', 'mspa', 'spam']
生成器表达式
生成器表达式(圆括号中的推导表达式,而不是方括号中的)也能按需生成值并保存它们的局部状态。
>>> G = (S[i:] + S[:i] for i in range(len(S)))
>>> list(G)
['spam', 'pams', 'amsp', 'mspa']
你可以把它包在一个简单函数中,让这个简单函数接收一个参数并返回一个使用该参数的生成器:
>>> F = lambda seq: (seq[i:] + seq[:i] for i in range(len(seq)))
>>> F(S)
<generator object <lambda>.<locals>.<genexpr> at 0x000001F5CF7C9DE0>
>>> list(F(S))
['spam', 'pams', 'amsp', 'mspa']