Python的类内成员函数是method
类型,而普通函数是function
类型,精确地来说,类成员函数应该被称为方法。
通过以下代码,可以看到它们各自的类型:
class TypeTest:
def method_test(self):
pass
print(type(TypeTest().method_test))
def function_test():
pass
print(type(function_test))
输出结果:
<class 'method'>
<class 'function'>
和很多现代高级语言一样(如JS),Python中的函数也都是对象,每一个函数都是callable types
的一个实例,method和function均属于callable types
,这里先介绍一下函数,类的方法则改日再讨论。
Function以及函数参数
function
通常如此定义:
def foo():
# do something
pass
跟其它语言一样,function可以输入参数:
def foo(arg):
# do something
pass
你甚至可以指定返回类型:
def foo(arg) -> None:
# do something
pass
Python允许你定义两个特殊的参数*args,和**kwargs,当然参数名可以自定义:
def foo(arg, *args, **kwargs) -> None:
# do something
pass
其中*args是一个tuple,而**kwargs是一个dict,也就是如果如此调用:
def foo(arg, *args, **kwargs) -> None:
print(arg)
print(args)
print(kwargs)
foo(1, 2, 3, 4, 5, test=6)
那么会输出如下结果:
1
(2, 3, 4, 5)
{'test': 6}
*和**符号本身也可以用来展开tuple和dict:
def foo(arg, *args, **kwargs) -> None:
print(*args)
print(*kwargs)
foo(1, 2, 3, 4, 5, test=6, test2=5)
这样的输出结果会是:
2 3 4 5
test test2
对于tuple,*将它展开成了各个元素,而对于dict,*将它的keys展开了,至于**符号则将会把dict展开成如下形式:
test=6 test2=5
这使得*args跟**kwargs在不同函数之间传递成为了可能:
def bar(*args, **kwargs):
print(args)
print(kwargs)
def foo(*args, **kwargs):
bar(*args, **kwargs)
注意,此处bar(arg,kwargs)
的使用方法将是错误的,这意味着传递了一个tuple跟一个dict作为两个普通参数,这将导致语法错误。
Python的参数可以使用参数名来指定赋值:
def foo(a, b):
print('{} {}'.format(a, b))
foo(b=1, a=2)
这将输出2 1
,这在使用大量默认参数时非常方便。
可以看到,这种形式跟**kwargs的kv对形式是一样的,为了避免歧义,在**kwargs后面不可以再定义参数,而*args则没有这个问题,所以*args后面是可以定义参数的:
def foo(*args, arg, **kwargs): # 合法
pass
def bar(*args, **kwargs, arg): # 非法
pass
当然,如果你使用def foo(*args, arg, **kwargs)
的写法,那么你在*args后面的所有参数都将需要使用指定参数名的形式赋值,不然都会默认为*args的元素,所以一般来说,*args跟**kwargs总是在最后定义。
Python函数的属性
如前所述,Python的函数本身就是一个callable
类型的对象,是对象,就有属性,函数的内置属性如下:
属性 | 作用 | 是否可写 |
---|---|---|
__doc__ |
函数文档 | 可写 |
__name__ |
函数名 | 可写 |
__qualname__ |
函数全名 3.3以后的新特性 |
可写 |
__module__ |
函数所在的模块名 | 可写 |
__defaults__ |
一个元组,包含了所有的默认参数值,如果未指定默认参数,则为None
|
可写 |
__code__ |
编译过的函数体代码(官方说明,打印这个属性得到的是该函数的具体文件位置和行数) | 可写 |
__globals__ |
该函数所在的模块中包含的全部全局变量,dict 类型 |
只读 |
__dict__ |
函数的属性集合 | 可写 |
__closure__ |
闭包,内函数调用了外函数的变量才有的属性 | 只读 |
__annotations__ |
注解,这个是你写了才有的 | 可写 |
__kwdefaults__ |
keyword-only的参数(在*args后面定义的参数)的元组 | 可写 |
大多数属性是可写的,但是如__name__
__qualname__
,重写了它们似乎也没有什么用,__doc__
和__annotations__
则可以通过以下形式定义:
def foo(arg: str) -> None:
"""
:rtype: None
:param arg:
"""
print(arg)
当然,对每一个函数写上完备的文档和注解是一种良好的编程习惯。
__defaults__
和__kwdefaults__
就比较有意思了,虽然在定义函数时已经指定了默认参数,但是由于__defaults__
和__kwdefaults__
是可写的,所以你可以运行时修改它们:
def foo(a: str = 'a', *args, b='b') -> None:
"""
:rtype: None
:param a:
:param args:
:param b:
"""
print(a)
print(b)
foo.__defaults__ = ('A',)
foo.__kwdefaults__ = {'b': 'B'}
foo()
以上代码打印的是大写的A和B。
__closure__
则是出现闭包才有的属性:
def foo(arg):
def bar():
print(arg)
print(bar.__closure__)
foo('hello')
如此会打印两个地址:<cell at 0x00000271BDCD6430: str object at 0x00000271BDD9D9F0>
至于其它属性就不做多赘述了。
Python函数装饰器
函数装饰器在函数上面用@定义:
def f1(arg):
def wrapper():
print('decorated')
arg()
return wrapper
@f1
def foo():
print('foo')
foo()
如上语法严格等价于:
def f1(arg):
def wrapper():
print('decorated')
arg()
return wrapper
def foo():
print('foo')
f1(foo)()
装饰器,顾名思义,对函数进行装饰,所以上面的f1
函数可以对传进来的arg
函数做出各种修改,甚至可以返回一个与原函数完全无关的callable
对象:
def f1(arg):
def wrapper():
print('decorated')
print('f1')
return wrapper
@f1
def foo():
print('foo')
foo()
这样的输出结果会是:
decorated
f1
此时,任何使用f1
进行装饰的函数都将输出一样的结果,与原函数无关。
甚至返回一些别的类型的对象也是可以的:
def f1(arg):
return 'decorated foo'
@f1
def foo():
pass
print(foo)
此时,foo
将是一个str
类型的对象,值为decorated foo
。
装饰器也可以有多个,还可以含参:
def f1(arg):
def wrapper(func_arg):
def inner_wrapper():
print('f1 decorated')
func_arg(arg)
return inner_wrapper
return wrapper
def f2(func):
def wrapper(arg):
print('f2 decorated')
func(arg)
return wrapper
# 以下写法严格等价于 foo = f1('foo')(f2(foo))
@f1('foo')
@f2
def foo(arg):
print(arg)
foo()
学过递归的同学一定很熟悉了,这也是某种形式的套娃,基础薄弱的同学可能会觉得有点烧脑:第一行的装饰器返回一个callable
对象并以第二行的装饰器返回的对象作为参数,以此类推。可以看到这里的上层装饰器f1
是可以不含参的,而它的返回值必须接收一个至少含一个参数的callable
类型,而这个参数既是f2(foo)
所返回的值。
那么敏锐的同学一定发现了,这里f2
的返回值它的类型不一定必须是callable
的,也就是f2(foo)
的返回值可以是任何类型:
def f1(arg):
def wrapper(func_arg):
def inner_wrapper():
print('f1 decorated')
print(func_arg)
return inner_wrapper
return wrapper
def f2(func):
return 'f2 decorator'
# 以下写法严格等价于 foo = f1('foo')(f2(foo))
@f1('foo')
@f2
def foo(arg):
print(arg)
foo()
以上写法也是合法的,只是没有什么实际价值。
装饰器可以应用在许多场合,可以说是一种高级的多态,它可以将类似的函数中重叠的部分抽取出来做成装饰器,剩下的有区别的部分再分别实现,典型的应用有:插入日志、性能测试、事务处理、缓存、权限校验等场景,这里我尝试写了一个简陋的日志插入装饰器:
INFO = 0
DEBUG = 1
GLOBAL_LOG_LEVEL = DEBUG
def log_level(level):
def wrapper(logger):
def inner_wrapper(logs):
if level <= GLOBAL_LOG_LEVEL:
print(logs)
logger()
return inner_wrapper
return wrapper
@log_level(level=DEBUG)
def debug_logger():
print('this is a debug level log')
@log_level(level=INFO)
def info_logger():
print('this is an info level log')
info_logger('info')
debug_logger('debug')
将GLOBAL_LOG_LEVEL
设置为INFO
,则仅执行info_logger
,设置为DEBUG
,则info_logger
和debug_logger
都会执行。
注意这里的装饰器@log_level(level=INFO)
严格等价于log_level(level=INFO)(info_logger)
,也就是跟以下写法完全等同:
def info_logger():
print('this is an info level log')
info_logger = log_level(level=INFO)(info_logger)
展开来看的话,是不是更好理解了?log_level(level=INFO)
的返回值wrapper
是一个callable
对象,接收一个info_logger
的callable
对象作为参数,并返回了一个inner_wrapper
的callable
对象,所以,当你执行info_logger('info')
时,事实上是执行了inner_wrapper
并且其参数log
为info_logger
的参数,而内部变量level
则是外部的装饰器log_level
的参数。