编程语言中的函数就是对一段代码逻辑的封装,它是面向过程编程的基本单元。Python 是面向对象的语言,但是也对函数式编程提供了部分支持,并且,函数是 Python 里面的一等对象,即:函数在运行时被创建;能作为变量赋值;能作为参数传递;能作为结果返回。
基于函数是一等对象,如果一个函数接受另外的函数作为参数,或者把另外的函数作为结果返回,那么该函数便是高阶函数。在函数式编程范式中,最为人熟知便是map
、filter
、reduce
,不过在 Python 中,它们都有更好的替代方案,即列表推导式和生成器表达式,使用后者,代码逻辑更加清晰,性能更好。
Python 内置库里也有 operator 和 functools 这样的函数式包,operator 主要为多个算术运算符提供了对应的函数,functools 提供了一系列高阶函数,如 reduce、partial 等。partial 用于生产偏函数,偏函数是指基于一个函数创建一个新的可调用对象,把原函数的某些参数固定,像 flask 的 request 等对象就是用 partial 生成的。
1.def
关键字
2.匿名函数lambda
3.动态创建,即使用字符串形式的函数表达式,通过编译等步骤最终生成函数对象,适合写框架等场景
def gen_function(function_express):
module_code = compile(function_express, '', 'exec')
function_code = [c for c in module_code.co_consts if isinstance(c, types.CodeType)][0]
return types.FunctionType(function_code, builtins.__dict__)
4.还有一种广义上的可调用对象,只需要实现__call__
特殊方法,任何 Python 对象都可以表现得像函数
我非常喜欢的 Python 特性之一是它极为灵活的参数处理机制,Python 里面共有位置参数、默认参数、可变参数、关键字参数、命名关键字参数,使用者可以灵活组合,而且可以配合拆包使用。
1.位置参数,def test(a)
,a 就是位置参数
2.默认参数,def test(a=1)
,a 就是默认参数
3.可变参数,def test(*a)
,此时传入的参数个位是可变的,并且会在函数内部组装成一个元祖,配合元祖拆包的话又可以很方便的还原成单个参数
4.关键字参数,def test(**a)
,和可变参数不同的是可以传入带参数名的参数,并且会组装成一个 dict
5.命名关键字参数,def test(a, *, b)
,此时,b 作为命名关键字参数,调用 test 函数的时候,只接受参数名为 b 的关键字参数,也就是把关键字参数参数名定死了
其实对于任何函数,都可以根据func(*args, **kw)
的形式去调用,简洁优雅
众所周知,Python 里面一切皆对象,函数也不例外,它是function
对象的实例。
函数对象有很多属性,可以使用dir
函数探知,其中很多都是 Python 对象共有的,但函数对象肯定会有自己的专业属性,下面这个表格简单记录了一下。
| 名称 | 类型 | 说明 |
|:-:|:-:|:-:|
aannotations|dict| 参数和返回值的注解
call|method-wrapper| 实现可调用对象协议,即 () 运算符
closure|tuple| 函数闭包,即自由变量的绑定
code|code| 编译成字节码的函数元数据和函数定义体
defaults|tuple| 形式参数的默认值
get|method-wrapper| 实现只读描述符协议
globals|dict| 函数所在模块中的全局变量
kwdefaults|dict| 仅限关键字形式参数的默认值
name|str| 函数名称
qulname|str| 函数的限定名称
要说明一点,虽然对象信息很全,但是里面信息组织的方式并不是便利的,比如函数对象的defaults属性保存着位置参数和关键字参数的默认值,然而,参数名称却在code属性中。这里我们可以借助inspect
模块。
Python3 提供了一种新的句法,用于为函数的参数和返回值附加元数据,信息便保存在__annotations__
属性里,例如def test(a:str,b:'int>0'=4) -> str:
。注解只是让代码逻辑更清晰,元数据可以提供给 IDE、框架等工具去使用,但是对解释器没有任何意义。
闭包是指延伸了自己作用域的函数,如果一个函数可以访问定义体之外定义的非全局变量,那么它就是闭包。
Python 的变量作用域遵循 LEGB 原则。L 指local,局部作用域;E 指enclosing,嵌套作用域;G 指glocal,全局作用域;B 指bulitin,内置作用域。有如下一个函数体:
def test():
l = 0
def inner(a):
l += 1
return a * l
return inner
此时,l
叫做自由变量,它没有在inner
的作用域内,而inner
访问了l
,所以inner
就是一个闭包,那么 Python 中是怎么实现这样的机制的呢?
函数对象有一个__code__
属性,__code__.co_freevars
就保存着自由变量的名称,它是一个元祖;还有一个__closure__
属性,里面保存着若干cell
对象,它也是一个元祖,这两个元祖中的元素一一对应,类似于键值对,而cell
对象的cell_contents
属性便保存着自由变量的值。这样在调用inner
的时候就能使用这些自由变量了。
但前面的test
定义其实是有问题的,问题在哪儿呢?其实在函数体内,一旦给变量赋值,那么解释器便认为该变量是局部变量!inner
中l+=1
等于l=l+1
,而且l
是不可变类型,未赋值先引用,所以会报错UnboundLocalError
。
Python3 引入了nonlocal
来解决这个问题,它的作用是把变量标记为自由变量,如果自由变量更新新值,闭包中的绑定也会随之更新。那么 Python2 要怎么解决呢?把l
变成 dict、list 等可变对象就可以了。
装饰器就是一种特殊的闭包,他只接受函数作为参数,严格来说,装饰器只是一种语法糖,它把被装饰的函数替换成其他函数,简洁地扩展函数功能。以下两种写法是等价的。
@decorate
def target():
pass
def target():
pass
target = decorate(target)
还有,遇到嵌套的装饰器不要慌,只要遵循着由下往上看,把下当成一个整体,下是上的参数,装饰器的结构便能理清楚了。例如如下的结构,就等价于target=decorator1(decorator2(a)(decorator3(target)))
。
@decorator1
@decorator2(a)
@decorator3
def target():
pass
装饰器是在导入时运行的,被装饰的函数在被调用时才运行,这点要尤为注意,否则在使用装饰器时会出现难以察觉的 bug。
装饰器的实现需要注意的是,被装饰的函数其实是装饰器返回的一个闭包,被装饰的函数对象原先的信息都没了,为了后续的调试等,一定要使用functools.wraps
来加一个装饰,装饰装装饰。
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('我被装饰了,好开心')
return func(*args, **kw)
return wrapper
def decorator(parameter):
def inner(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('我被装饰了,好开心')
return func(*args, **kw)
return wrapper
return inner
用可调用对象的思路去实现装饰器效果会更好,并且可以优雅兼容有参无参的情况
class Decorator(object):
def __init__(self, *args, **kwargs):
self.func = None
# 如果装饰器类初始化参数只有1个函数对象,那么就认为该装饰器自身是无参的
# 否则,装饰器类自身有参数,那么会先自身初始化
if len(args) == 1 and isinstance(args[0], types.FunctionType):
self.func = args[0]
# 各种初始化,省略
def __call__(self, *args, **kwargs):
if self.func:
return self.func(*args, **kwargs)
# 如果self.func没有初始化,那么到这一步时,装饰过程才会启动
self.func = args[0]
def wrapper(*args, **kwargs):
return self.func(*args, **kwargs)
return wrapper
类装饰器
def class_decorator(cls):
print('我被装饰了,好开心')
return cls
类方法装饰器,Python 内置的像@property,@classmethod类似,背后是描述符协议,留待之后再说
def method_decorator(func):
@functools.wraps(func)
def wrapper(self, *args, **kw):
print('我被装饰了,好开心')
return func(self, *args, **kw)
return wrapper