Chapter5 函数
- 举例
语句 | 例子 |
---|---|
calls | myfunc(“spam”,“eggs”,meat = ham) |
def | def adder(a,b=1,*c) |
return | return a+b+c[0] |
global | def changer(): global x ; x = ‘new’ |
nonlocal | def changer(): nonlocal x; x = ‘new’ |
yield | def squares(x): for i in range(x): yield i **2 |
lambda | Funcs = [lambda x: x** 2,lambda x: x*3] |
- 函数只是一个对象,在运行时,创建一个新的函数名并将其赋值给一个变量名。
if test :
def func():
...
else:
def func():
...
func() #python在程序运行前不需要全部定义
othername() = func() #可以调用
func.attr = value #可以将任意的属性附加到记录信息
- 作用域
对于一个“def”语句:
- 变量名引用分为三个作用域进行查找 LEGB :首先是本地(Local function)–>函数内(Enclosing function locals)–>全局(Global module)–>内置(Built-int python)
- 默认情况下,变量名赋值会创建或者改变本地变量
- 全局声明和非本地声明将赋值的变量名映射到模块文件内部的作用域
- 嵌套作用域和lamba
lamba是一个表达式,将会生成后面调用的一个新的函数,与def语句很相似。由于它是一个表达式,尽管可以使用在def中不能使用的地方,但是lambda表达式引入了新的本地作用域。
5.最小化文件修改
#first.py
X = 99
#seconde.py
import first
print(first.X)
first.X = 88
第一个模块文件定义了变量X,这个变量在第二个文件中通过赋值被修改了,一个模块的全局变量一旦被导入就称成为了这个模块对象的一个属性。
这种方法可以跨文件修改变量,但是维护第一个模块时,很难知道会有模块修改X。
最好的解决办法时:在文件中通信最好的办法就是通过调用函数,传递参数,然后得到其返回值。
#first.py
X = 99
def serX(new):
global X
X = new
# second.py
import first
first.setx(88)
当人们看到第一个模块时,会知道这是一个接入点,并且知道这将改变变量x,
Chapter6 参数
参数(argument or parameter)
python通过赋值进行传递的机制与C++的引用参数选项并不完全相同,但是在实际中,它与c语言的参数传递模型相当相似。
- 不可变参数“通过值”进行传递。整数,字符串这样的对象是通过 对象引用而不是拷贝进行传递,但是你无论怎样都不能在原处改变不可变对象,实际的效果就很像创建了一份拷贝。
- 可变对象是通过“指针”进行传递的。例如,列表和字典这样的对象也是通过对象引用进行传递的,这一点与C语言使用指针传递数组很相似:可变对象能够在函数内部进行原处的改变,和c数组很像。
参数和共享引用
例子:
def f(a):
a = 99
b=88
f(b)
print(b)
#88
这里的数字,元组为不可改变对象,因此无影响。
def change(a,b):
a = 2
b[0] = 'spam'
X=1
L=[1,2]
change(X,L)
#X,L -> (1,['spam',2])
这里chang的第二条赋值语句并没有修改b,我们修改的是b当前所引用的对象的一部分。
X=1
a=X
a=2
print(X)
#1
L = [1,2]
b = L
b[0] = 'spam'
print(L)
#['spam',2]
避免可变参数的修改
对于可变参数的原处修改的行为不是一个BUG–他只是参数传递在python中的工作方式,在pyhton中,默认通过引用(也就是指针)进行函数的参数传递,这通常是我们想要的:意味着不需要创建多个拷贝就可以在我们程序中传递很大的对象。
如果不想如此,可以简单的创建一个可变对象的拷贝,
L = [1,2]
change(X, L[:])
def change(a,b)
a = b[:]
这个仅仅是防止这些改变会影响调用者,为了真正意义上防止这些改变,可以将 可变对象 转换为 不可变对象。但是很少使用。
change(X,tuple(L))
特定的参数匹配模型
-
位置:从左到右
匹配顺序为从左到右 -
关键字参数:通过参数名进行匹配
调用者可以定义哪一个函数接收这个值,通过在调用时使用参数的变量名,使用 name=value这种语法 -
默认参数:为没有传入值的参数定义参数值
如果调用时传入的值过于少的话,函数能够为参数定义接受的默认值,再一次使用语法 name=value -
可变参数:收集任意多基于位置或关键字的参数
函数能够使用特定的参数,它们是以字符 * 开头,收集任意多的额外参数(这个特性常常叫做 可变参数) -
可变参数解包: 传递任意多的基于位置或关键字的参数
调用者能够再使用 * 语法去将参数集合打散,分成参数。这个 * 与在函数头部的 * 恰恰相反:在函数头部意味着收集任意多的参数,而在调用者中意味着传递任意多的参数。 -
Keyword-only参数:参数必须按照名称传递
在python3.0中(不包括2.6)函数也可以指定参数,参数必须用带有关键参数的名字(而不是位置)来传递。这样的参数通常用来定义实际参数以外的配置选项。
python参数匹配步骤:
- 通过位置分配非关键字参数
- 通过匹配变量名分配关键字参数
- 其他额外的非关键字参数分配到*name元组中
- 其他额外的关键字参数分配到**name字典中
- 用默认值分配给在头部未得到分配的参数
在这之后,确认每个参数只传入了一个值,否者发生错误。
关键字参数和默认参数的实例
#位置
def f(a,b,c):print(a,b,c)
#关键字
f(c=3,b=2,a=1)
#默认参数
def f(a,b=2,c=3):print(a,b,c)
#关键字和默认参数
def func(spam,eggs,toast=0,ham=0):
print((spam,eggs,toast,ham))
func(1,2) # Output: (1,2,0,0)
func(1,ham=1,eggs=0) # Output: (1,0,0,1)
func(spam=1,eggs=0) # Output: (1,0,0,0)
func(toast=1,eggs=0,spam=3) # Output: (3,0,1,0)
func(1,2,3,4) # Output: (1,2,3,4)
#任意参数的实例
#第一种,*参数 在元组中收集不匹配的位置参数
def f(*args):print(args)
f() #Output: ()
f(1) #Output: (1,)
f(1,2,3,4) #Output: (1,2,3,4)
#任意参数的实例
#第二种,**参数 在字典中收集
def f(**args): print(args)
f() #Output: {}
f(a=1,b=2) #Output: {'a':1,'b':2}
#任意参数的实例
#第三种,函数头部混合一般参数,*参数以及 **参数去实现
def f(a,*pargs,**kargs):print(a,pargs,kargs)
f(1,2,3,x=1,y=2) #Output: 1 (2, 3) {'x': 1, 'y': 2}
解包参数实例
在调用函数的时候能够使用*语法,在这种情况下,它与函数定义的意思相反。它会解包参数的集合,而不是创建参数的集合。例如,我们通过一个元组给一个函数传递四个参数。
def func(a,b,c,d):print(a,b,c,d)
#元组 元组用一个 * 号
args = (1,2)
args +=(3,4)
func(*args) #Output: 1 2 3 4
#字典 字典用两个 * 号
args={'a':1,'b':2,'c':3}
args['d'] = 4
func(**args) #Output: 1 2 3 4
#此外,在调用中能够以非常灵活的方式混合普通的参数,基于位置的参数以及关键字参数
func(*(1,2),**{'d':4,'c':4}) # 1 2 4 4
func(1,*(2,3),**{'d':4}) # 1 2 3 4
func(1,*(2,3),d=4) # 1 2 3 4
min调用实例
假设编写一个函数,这个函数能够计算任意参数集合和任意对象数据类型集合中的最小值。这个函数应该能够接受零个或多个参数,能够使用所有的Python对象类型。
def min1(*args):
res = args[0]
for arg in args[1:]:
if arg < res:
res = arg
return res
def min2(first, *rest):
for arg in rest:
if arg < first:
first = arg
return first
def min3(*args):
tmp = list(args)
tmp.sort()
return tmp[0]
ef minmax(test,*args):
res = args[0]
for arg in args[1:]:
if test(arg, res):
res = arg
return res
def lessthan(x,y): return x < y #See also: lambda
def grtrthan(x,y): return x > y
print(minmax(lessthan,4,2,1,5,6,3)) #Self-test code
print(minmax(grtrthan,4,2,1,5,6,3))
一个更有用的例子:通用set函数
def intersect(*args):
res = []
print(args)
for x in args[0]:
print('x:',x)
for other in args[1:]:
print('other:',other)
if x not in other: pass
else :
res.append(x)
print('res:',res)
return print(res)
def union(*args):
res = []
for seq in args:
for x in seq:
if not x in res:
res.append(x)
return print(res)
s1, s2, s3 = 'SPAMH', 'SCAM', 'SLAMH'
intersect(s1,s2,s3)
union(s1,s2)
Chapter7 函数的高级话题
开始使用函数时,开始面对如何将组件聚合再一起的选择,如何将任务分解成为更有针对性的函数(导致了聚合性),函数如何通行(耦合性)等。
- 耦合性:对于输入使用参数并且对于输出使用 return 语句。
- 耦合性:只有在真正必要的情况下使用 全局变量。
- 耦合性:不要改变 可变类型 的参数。
- 聚合性:每个函数都应该有一个单一的,统一的目标。
- 耦合:避免直接改变在另外一个模块文件中的变量。
递归计算
计算一个嵌套的子列表结构中所有数字的总和:
[1,[2,[3,4],5],6,[7,8]]
def sumtree(L):
tot = 0
for x in L:
#isinstance(obj,class)判断obj是不是class的实例化
#isisntance(1,int)->True
if not isinstance(x,list):
tot += x
else:
tot += sumtree(x)
return tot
L=[1,[2,[3,4],5],6,[7,8]]
print(sumtree(L))
间接函数的调用
由于pyhton函数是对象,因此函数对象可以赋值给其他的名字,传递给其他的函数,嵌入到数据结构中,还可以由一个函数表达式后面的括号中的列表参数调用。
def echo(message):
print(message)
schedule = [(echo,"SPAM!"),(echo,'Ham!')]
for (func,arg) in schedule:
func(arg)
'''
SPAM!
Ham!
'''
匿名函数:lambda
- lambda 是一个表达式,而不是语句
- lambda 的主体是一个单个的表达式,而不是一个代码块
- lambda arguement1, arguement2, arguementN: expression using arguement
f = lambda x,y,z: x+y+z
f(2,3,4)
#Output: 9
#lambda可以使用默认参数
x = (lambda a="fee",b="fie",c="foe":a+b+c)
x("wee")
#OutPut: 'weefiefoe'
#lambda的作用域查找法则:LEGB
def knights():
title = "Sir"
action = (lambda x: title + ' ' + x)
return action
act = knights()
act('robin')
#OutPut:'Sir robin'
为何使用lambda
lambda通常用来编写跳转表(jump table),也就是行为的列表或字典,能够按照需要执行相应的动作,如下段代码所示。
L = [
lambda x: x**2,
lambda x: x**3,
lambda x: x**4
]
for f in L:
print(f(2))
#OutPut: 4 8 16
print(L[0](3))
#OutPut:9
lambda可以用于实现 三元选择
lower = (lambda x,y: x if x<y else y)
lower('bb','aa')
#OutPut: aa
lower('aa','bb')
#OutPut: aa
在序列中映射函数:map
例如,更新一个列表counter的所有数字:
counters = [1,2,3,4]
updated = []
for x in counters:
updated.append(x+10)
updated
#OutPut: [11,12,13,14]
在这个常见的操作中,python实际上提供了一个内置的工具。
map函数会对一个序列对象中的每一个元素应用被传入的函数,并且返回一个包含了所有函数调用结果的一个列表。
counters = [1,2,3,4]
def inc(x): return x+10
list(map(inc,counters))
# [11, 12, 13, 14]
map对一个可迭代对象中的项应用一个内置函数。这里,对列表中的每一个元素应用这个函数。别忘记了,map也是一个可迭代对象,因此,用一个列表调用来迫使它生成所有结果加以显示。
由于map期待传入一个函数,这恰好是lambda 通常出现的地方之一:
list(map((lambda x:x+10),counters))
Chapter8 迭代和解析,第二部分
列表解析和map
pyhton内置函数 ord 函数会返回一个单字符的ASCII整数编码:
ord(‘s’)
115
采用for循环
res = []
for x in 'SAPM':
res.append(ord(x))
采用map
list(map((lambda x:ord(x)),"SPAM"))
list(map(ord,"SPAM"))
采用列表解析式
res = [ord(x) for x in "spam"]
实际上,列表解析还能够更加通用,你可以在一个列表解析中编写任意数量的嵌套for循环,并且每一个都有可选的关联if的测试。
[expression for target1 in iterable1 [if condition1]
for target2 in iterable2 [if condition2]
for target3 in iterable3 [if condition3]...]
[x+y for x in [0,1,2] for y in [100,200,300]]
#OutPut:[100, 200, 300, 101, 201, 301, 102, 202, 302]
列表解析和矩阵
M=[[1,2,3],
[4,5,6],
[7,8,9]]
N=[[2,2,2],
[3,3,3],
[4,4,4]]
#提取某一行的元素
[row[1] for row in M]
#Out: [2, 5, 8]
#提取对角线
[M[i][i] for i in range(len(M))]
#矩阵每个元素相乘
[M[row][col] * N[row][col] for row in range(3) for col in range(3)]
# Out: [2, 4, 6, 12, 15, 18, 28, 32, 36]
#两层循环,外面一层是 row
[[M[row][col] * N[row][col] for col in range(3)] for row in range(3)]
# Out: [[2, 4, 6], [12, 15, 18], [28, 32, 36]]
理解列表解析式
map调用比for循环快两倍,而列表解析往往比map调用稍快一些。速度的差距来自于底层,map和列表解析是在解释器中以C语言的速度来运行,比Python的for循环代码在PVM中步进运行快很多。。
重访迭代器:生成器
如今Python对延迟提供更多的支持–它提供了工具在 需要 的时候才 产生结果,而不是立即产生结果。特别地,有两种语言结构京可能地延迟结果创建。
- 生成器函数:编写为常规的 def 语句,但是使用 yield 语句 一次 返回 一个 结果,在每个结果之间 挂起 和继续它们的状态。
- 生成器表达式类似于上一小节的 列表解析式,但是,他们返回按需产生结果的一个 对象,而不是构建一个结果列表。
由于二者都 不会一次性 构建一个列表,它们 节省了内存空间,并且允许计算时间 分散 到各个结果请求。
生成器函数: yield VS return
- 常规函数:接受输入参数并 立即 送回单个结果。
- 生成器函数:送回一个值并随后从其退出的地方继续的函数,它们随时间产生值的一个序列
状态挂起
和返回一个值并 退出 的 常规函数 不同,生成器函数 自动在生成值的时刻 挂起 并继续函数的执行。因此,他们对于提前计算整个一系列值以及在类中手动保存和恢复状态都很有用,由于生成器函数在挂起时保存的状态包含它们的整个本地作用域,当函数恢复时,它们的本地变量保持了信息并且使其可用。
生成器函数和常规函数之间的主要的代码不同之处在于,生成器 yields 一个值,而不是 返回 一个值。 yield语句挂起该函数并向调用者发送回一个值,但是,保留足够的状态以使得 函数能够从它离开的地方继续。 当继续时,函数在上一个 yield返回后立即继续执行。从函数的角度来看,这允许其代码随着时间产生一系列的值,而 不是一次计算 它们并在诸如 列表 的内容中送回它们。
迭代协议整合
可迭代的对象定义了一个 __next__ 方法,它要么返回迭代中的下一项,或者引发一个特殊的 StopInteration 异常来终止迭代。一个对象的迭代器用 iter 内置函数来接收。
直接效果就是生成器函数,编写为包含yield语句的def语句,自动地支持迭代协议,并且由此可能用在任何迭代环境中以随着时间并根据需要产生结果。
生成器函数应用
def gensquares(N):
for i in range(N):
yield i**2
for i in gensquares(5):
print(i,end=':')
#OutPut: 0:1:4:9:16:
x = gensquares(4)
print(x)
#<generator object gensquares at 0x000001E470055888>
#得到的是一个生成器对象,支持迭代协议,生成器对象有一个__next__方法,它可以
#开始这个函数,或者从它上次yield值后的地方恢复,并且在得到一系列的值的最后一个时,
#产生StopIteration异常,next(x)内置函数为我们调用了一个对象的X.__next__()方法
print(next(x)) #0
print(next(x)) #1
print(next(x)) #2
print(next(x)) #9
print(next(x)) #StopIteration
for循环(以及其他的迭代环境)以同样的方式与生成器一起工作:通过重复调用**__next__**,直到捕获到一个异常。
生成器表达式:迭代器遇到列表解析
编写一个 列表解析 基本等同于:在一个list内置调用中包含一个生成器表达式以强迫其一次生成列表中所有的结果。
[x**2 for x in range(4)]
# Out: [0, 1, 4, 9]
(x**2 for x in range(4))
# <generator object <genexpr> at 0x000001B9F0DB59E8>
G = (x**2 for x in range(4))
next(G) #0
next(G) #1
next(G) #4
next(G) #9
next(G) #StopInteration
生成器函数表达式大体上可以认为是对 内存空间的优化,它们不需要像方括号的列表解析式一样,一次构建出 整个结果列表。
生成器函数 VS 生成器表达式
同样的迭代可以用 生成器函数 或 一个生成器表达式 编写。
G = (c*4 for c in "SPAM")
list(G)
# ['SSSS', 'PPPP', 'AAAA', 'MMMM']
def timefour(S):
for c in S:
yield c*4
G = timefour('spam')
list(G)
# ['ssss', 'pppp', 'aaaa', 'mmmm']
生成器是单迭代对象
生成器函数和生成器表达式自身都是迭代器,并且支持 一次活跃迭代,实际上,一个生成器的迭代器是生成器自身。
G = (c*4 for c in 'SPAM')
iter(G) is G
# True
T1 = iter(G)
next(T1) # 'SSSS'
next(T1) # 'PPPP'
T2 = iter(G)
next(T2) # 'AAAA'
next(T2) # 'MMMM'
#这个时候再通过给G赋新值都是无用的,因为已经迭代完毕,只能重新赋值
T3 = (c*4 for c in 'SPAM')
这个与内置的类型是不同的,它们支持多个迭代器并且在一个活动迭代器中传递并反应它们在原处的修改。
L = [1,2,3,4]
I1,I2 = iter(L),iter(L)
next(I1) #1
next(I1) #2
next(I1) #3
next(I2) #1
next(I2) #2
next(I2) #3
函数陷阱
本地变量是静态检测的
python定义的在一个函数中进行分配的变量名是默认为 本地变量 的,它们存在于函数的作用域并只在函数运行时存在。python是 静态检测 python的本地变量的,当编译def代码时,不是通过发现赋值语句在运行时进行检测的。
一般来说,没有在函数中赋值的变量名会在整个模块文件中查找。
X = 99
def p():
print(X)
P() # 99
def p2():
print(X)
X = 88
p2() #UnboundLocalError: local variable 'X' referenced before assignment
在编译时,python看到了对X的赋值语句,并且决定了X将会在函数中任一地方都将是 本地变量名,但是,在函数实际运行时,因为在print执行时赋值语句并没有发生,python告诉你正在使用一个未定义的变量名。实际上,任何在函数体内的赋值都会将其成为一个本地变量名。Import、=、嵌套def、嵌套类等,都会受这种行为的影响。
#下面这种用法是错误的
X=100
def p():
global X
print(X)
X=88
---------------------------------------------
#应该导入 __mian__模块,从__main__命名空间得到了全局变量版本的X
X = 100
def p():
import __main__
print(__main__.X)
X=33
p()
Chapter9 类和OOP
类的继承,类树,低端有两个实例(l1,l2),在它上有个类(c1),而顶端有两个超类/基类(c2和c3)。继承就是由下至上搜索此树,来寻找属性名称所出现的最低的地方。
- I1.x 和 I2.x 两者都会在 C1 中找到 x 并停止搜索,因为 c1 比 c2 位置低。
- I1.y 和 I2.y 两者都会在 C1 中找到 x 并停止搜索,因为 y只出现在c1中。
- I1.z 和 I2.z 两者都会在 C2 中找到 z 并停止搜索, 因为 c2 比 c3 更靠左侧。
以双下划线命名的方法(__x__)是特殊的钩子:
- 当新的实例构造时,会调用__x__
- 当实例出现在 + 表达式时,会调用__add__
- 当打印一个对象的时候,运行__str__
class FirstClass(object):
def setdata(self,value):
self.data = value
def display(self):
print(self.data)
class SecondClass(FirstClass):
def __init__(self,value):
self.data = value
def __add__(self,other):
return SecondClass(self.data + other)
def __str__(self):
return 'hello:%s'%self.data
y = SecondClass('adc')
y.display() #adc
print(y) #hello:adc 直接调用 __str__
b = y + 'xyz' #使用+号时,直接调用 __add__
b.display() #adcxyz