函数调用的sum(x,y)
形式,其中(x,y)
代表的是一个元组。
如果使用了带有默认值的参数,那么该参数及其所有后续参数都是可选的,可选参数需要有默认值。
def sum(x,y=0,z): #报错,z没有默认值
return x+y+z
#如果不想程序报错,可以使用z=None,代表没有z值
默认参数可能会保留前面调用时进行的修改(可变对象),为了防止这种情况可以使用None
。
def foo(x,items=[]):
items.append(x)
return items
foo(1) #[1]
foo(2) #[1,2]
foo(3) #[1,2,3]
def foo(x,items=None):
if items is None:
items=[]
items.append(x)
return items
foo(1) #[1]
foo(2) #[2]
foo(3) #[3]
如果参数名加上星号,则代表参数个数不固定.这种参数不需要设定默认值。
#z的类型是元组
def sum(x,y=0,* z):
temp=x+y
for i in z:
temp+=i
return temp
sum(1,2,3,4) # 10
关键字参数
这是提供函数参数的一种方式,显示地命名每个参数并为其指定一个值。不允许多次定义一个相同的参数。
def sum(x,y=0,z=0):
temp=x+y
for i in z:
temp+=i
return temp
sum(1,z=2,y=3)
注意:位置参数
与关键字参数
可以同时出现在一次调用中,这就要求位置参数先出现,关键字参数后出现。
**kwargs
它必须是函数定义的最后一个参数,它代表一个字典(名kwargs),所有额外的关键字参数都会放入其中。常见的用法是配置选项较多的函数。
def sum(x,y,z,**kwargs):
abs_flag=kwargs.pop("abs",False)
if abs_flag:
return abs(x)+abs(y)+abs(z)
else:
return x+y+z
sum(-1,-2,3) # 0
sum(-1,-2,3,abs=True) # 6
python的参数传递的规则与Java类似,属于“引用传递”。引用传递会带来隐藏的错误,因此使用不可变对象当作参数是一个较好的方式。
作用域总共有三类:
系统每次执行一个函数时,就会创建一个局部命名空间
,其中包含:参数名称
和在函数体内赋值的变量名称
。解析名称时,按照 1->2->3
的顺序查找,如果仍然找不到,则引发异常。
a = 10
def foo() :
a = 3 #局部变量
def foo1():
global a #明确a是全局变量
a=3
foo()
print(a) # 10
foo1()
print(a) # 3
Python支持嵌套函数
,例如
def countdown(start):
n=start
def display():
print(n) # 局部命名空间找不到,向外层命名空间查找,这种用法有病~
while n>0:
display()
n=n-1
countdown(5) # 5 4 3 2 1
再看一例
def countdown(start):
n=start
def display():
nonlocal n # 声明n
print(n) # 局部命名空间找不到,向外层命名空间查找
# n-=1 # 报错
# n=3 #不报错
n=n-1
while n>0:
display()
n=n-1
countdown(5) # 5 4 3 2 1
解释:嵌套函数的变量是一种专门的作用域静态作用域(lexical scoping)
限定,读写模式也遵循一般的局部变量规则,内部函数不能给定义在外部函数中的局部变量重新赋值(赋值无效,不是语法错误)。
要解决这个问题,使用nonlocal
声明变量,动态作用域(dynamic scope)
。
再看一例:
i=0
def foo():
i=i+1 #报错,这里i被认为是一个局部变量,而foo没有初始化这个值
# i=3 #不报错,这里认为是定义了一个局部变量
print(i)
函数是Python中是第一类对象
,是对象,它就可以当作参数传递,还可以当作函数的结果返回。
使用举例:
# foo.py
def count():
print(1)
# goo.py
def callf(func):
return func() #()是函数调用运算符
import foo.py as f
callf(f.count) #f.count是一个函数对象
>>
1
闭包
将组成函数的语句和这些语句的执行环境打包在一起时,得到的对象称为闭包。执行环境
,例如goo.py和foo.py中有相同的变量,执行环境就涉及这种问题。
查看函数的执行环境:
f.count.__globals__
利用嵌套函数实现延迟初始化:
def lazyinit():
def getlist():
return []
return getlist
l=lazyinit()
items=l() #这里才真正初始化
如果需要在系列函数调用中保持某个状态,使用闭包非常高效。保持状态一般的方法是使用(可变)对象。
def trace(func):
def callf(*args,**kwargs):
print(args)
r=func(*args,**kwargs) # 不能使用func(args,kwargs)
return r
return callf
@trace
def sum(x,y):
return x+y
sum(1,2) #使用装饰器时,相当于trace(sum)(1,2)
如果使用多个装饰器那么顺序是:
@foo
@bar
@spam
def go():
pass
相当于
def go():
pass
go=foo(bar(spam(go)))
装饰器也支持参数传递:
def trace(msg):
def wrapper(func):
def callf(*args,**kwargs):
print(args)
r=func(*args,**kwargs)
return r
return callf
print(msg)
return wrapper
@trace("wrapper param")
def sum(x,y):
return x+y
sum(1,2)
>>
wrapper param
(1,2)
# 以上代码等同于
wrapper=trace("wrapper msg") #wrapper
wrapper(sum)(1,22)
装饰器要支持参数传递,关键点就是有多层包装。
yield
可以生成 生成器对象。
def count_down(n):
while n>0:
yield n
n=n-1
for i in count_down(5):
print(i)
>>
5
4
3
2
1
Python3将生成器函数的next()
改成__next__
,这已经说明,在大部分情况下,都是使用循环遍历生成器对象。
def reciever():
print("start recieve...")
while True:
s=(yield )
print("msg:"+s)
r=reciever()
r.__next__() #必不可少:执行yield之前的语句
r.send("1")
r.send("2")
r.send("3")
>>
start recieve...
msg:1
msg:2
msg:3
利用装饰器可以包装next
方法的调用,使代码更紧凑。
下面的例子,请关注send
的返回值.
def line_splitter(s=None):
print("start...")
result=None
while True:
line=(yield result)
result=line.split(s)
ls=line_splitter(",")
ls.__next__()
r0=ls.send("a,b,c")
r1=ls.send("e,d,f")
ls.close()
>>
start...
['a', 'b', 'c']
['e', 'd', 'f']
下面是一个在指定目录下,查找特定的文件,并在这些文件中查找特定的行,它非常类似于unix管道命令:
find | cat | grep
import os
import fnmatch
import sys
def find_files(dir,pattern):
for path,dirname,filelist in os.walk(dir):
for name in filelist:
if fnmatch.fnmatch(name,pattern):
yield os.path.join(path,name)
def opener(filenames):
for name in filenames:
f=open(name)
yield f
def cat(filelist):
for f in filelist:
for line in f:
yield line
def grep(pattern,lines):
for line in lines:
if pattern in line:
yield line
# 这个程序的好处是全部是延迟加载,程序中不存在特别大的列表
dir="/Applications/MyEclipse 2017/MyEclipse 2017.app/Contents/logs"
paths=find_files(dir,'*log')
files=opener(paths)
lines=cat(files)
pylines=grep("startup",lines)
for line in pylines:
sys.stdout.write(line)
# 也可以采取以下方式,代码更紧凑
#for line in grep("startup",cat(opener(find_files(dir,"*log")))):
# sys.stdout.write(line)
上面的程序简化后存在一个缺陷,代码的顺序不是类似unix管道,而是反过来,可以利用协程改进。改进的思路如下:
[expression for item1 in iter1 if condition1
for item2 in iter2 if condition2
...
for itemN in iterN if conditionN]
#等价于
s=[]
for item1 in iter1:
if condition1:
for item2 in iter2:
if condition2:
...
for itemN in iterN:
if conditionN:
s.append(expression)
s=[ (x,y) for x in range(20) if x%2 !=0
for y in range(10) if y%2!=0]
print(s)
注意,如果要得到元组的列表,那么(x,y)
的括号不能省略。
在Python3中迭代变量是私有变量,Python2中不是。
生成器表达式是一个对象,它执行的计算与列表推导相同,不同点是:列表推导会创建一个包含结果的列表,而生成器对象并未生成数据,而只包含生成数据的规则,在需要的时候再创建结果数据。这是函数式编程的体现。能够极大地优化内存使用。
f = open('t_example2.py')
lines = (line.strip() for line in f.readlines()
if line is not None and len(line) > 0 and line[0] == '#')
#ls=list(lines)
for line in lines:
print(line)
请注意注释的那行代码:
已使用
的生成器对象,它无法再重新迭代了,即生成器对象的游标是有状态的(类似一个工作流)列表推导和生成器表达式都属于声明式编程。
使用lambda
可以创建表达式形式的匿名函数:
lambda args:expression
s=(lambda x,y:x+y)(1,2)
print(s) #3
Python对递归函数的深度做了限制,一般是1000.
sys.getrecursionlimit()
sys.setrecursionlimit()
import sys
print(sys.getrecursionlimit())
#code 1:深度遍历一个列表
def flattern(lists):
for s in lists:
if isinstance(s,list):
flattern(s)
else:
print(s)
items=[[1,3,2],3,[4,[5,6]]]
flattern(items)
# code 2
def genflattern(lists):
for s in lists:
if isinstance(s,list):
genflattern(s)
else :
yield s
gf=genflattern(items)
for i in gf:
print(i) #只打印3
# code 3
def genflattern3(lists):
for s in lists:
if isinstance(s,list):
for item in genflattern(s):
yield item
else :
yield item
gf=genflattern3(items)
for i in gf:
print(i)
请注意上述2,3两段代码的区别,第2段是无法正常工作的,因为每次递归都创建新的生成器,而不是预想中的迭代生成器。
在使用文档字符串时,需注意带有装饰器的函数,它返回的文档字符串返回的是装饰器函数的相关属性,所以在定义装饰器时,通常可以使用以下语句来复制文档字符串:
def warp(func):
call(*args,**kwargs):
return func(*args,**kwargs)
call.__doc__=func.__doc__
call.__name__=func.__name__
return call
@warp
def foo():
"foo test"
pass
def foo():
pass
foo.x=1
函数也是一个对象,它有一个__dict__
属性。它也需要注意使用装饰器时,装饰器和原函数的对象的__dict__
并不是一个,通常装饰器函数需要进行如下操作:
call.__dict__.update(func.__dict__)
x=1
y=2
s=eval('(lambda a,b:a+b)(x,y)') # 3
exec("s=s+1")
print(s) # s=4
eval(str,globals,locals)
执行的是一个表达式,可以传入全局和局部命名空间来指定运行时的部分的环境
exec(str,globals,locals)
它等同于一条语句
由于eval,exec
运行时十分消耗资源,因此可以先将字符串编译,保存起来,以优化性能.
s=1
src="s+=1"
bsrc=compile(src,'','exec')
exec(bsrc)
exec(bsrc)
print(s) # 3