一、函数、名称空间与作用域
1.函数的构成
python有三种层次的抽象:(1)程序可分成多个模块;(2)每个模块包含多条语句;(3)每条语句对对象进行操作。函数大致处于第二层。函数有它的定义格式、参数、逻辑代码块、返回值、以及函数属性五部分组成。
1 def foo(name): # foo: 别名; (),执行规则; name, 形式参数 2 """doc""" # 说明文档,可以用foo.__doc__获取 3 return name # 返回值 4 print(foo("foo")) # 填写形式参数对应的实体参数,并执行foo函数 5 f = foo # 函数别名可以赋值 6 print(f("foo"))
2.函数与方法
函数和方法是有一些区别的,提到方法一般指的是某个对象的方法。因为python自上而下执行,所以函数不可以提前声明。但是方法可以,例如类对象中,可以直接调用后面的方法。但当方法或者函数被调用时,都是函数。
1 class Foo: 2 def pri(self): 3 return foo() 4 def foo(self, name): 5 return name 6 f = Foo() 7 f.pri("foo")
3.名称空间与作用域
- 名称空间
名称空间是站在程序的角度对抽象的结构进行划分。
python使用名称空间(namespaces)的概念在应用程序上储存关于对象的信息和位置。内置函数、语句和对象有特殊的命名空间,函数、模块有单独的命名空间,也就是两者有各自的命名空间,及特定的抽象级别。在特定的抽象级别上访问对象或函数时,都将搜寻名称空间表以获得可引用的特定名字,并确定处理存储在该位置的信息的方法。
函数的参数,加上函数体中绑定的所有变量(通过赋值或者其它绑定语句,如def),功能构成了该函数的本地命名空间(local namespace),也称为本地范围(local space)。所有这些也称为该函数的本地变量(local variable)。
其它则是全局名称空间,包括可被其它函数或模块直接调用的、存活于整个程序运行期间的变量、函数、模块等。
- 作用域
作用域是站在对象的角度查看它的适用范围。在python中,作用域遵循以下规则:
- 每个模块都有自己的作用域,这意味着多重模块有它们自己的作用域,从而形成自己的名名称空间。
- 定义新函数时创建新的作用域,作用域仅限于函数体内。
- 每个未缩进的逻辑代码块,它的作用域是全局的。
4.全局变量和局部变量
定义在最高级别的模块中的变量,被称为全局变量;定义在局部作用域中的变量被称为局部变量。一个变量的作用域和它寄宿的名称空间相关。
python变量遵循LGB规则:
- 变量引用依次搜寻3种作用域:局部(Local)、全局(Global)、内置(Built-in)。
- 在局部作用域内对变量赋值时会创建新对象或更新对象,如果想在局部作用域对全局对象赋值,必须使用关键字global。
- 全局声明会把赋值名字映射到封装的模块的作用域。这意味着,要么显示地从输入的模块中输入对象,要么使用完全有效的模块/对象名。
示例一:GLB规则
1 a = 20 2 def func(): 3 print(a) # 在局部作用域找不到a,会继续从全局作用域找a 4 func() 5 # 打印ax
示例二:全局变量与局部变量的互不干扰
1 a = 20 # 全局变量 2 def func(): 3 b = 25 4 a = 30 5 global c 6 c = 125 7 print(a) 8 print(b) 9 print(a) 10 func() 11 print(c) 12 print(a) 13 # 打印结果为: 14 20 15 30 16 25 17 125 18 20
也就是说,创建局部变量a,是直接开辟内存生成对象30,然后引用给a。它与全局变量的a毫不相关。global可以修改局部变量为全局变量。
globals()函数和locals()函数可以直接查看当前文件中的全局变量和局部变量。
5、参数
python函数中的传递参数,是传递变量的值本身,而不是创建一个新的引用。
在定义函数时,有位置参数,默认参数,以及扩展参数;在调用函数时,可以按位置传递相应的变量,也可以按键值对传递相应的参数,也可以以扩展的方式传递参数。
在定义函数时,位置参数、默认参数与扩展参数的顺序是:位置参数 > 扩展元组 > 默认参数 > 扩展字典。
1 def func(a, b, *args, author="jingyu", **kwargs): 2 print(a) 3 print(b) 4 print(args) 5 print(author) 6 print(kwargs)
1 func(123, 456, 7,8,9, gender="female", age=20) 2 # 打印结果为: 3 123 4 456 5 (7, 8, 9) 6 jingyu 7 {'gender': 'female', 'age': 20}
在定义函数时,*args会把所有位置参数匹配剩余的变量组合成元组,**kwargs会把关键字参数(键值对)组合成一个字典。
在执行函数时,*list或者*tuple会把所有的元素展开成单独的位置参数,**dict会把字典内的键值对展开成关键字参数。
这个功能在写闭包和装饰器的时候非常有用。
1 func( 2 123, 456, 3 *[7, 8, 9], *[10, 11, 12], *{"name1": "sunbin", "name2": "mengtian"}, 4 **{"name3": "baiqi", "gender": "female", "age": 72}, 5 ) 6 # 打印结果为: 7 123 8 456 9 (7, 8, 9, 10, 11, 12, 'name1', 'name2') 10 jingyu 11 {'name3': 'baiqi', 'gender': 'female', 'age': 72}
6、函数属性
可以给函数创建一些属性,并通过func.__dict__来查看。函数属性应用在高度专业的应用程序中。
1 def say(): 2 print("hello, my man.") 3 # 接上面定义的func 4 func.__live__ = 20 5 func.__create__ = "2018_04_18" 6 func.say = say 7 print(func.__dict__) 8 # 打印结果为: 9 {'__create__': '2018_04_18', '__live__': 20, 'say': <function __main__.say>} 10 # 显然它可以执行 11 func.say() 12 # 结果显示 13 hello, my man. 14 # 这个和js的匿名函数有些类似,但是不支持func.say = function (){console.log("hello, my man.)} 这种写法
二、闭包
闭包是内层函数对层函数局部变量的引用。简单来说,闭包就是内部包含函数的函数。
闭包的好处:如果python检测到闭包,它有一个机制,局部作用域不会随着函数的结束而结束。可以在局部作用域添加缓存机制,使得对于计算量较大时能够提高效率。闭包也是装饰器的前提。
1、创建闭包
1 def outer(name): 2 def inner(age): 3 return "hello, i'm %s, %s." % (name, age) 4 return inner 5 outer("baiqi")(72) 6 # 打印结果为: 7 "hello, i'm baiqi, 72."
可以看到,outer("baiqi")的返回值是一个函数对象,即inner,inner可以接收参数并执行,它的返回值是真正要打印的结果。可以隐隐地感觉到,outer这个外层函数实际是在打印真正结果之前做了一些额外的"修饰"工作。
2、闭包中的外层变量和内层变量也遵循GLB规则
1 def outer(): 2 a = 100 3 def inner1(): 4 return a * 5 5 b = inner1() 6 def inner2(): 7 return b * 10 8 a = inner2() 9 def inner3(): 10 b = a 11 print(a, b) 12 inner3() 13 print(a, b) 14 outer() 15 # 打印结果为: 16 5000 5000 17 5000 500
闭包里可以写很多逻辑代码和函数,闭包并不一定要返回某个具体的内层函数,闭包有着很好的灵活性和弹性,它相当于一个工作室。
三、装饰器
1、装饰器函数的推导
重写一下白起打招呼:
1 import time 2 def hello(): 3 print("Hello, i'm baiqi, 72 years old.") 4 5 def timer(): 6 t = time.strftime("%Y-%m-%d %X", time.localtime()) 7 print(t) 8 return hello()
把它改写成闭包的形式:
1 def timer(): 2 t = time.strftime("%Y-%m-%d %X", time.localtime()) 3 print(t) 4 def fn(): 5 return hello() 6 return fn 7 timer()()
前面说到outer("baiqi")返回的是一个待执行的函数,这里也是,timer()返回的是fn,fn()执行时返回hell()执行的结果。于是正常显示:
1 2018-04-18 23:03:54 2 Hello, i'm baiqi, 72 years old.
hello这个返回值不够灵活,把hello函数作为对象传进来;并且timer()运行时直接打印了t,它是timer()函数的运行时间而不是hello的,于是改写为:
1 def timer(f): 2 def fn(): 3 t = time.strftime("%Y-%m-%d %X", time.localtime()) 4 print(t) 5 return f() 6 return fn 7 timer(hello)()
此时如果要只写hello()函数就打招呼,而不是用timer(hello)()这个额外的函数名,可以改写为:
1 import time 2 def hello(): 3 print("Hello, i'm baiqi, 72 years old.") 4 def timer(f): 5 def fn(): 6 t = time.strftime("%Y-%m-%d %X", time.localtime()) 7 print(t) 8 return f() 9 return fn 10 hello = timer(hello) 11 print(hello, hello.__name__) 12 hello() 13 # 打印结果为: 14 <function timer.<locals>.fn at 0x10f201d08> fn 15 2018-04-18 23:12:57 16 Hello, i'm baiqi, 72 years old.
在hello = timer(hello)这一步,把原来的hello函数作为参数传递给timer,那么timer(hello)的返回值就是未执行的fn函数。fn函数在执行时,也就是timer(hello)() = fn() = hello(),会返回print字符串。
在python中,用@符号来代表hello = timer(hello)这个关系式,并要求def hello()写在它的下面。即:
1 import time 2 def timer(f): 3 def fn(): 4 t = time.strftime("%Y-%m-%d %X", time.localtime()) 5 print(t) 6 return f() 7 return fn 8 @timer 9 def hello(): 10 print("Hello, i'm baiqi, 72 years old.") 11 hello()
注意:逻辑代码写内层函数里面。上面将t写在外面时,有运行timer()会直接打印调用时间,这个需要避免。可以在fn函数的内部尽情地写if for while 等等代码块,也不一定非要返回f()。
可以看出装饰器的作用:在尽量保留被装饰函数的基础上,不着痕迹地添加一些额外的功能。“尽量保留”说明它的确修改了原函数的一些内容,如函数名称(fn而不是hello),
1 print(hello.__name__) 2 # 打印结果为: 3 fn
“不着痕迹”是指不要在外层函数的局部作用域内写逻辑代码,除非业务需要。
2、装饰器函数传递参数
1 import time 2 def timer(f): 3 def fn(name, age): 4 t = time.strftime("%Y-%m-%d %X", time.localtime()) 5 print(t) 6 return f(name, age) 7 return fn 8 @timer 9 def hello(name, age): 10 print("Hello, i'm %s, %d years old." % (name, age)) 11 hello("wangjian", 62)
因为fn返回和执行hello本身,所以在hello中传递参数,就需要在fn中传递参数。为了使fn和返回执行的f函数(就是hello)具有更大的灵活性,通常会这么写:
1 import time 2 def timer(f): 3 def fn(*args, **kwargs): 4 t = time.strftime("%Y-%m-%d %X", time.localtime()) 5 print(t) 6 return f(*args, **kwargs) 7 return fn 8 @timer 9 def hello(name, age): 10 print("Hello, i'm %s, %d years old." % (name, age)) 11 hello("limu", 62)
前面已提到*args和**kwargs在定义函数和执行函数的功能。这里再重申一遍:在定义函数时,*args会将位置参数上传递的元素组合成元组,该元组赋给了变量args;在执行函数时,*args(此时args是一个元组(limu, 62))会把元组展开成元素。于是name还是那个name,age还是那个age。
1 class Cla(object): 2 def __init__(self, *args, **kwargs): 3 # l1 = *args # SyntaxError can't use starred expression here 4 print(*args) 5 # l1 = args # l1 = (1, 2, 3) 6 # l1, l2, l3 = args # l1 = 1 7 print(*kwargs) 8 def __call__(self): 9 print("hello, world!") 10 11 def func(*args, **kwargs): 12 return Cla(*args, **kwargs) 13 14 func(1,2,3, name=123, age=123)()
这也说明,fn的参数和f的参数必须保持一致。
3、装饰器上带参数
1 import time 2 def hello(name, age): 3 print("Hello, i'm %s, %d years old." % (name, age)) 4 def gender(male=False): 5 def timer(f): 6 def fn(*args, **kwargs): 7 t = time.strftime("%Y-%m-%d %X", time.localtime()) 8 print(t) 9 f(*args, **kwargs) 10 if male: 11 print(" I'm hero.") 12 else: 13 print("I'm beautiful girl.") 14 return fn 15 return timer 16 f1 = gender(male=True) # f1就是gender函数执行的返回值,也就是timer 17 f2 = f1(hello) # f2就是timer(f)函数执行的返回值,也就是fn 18 f2("limu", 62)
改写一下丑陋的f1和f2:
1 gender = gender(male=True) # f1就是gender函数执行的返回值,也就是timer 2 hello = gender(hello) # f2就是timer(f)函数执行的返回值,也就是fn 3 hello("limu", 62)
再把gender和hello写到一行:
1 hello = gender(male=True)(hello)
它符合装饰器的写法,即如果有hello=timer(hello),那么有@timer。于是这里可写为@gender(male=True)。最后,杨玉环打招呼了。
1 import time 2 def gender(male=False): 3 def timer(f): 4 def fn(*args, **kwargs): 5 t = time.strftime("%Y-%m-%d %X", time.localtime()) 6 print(t) 7 f(*args, **kwargs) 8 if male: 9 print(" I'm hero.") 10 else: 11 print("I'm beautiful girl.") 12 return fn 13 return timer 14 @gender(male=False) 15 def hello(name, age): 16 print("Hello, i'm %s, %d years old." % (name, age)) 17 hello("yangyuhuan", 62)
打印结果为:
1 2018-04-19 00:07:25 2 Hello, i'm yangyuhuan, 62 years old. 3 I'm beautiful girl.
4、装饰器的叠加:
此时,又有一个要求,要打印这个人物的国家。可以在gender里再添加一个参数,在fn函数里去写细节的逻辑代码。现在用另一种方式实现要求:
1 import time 2 def timer(f): 3 def fn(*args, **kwargs): 4 t = time.strftime("%Y-%m-%d %X", time.localtime()) 5 print(t) 6 f(*args, **kwargs) 7 return args 8 return fn 9 def country(f): 10 def fn(*args, **kwargs): 11 variables = f(*args, **kwargs) 12 if args[0] == "yangyuhuan": 13 print("A beautiful girl, from TangChao.") 14 else: 15 print("A hero from ZhanGuo.") 16 return fn 17 @country 18 @timer 19 def hello(name, age): 20 print("Hello, i'm %s, %d years old." % (name, age)) 21 hello("yangyuhuan", 24)
打印结果为:
1 2018-04-19 00:17:59 2 Hello, i'm yangyuhuan, 24 years old. 3 A beautiful girl, from TangChao
注意:两个装饰器叠加,第一个装饰器最好写返回值。叠加的装饰器由上而下装饰,由下而上执行。
5、漏掉的functools
前面提到hello.__name__是fn,即内层的那个函数名。这在框架里面是不允许出现的,于是有了个functools.wraps()方法能完整保留被装饰函数的完整信息。它的用法也十分简单。
1 import time, functools 2 def timer(f): 3 @functools.wraps(f) 4 def fn(): 5 t = time.strftime("%Y-%m-%d %X", time.localtime()) 6 print(t) 7 return f() 8 return fn 9 @timer 10 def hello(): 11 print("Hello, i'm baiqi, 72 years old.") 12 print(hello.__name__) 13 # 打印结果为 14 hello
6、类装饰器
类装饰器写法一:
1 import functools, time 2 class Add(object): 3 def __call__(self, f): 4 @functools.wraps(f) 5 def decorator(*args, **kwargs): 6 t1 = time.time() 7 outcome = f(*args , **kwargs) 8 time.sleep(1) 9 t2 = time.time() 10 print("t: %r " % (t2 - t1)) 11 return decorator 12 13 @Add() 14 def add(var1, var2): 15 return var1 * var2 16 add(4, 5)
类装饰器写法二:
1 class Add(object): 2 def add(self,f): 3 @functools.wraps(f) 4 def fn(var1, var2): 5 t1 = time.time() 6 outcome = f(var1 , var2) 7 t2 = time.time() 8 print("t: %r " % (t2 - t1)) 9 return outcome 10 return fn 11 myAdd = Add() 12 @myAdd.add 13 def add(var1, var2): 14 return var1 * var2 15 add(4, 5)
类装饰器写法三:
1 class Add(object): 2 def __init__(self, appname): 3 self.appname = appname 4 self.function_dict = {} 5 def add(self,route): 6 def decorator(f): 7 @functools.wraps(f) 8 def fn(*args, **kwargs): 9 t1 = time.time() 10 outcome = f(*args, **kwargs) 11 t2 = time.time() 12 print("t: %r " % (t2 - t1)) 13 self.function_dict[route] = fn.__name__ 14 return outcome 15 return fn 16 return decorator 17 18 app = Add("apple") 19 20 @app.add("/index") 21 def add(var1, var2): 22 return var1 * var2 23 print(add(4, 5)) 24 print(app.function_dict)
打印结果为:
1 t: 1.9073486328125e-06 2 20 3 {'/index': 'add'}
四、列表生成式、匿名函数和高阶函数
1、列表生成式[列表推导式]
列表生成式是可迭代对象迭代时的一种简便写法。
1 table = [ 2 {"name": "baiqi", "attr": "guy", "rank": 1}, 3 {"name": "limu", "attr": "guy", "rank": 2}, 4 {"name": "wangjian", "attr": "guy", "rank": 3}, 5 {"name": "lianpo", "attr": "guy", "rank": 4}, 6 {"name": "xishi", "attr": "girl", "rank": 1}, 7 {"name": "wangzhaojun", "attr": "girl", "rank": 2}, 8 {"name": "diaochan", "attr": "girl", "rank": 3}, 9 {"name": "yangyuhuan", "attr": "girl", "rank": 3}, 10 ] 11 12 13 name = [person["name"] for person in table] 14 15 name = [[person["name"], person["attr"], person["rank"]] for _, person in enumerate(table)] 16 name = [{person["name"]: person["rank"]} for _, person in enumerate(table)] 17 18 name = {person["name"]: [person["attr"], person["rank"]] for _, person in enumerate(table)} 19 20 boy = [person["name"] for person in table if person["attr"] == "guy"] 21 boy = [person["name"] for person in table if person["rank"] >= 3] 22 23 girl = [person["name"] if person["rank"] >3 and person["attr"] == "girl" else -1 for person in table]
实际上,列表生成式的使用要比上面的例子中更灵活。比如两层循环或者两个可变序列情形时。
2、匿名函数
对于上面的第一个name,可以用lambda函数来重写。同样地,可以用lambda改写上面所有的列表生成式。lambda还支持传入函数,但是没有必要性,它的核心在于简洁。
lambda params: expression。
1 func = lambda lis: [person["name"] for person in lis] 2 func(table)
3、三个高阶函数
高阶指的是该函数的作用域和存在的名称空间,以及它替代一些逻辑代码块的作用。
apply(FUNCTION, TUPLE)。快要被摒弃的函数。reduce(),逐步叠加计算,在python3中已不存在了。
map(FUNCTION),遍历,function作用在序列的每个元素上以改变元素。filter(FUNCTION),过滤,function作用在序列的每个元素上,进行条件判断并返回符合条件的元素。这些函数都不改变原序列。
1 number = list(range(1, 11, 2)) 2 outcome1 = list(map(lambda x: x**2, number)) 3 outcome2 = list(filter(lambda x: x>4, number)) 4 print(outcome1) 5 print(outcome2)
需要注意的是,map函数和filter函数的返回结果是map对象和filter对象,需要list转换一下。这些高阶函数apply函数和map函数也会以方法的形式出现在一些第三方模块中。
1 import pandas as pd 2 df = pd.DataFrame(table) 3 df["name_map"] = df.name.map(lambda x: x + "_China") 4 print(df) 5 # 打印结果为: 6 7 attr name rank name_map 8 0 guy baiqi 1 baiqi_China 9 1 guy limu 2 limu_China 10 2 guy wangjian 3 wangjian_China 11 3 guy lianpo 4 lianpo_China 12 4 girl xishi 1 xishi_China 13 5 girl wangzhaojun 2 wangzhaojun_China 14 6 girl diaochan 3 diaochan_China
4、偏函数
偏函数的功能和上下文管理器一致。都是管理上下文的开始和结束,即with ... 直到结束。在tensorflow及其它的一些框架中,reader()这个阅读器的功能随处可见,可见它功能还是很强大的。
1 f1 = open("homework/user.txt", mode="r", encoding="utf-8") 2 data = f1.readlines() 3 f1.close() 4 5 with open("homework/user.txt", mode="r", encoding="utf-8") as f3: 6 data = f3.readlines() 7 8 import functools 9 reader = functools.partial(open, mode="r", encoding="utf-8") 10 f3 = reader("homework/user.txt") 11 f3.readlines()
5.递归函数
在函数的内部调用自己本身的函数称为递归函数,递归的最大深度为998。两个例子:
1 # 利用递归函数求阶乘 2 def factorial(number, start=1): 3 fac = 1 if start in [0, 1] else start 4 if start < int(number): 5 return factorial(number, start+1) * fac 6 else: 7 return fac
注意,函数内部的factorial(number, start+1)就相当于fac。这是它的返回结果,递归函数必须存在可以终止的return。另一个特性,递归函数会在当件未满足时一直递归。
1 # 二分查找 2 def func(value, l, index=0): 3 """返回被查找值的索引""" 4 middle = len(l) // 2 # 切片 5 if middle == 0: # 当middle为0时,说明len(l)的值<=1 6 return 0 if value == l[0] else None 7 if value > l[middle]: 8 index += middle 9 return func(value, l[middle:], index) 10 elif value < l[middle]: 11 return func(value, l[: middle], index) 12 else: 13 return l.index(value) + index 14 15 l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88, 100] 16 print(func(2, l)) 17 print(func(100, l)) 18 print(func(14, l)) 19 # 打印结果为 20 # 0 21 # 25 22 # None
6.内置函数
python3.6.4官方文档中给出的内置函数如下。