共计 1632 个字符,预计需要花费 5 分钟才能阅读完成。
起步
文章《Python 中 property 的实现原理及实现纯 Python 版》中探究了 property
的实现原理。如果能理解那边描述符的使用方式,那也能很快理解本篇中的 staticmethod
和 classmethod
。
函数与方法
对于类中定义的方法来说,通过类来调用与实例调用是不一样的:
class C:
def f(self): pass
print(C.f) # <function C.f at >
print(C().f) # <bound method C.f of >
一个返回的是 function
类型,一个返回的是 method
类型。他们的主要区别在于,函数的 传参都是显式传递的
而方法则方法中 传参往往都会有隐式传递的,具体根据于调用方
。例如示例中的 C().f
通过实例调用的方式会隐式传递 self
数据。
staticmethod 的实现
staticmethod
的效果是让 C.f
与 c.f
都返回函数,等价于 object.__getattribute__(c, "f")
或 object.__getattribute__(C, "f")
,运行代码如下:
class C:
@staticmethod
def sf(): pass
c = C()
print(C.sf) # <function C.sf at 0x000001AEDDA64040>
print(c.sf) # <function C.sf at 0x000001AEDDA64040>
print(C.sf is c.sf) # True
要实现这样的方式也可以依托于描述符的机制,在 __get__
中返回原始的函数,因此它的 Python 实现版本异常的简单:
class staticmethod(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
return self.f
这么简单的代码也已经是 C 实现版本对应的 Python 完整代码了。
classmethod 的实现
classmethod
则是要让 C.f
和 c.f
都返回方法,并且传递隐式参数 cls
, 运行代码如下:
class C:
@classmethod
def cf(cls): pass
c = C()
print(C.cf) # <bound method C.cf>
print(c.cf) # <bound method C.cf>
print(C.cf is c.cf) # False
classmethod
不仅要隐式传递参数,还需要每次创建新的 <bound method>
对象。因此它的实现上需要用闭包,将闭包函数作为返回值以便得到新的对象:
class classmethod(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
def newfunc(*args):
return self.f(klass, *args)
return newfunc
这里的技巧就在于闭包将隐式的 cls
通过闭包空间进行绑定。这个纯 python 实现版本在功能上没什么问题,仅有个小缺陷:
c = C()
print(C.cf) # <function classmethod.__get__.<locals>.newfunc at 0x000001EDF2527EE0>
print(c.cf) # <function classmethod.__get__.<locals>.newfunc at 0x000001EDF2527EE0>
print(C.cf is c.cf) # False
尽管我们用闭包绑定了个隐式参数,但通过 c.cf
获取的依然是 function
对象。我没有找到可以在 Python 代码中创建 <bound method>
实例的方式。
总结
staticmethod
和 classmethod
都运用了描述符的机制,学习描述符不仅能提供接触到更多工具集的方法,还能更深地理解 Python 工作的原理并更加体会到其设计的优雅性。