在定义 Python 类时,成员方法有:
- 普通方法,第一个参数为实例,通常命名为 self;
- 类方法,第一个参数为类对象,通常命名为 cls;
- 静态方法,没有特殊参数。
class MyClass:
def hello(self):
'''
普通方法
'''
print("world")
@classmethod
def className(cls):
'''
类方法
'''
print(cls.__name__)
@staticmethod
def sum(a, b):
'''
静态方法
'''
return a + b
装饰器(Decorator)
定义类方法和静态方法需要两个装饰器:
- classmethod
- staticmethod
这是两个 Python 内置的方法,可以在 Python 中查看它们的类型:
>>> classmethod
<class 'classmethod'>
>>> staticmethod
<class 'staticmethod'>
它们是两个类。
我们之前的文章介绍过装饰器(Decorator):
- 它是 1 个函数;
- 参数也是 1 个函数;
- 返回值还是 1 个函数;
下面是一个简单的例子:
def myDecorator(func):
def wrapper(*args, **kargs):
print("执行前")
ret = func(*args, **kargs)
print("执行后")
return ret
return wrapper
@myDecorator
def hello():
print("world")
hello()
执行结果:
pi@raspberrypi:~ $ python3 test.py
执行前
world
执行后
装饰器的意思是,以传入的函数为基础,在函数前后增加一些 “装饰” 代码。这有点像快递,打包机是装饰器,商品是输入的函数,快递盒就是 “装饰”,完整的包裹是输出的函数,打包机为商品增加了外观保护功能。
其中 @myDecorator
实际上是个语法糖,它会被 Python 解析为下面的等价形式:
def hello():
print("world")
hello = myDecorator(hello)
刚才看到 classmethod, staticmethod 明明都是 class,这是怎么回事呢?
实际上只要是 callable 对象都可以,在 Python 中可以验证 classmethod, staticmethod 是否 callable:
>>> callable(classmethod)
True
>>> callable(staticmethod)
True
这两个装饰器被 Python 语法糖转换以后,相当于:
def className(cls):
print(cls.__name__)
className = classmethod(className)
def sum(a, b):
return a + b
sum = staticmethod(sum)
刚好是这两个类的实例化,参数分别是两个成员方法,最后的结果是 MyClass 拥有 className 和 sum 两个成员,但类型都不是 function:
>>> vars(MyClass)
mappingproxy({'__module__': '__main__', 'hello': <function MyClass.hello at 0x7f80592940>, 'className': <classmethod object at 0x7f80598fd0>, 'sum': <staticmethod object at 0x7f80598fa0>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None})
注意到它们分别变成了:
- 'className': <classmethod object at 0x7f80598fd0>
- 'sum': <staticmethod object at 0x7f80598fa0>
Descriptor(表示器)
Python 是一种动态语言,没有提供类似 interface 这样的机制,但 Python 规定了一系列协议(Protocol)。我觉得 Protocol 翻译为惯例或者约定更容易理解。
只要一个对象提供了指定的接口,那么它就满足这个 Protocol。Python 没有强制的接口检查,这种协议更接近调用约定。常见的比如 Iterator(迭代器)、Generator(生成器)、ContextManager(用于支持 With)等等,可以在 Python 文档中找到所有协议的介绍。
classmethod 和 staticmethod 类都实现了表示器(Descriptor)协议,实现了下述方法(全部或部分):
__get__()
, 处理实例的查询;__set__()
, 处理实例的赋值;__delete__()
,处理实例的删除;
下面是 Python 文档中的例子:
class Ten:
def __get__(self, obj, objtype=None):
return 10
class A:
x = 5 # Regular class attribute
y = Ten() # Descriptor instance
>>> a = A() # Make an instance of class A
>>> a.x # Normal attribute lookup
5
>>> a.y # Descriptor lookup
10
需要注意的是,表示器协议需要对象作为类或者实例的属性时才有效。
再回到 classmethod
和 staticmethod
, 确认它们实现了 __get__
方法:
>>> dir(MyClass.className)
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> dir(MyClass.sum)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
前面注意到,用 @classmethod
和 @staticmethod
定义方法时,语法糖只是初始化了 classmethod
和 staticmethod
实例,对比前面提到的装饰器,“包装” 过程应当在访问属性时实现:
# Decorator obj as an attibute, this method handlers the getattr call.
def __get__(self, obj, obj_type = None):
# warp the original function
def wrapper(*args,**kargs):
return self._func(obj_type, *args, **kargs)
return wrapper
__get__
方法接受 3 个参数(以 classmethod 装饰器说明):
- self,属性自身,也就是
MyClass.className
; - obj,
MyClass
的实例,对于 classmethod,显然不需要实例; - obj_type,
MyClass
类。
显然,装饰函数值需要将 obj_type
插入到函数的参数列表第一个位置即可实现 `classmethod。
对于 staticmethod 则更简单,直接返回内部函数即可,直接忽略 obj
和 obj_type
两个参数,这也是静态函数和一般的独立函数一样的原因,因为静态函数就是一个保存在类中的独立函数。
总结
下面是我实现的类方法和静态方法装饰器,相信你已经了解它们是如何工作的。
# define our own classmethod decorator
class myClassMethod():
# init a Decorator obj, see the Decorator protocol here: https://docs.python.org/3/glossary.html#term-descriptor
def __init__(self,func):
self._func = func
# Decorator obj as an attibute, this method handlers the getattr call.
def __get__(self, obj, obj_type = None):
# warp the original function
def wrapper(*args,**kargs):
return self._func(obj_type, *args, **kargs)
return wrapper
class myStaticMethod():
def __init__(self, func):
self._func = func
def __get__(self, obj, obj_type = None):
return self._func
# try it
class Test:
@myClassMethod
def hello(cls):
print("world.")
@myStaticMethod
def sum(a, b):
return a+b
t = Test()
# works as classmethod and staticmethod does.
Test.hello() # world
t.hello() # world
print(Test.sum(1, 2)) # 3
print(t.sum(1, 2)) # 3
可以看到,它们工作良好。
今天捎带着介绍了一点我对 Protocol 的理解,重温 Decorator 的用法,认识了一个新的 Protocol:Descriptor。可以看到 Python 中灵活应用 Protocol,做很多有趣的东西。