1.一般函数的定义,查看和调用
在python中,函数也是一种对象。同样也有地址和空间,同时也具有很多的属性和方法。每种编程语言都存在变量生存周期和空间:初始化对象-----分配空间-----定义变量----回收空间。
当我们在python中定义一个函数def demo(): 的时候,内存当中会开辟一些空间,存下这个函数的代码、内部的局部变量等等。这个demo只不过是一个变量名字,它里面存了这个函数所在位置的引用而已。我们还可以进行x = demo, y = demo,这样的操作就相当于,把demo里存的东西赋值给x和y,这样x 和y 都指向了demo函数所在的引用,在这之后我们可以用x() 或者 y() 来调用我们自己创建的demo() ,调用的实际上根本就是一个函数,x、y和demo三个变量名存了同一个函数的引用。我们首先看看如何定义函数和调用函数。
# 定义函数
def square(x):
return x**2
# return只能返回一次,只要执行return函数就终止
#返回值:没有类型限制,也没有个数限制
square
# 这并不是调用函数,而是得到函数的地址
a=square
# 把a这个变量名指向函数空间,也就是a变成了上面那个函数
b=square(3)
# 调用函数square函数传入x=3,调用函数得到的结果传给b
dir(square)
# 查看函数的方法,可以自己尝试
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
# 以上是函数对象的属性和方法
所有的函数都存在调用参数的问题,也就是形参和实参的问题。形参是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传递的参数;实参在进行函数调用时,把这些值传送给形参的参数。
我使用通俗一点的语言来说,就是在定义函数时,如果是函数体是工作空间,那么形参就是预留的接口,而实参就是调用函数时(函数工作时)插进接口的对象(常量、变量、表达式、函数)。有多少接口就必须插入多少对象而且必须一一对应,否则函数就不能工作。具体见以下实例:
# 定义一个函数具有两个参数
def foo(x, y=0): # x,y都是形参,但是y默认等于0,相当于y这个形参已经给了实参0
return x - y
a=foo(3, 1) # 实参一个是3,一个是1;自动匹配
b=foo(3) #因为y默认等于0,所以给一个实参也可以
c=foo() # 这里没有给出任何实参。但是实际上系统认为你只给出了y=0的实参
d=foo(y=1, x=3) #给出表达式的实参,也就是3-1=2
print (a,b,c,d)
2.将不定长的元组和字典作为函数的参数
有时候,你预先不知道函数需要接受多少个实参(大白话:用*表示函数将可以用元组的方式传入多个参数), 好在Python允许函数从调用语句中收集任意数量的实参。
“*args”参数表示未知的位置参数序列长度,而“**kwargs”代表包含关键字和值映射关系的字典,它可以包含任意数量的关键字和值映射,并且在函数定义中“*args”必须位于“**kwargs”前面。下面的代码演示了这种情况:
#定义函数
def show_args(arg, *args, **kwargs): #第一个参数是单个参数;第二个参数是不定长参数;第三个是不定长字典。
print (arg)
for item in args:
print (item)
for key, value in kwargs.items():
print (key, value)
args = [1, 5, 3, 4]
kwargs = dict(name='testing', age=24, year=2014)
print (show_args("hey", *args, **kwargs))
#结果是:
hey
1
5
3
4
name testing
age 24
year 2014
#感兴趣也可以尝试改变列表或者字典来试验一下。
3.嵌套函数
在一个函数中定义了另外一个函数,叫做嵌套函数。嵌套函数对于学习编程具有很重要的意义。例如常常我们会遇到这样的场景,我们调用了一个函数将一个大表格拆分成了10个小表格组成的列表。而我们要对10个小表格中的某条件的数据进行提取和处理。这些就是嵌套函数发挥作用的时候了。
嵌套函数可以访问创建它的环境(上层函数的变量),这是python函数定义语义的直接结果。一个结果是,外部函数中定义的变量可以在内部函数中引用,即使外部函数已经执行结束。(重要特性)
当内部嵌套的函数引用外部函数中的变量时,我们说嵌套函数相对于引用变量是封闭的。我们可以使用函数对象的一个特殊属性“__closure__”来访问这个封闭的变量。
#定义函数
def outer():
x = 1
def inner():
print (x) # 1
inner()
outer() #结果等于1
#下面是另外一个例子
axx=[1,2,3,4]
bxx='it is global str'
def turn():
axx.append(6)
bxx='it is locals str'
return axx,bxx
print axx,bxx #[1, 2, 3, 4] it is global str
print turn() #([1, 2, 3, 4, 6], 'it is locals str')
#函数内部的变量应该是copy上层空间的变量。但是当函数执行完毕整个变量都消失不见了。
#传递的参数尽量不要是列表等可变的数据类型。会导致不可揣测的错误。
#应该用下面这种方式进行:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
#传递的参数尽量不要是列表等可变的数据类型。会导致不可揣测的错误。
#应该用下面这种方式进行:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
说到嵌套函数就不能不说变量作用域的问题。
- L (Local) 局部作用域:比如一个函数/方法内部
- E (Enclosing) 闭包函数外的函数中:嵌套函数的外层
- G (Global) 全局作用域:如当前模块的全局变量
- B (Built-in) 内建作用域:包含了内建的变量/关键字等。
Python 的变量名解析机制也称为 LEGB 法则,具体如下:即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。
翻译一下:如果函数中有个变量a没有发现值。那么程序会在这个函数的函数体(local)中找a的值;如果没有找到则会去这个函数的上一层函数的函数体(Enclosing)寻找a的值;如果还是没有找到则会到整个py程序空间(Global)中寻找a的值。如果仍然没有找到则会到的内置函数或者模块(Built-in)中去寻找a的值。如果还是没有则会报错。当然按照之前的顺序先找到的值就是a的值。
需要注意的是:def/lambda会创建新的作用域,生成器表达式都有引入新的作用域,class的定义没有作用域,只是创建一个隔离的命名空间。在Python中,scope是由namespace按特定的层级结构组合起来的。scope一定是namespace,但namespace不一定是scope。命名空间跟作用域的区别是,它不能在里面再嵌套其他作用域。
4.返回函数的嵌套函数->闭包
python中函数也是对象。可以给函数传入函数参数,当然也可以返回函数。下面我先来看看下面这个特殊的嵌套函数:
#闭包函数的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer(a):
b = 10
# inner是内函数
def inner():
#在内函数中 用到了外函数的临时变量
print(a+b)
# 外函数的返回值是内函数的引用
return inner
if __name__ == '__main__':
# 在这里我们调用外函数传入参数5
#此时外函数两个临时变量 a是5 b是10 ,并创建了内函数,然后把内函数的引用返回存给了demo
# 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数
demo = outer(5)
# 我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量
# demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数
demo() # 15
demo2 = outer(7)
demo2() # 17
在上面的实例中两次调用外部函数outer,分别传入的值是5和7。内部函数只定义了一次,我们发现调用的时候,内部函数是能识别外函数的临时变量是不一样的。python中一切都是对象,虽然函数我们只定义了一次,但是外函数在运行的时候,实际上是按照里面代码执行的,外函数里创建了一个函数,我们每次调用外函数,它都创建一个内函数,虽然代码一样,但是却创建了不同的对象,并且把每次传入的临时变量数值绑定给内函数,再把内函数引用返回。虽然内函数代码是一样的,但其实,我们每次调用外函数,都返回不同的实例对象的引用,他们的功能是一样的,但是它们实际上不是同一个函数对象。
如果在一个内部函数里, 对在外部作用域(但不是在全局作用域) 的变量进行引用, 那么内部函数就被认为是闭包(closure)。定义在外部函数内的但由内部函数引用或者使用的变量被称为自由变量。
简单翻译:一个闭包就是你调用了一个函数A,这个函数A把参数传给了函数B并返回了给你。这个返回的函数B就叫做闭包。你在调用函数A的时候传递的参数就是自由变量。
在Python中创建一个闭包必须满足以下三点:
1闭包函数必须有内嵌函数;2.内嵌函数需要引用该嵌套函数上一级namespace中的变量;3.闭包函数必须返回内嵌函数
这样,我们可以把上面代码中的返回的函数inner叫做闭包。我们在实际应用中,闭包到底有什么作用呢?
1.闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。比如有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行。
如果我们需要取得文件"result.txt"中含有"pass"关键字的行,则可以这样使用例子程序:
def make_filter(keep):
def the_filter(file_name):
file = open(file_name)
lines = file.readlines()
file.close()
filter_doc = [i for i in lines if keep in i]
return filter_doc
return the_filter
#我们可以这样对上面的闭包进行应用
filter = make_filter("pass")
filter_result = filter("result.txt")
2.当闭包执行完后,仍然能够保持住当前的运行环境。
比如说,如果你希望函数的每次执行结果,都是基于这个函数上次的运行结果。我以一个类似棋盘游戏的例子来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。棋子运动的新的坐标除了依赖于方向和步长以外,当然还要根据原来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。
origin = [0, 0] # 坐标系统原点
legal_x = [0, 50] # x轴方向的合法坐标
legal_y = [0, 50] # y轴方向的合法坐标
def create(pos=origin):
def player(direction,step):
# 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等
# 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了。
new_x = pos[0] + direction[0]*step
new_y = pos[1] + direction[1]*step
pos[0] = new_x
pos[1] = new_y
#注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过
return pos
return player
player = create() # 创建棋子player,起点为原点
print player([1,0],10) # 向x轴正方向移动10步
print player([0,1],20) # 向y轴正方向移动20步
print player([-1,0],10) # 向x轴负方向移动10步
5.装饰器
装饰器是闭包的一种用法。也可以这么说装饰器是闭包的高级应用。装饰器的原理不做过多阐述,简言之你在一个函数func上加上@decorator_func, 就相当于decorator_func(func):
看明白了上面的闭包。就能明白装饰器其实非常容易理解,用我上一篇的文章例子来说明:
import time
import os
import chardet
import codecs
def timeit(func):
def wrapper():
starttime=datetime.datetime.now()
func() # 这里就是重点需要关注的
endingtime=datetime.datetime.now()
cost=(endingtime-starttime).seconds
print ('程序总共用了'+str(cost)+'秒钟')
return wrapper
@timeit # 实际下面的函数就是填充了上面函数的func()。只是使用这种写法使得编程非常方便
def test():
with codecs.open('d:\\school\\card_final_test.txt','rb') as fb:
text_coding=chardet.detect(fb.readline())
t_coding=text_coding.get('encoding')
print (t_coding)
with codecs.open('d:\\school\\card_final_test.txt','r+',encoding=t_coding) as fb:
for i in fb:
print (i.strip())
这里就是重点需要关注的
endingtime=datetime.datetime.now()
cost=(endingtime-starttime).seconds
print ('程序总共用了'+str(cost)+'秒钟')
return wrapper
@timeit # 实际下面的函数就是填充了上面函数的func()。只是使用这种写法使得编程非常方便
def test():
with codecs.open('d:\\school\\card_final_test.txt','rb') as fb:
text_coding=chardet.detect(fb.readline())
t_coding=text_coding.get('encoding')
print (t_coding)
with codecs.open('d:\\school\\card_final_test.txt','r+',encoding=t_coding) as fb:
for i in fb:
print (i.strip())
仓促写就,有很多不足。下面我会写出更多关于闭包和装饰器的例子出来。为自己加深理解,也为了小伙伴们更容易理解。如有不理解的或是发现问题的,欢迎留言指正或者讨论。谢谢!王谦(rwangnqian@126.com)