您必须创建自己的描述符来处理此问题;如果没有可用的实例,它必须绑定到类,否则绑定到实例:
class class_or_instance_method(object):
def __init__(self, func, doc=None):
self.func = func
self.cmdescriptor = classmethod(func)
if doc is None:
doc = func.__doc__
self.__doc__ = doc
def __get__(self, instance, cls=None):
if instance is None:
return self.cmdescriptor.__get__(None, cls)
return self.func.__get__(instance, cls)
如果没有可用的实例,此描述符将委托给 classmethod() 对象,以生成正确的绑定。
像这样使用它:
class Foo(object):
@class_or_instance_method
def details(cls_or_self, id=None):
if isinstance(cls_or_self, type):
# called on a class
else:
# called on an instance
您可以通过返回您自己的类似方法的包装器对象来使其更花哨,该包装器对象为绑定传递关键字参数。
演示:
>>> class Foo(object):
... @class_or_instance_method
... def details(cls_or_self, id=None):
... if isinstance(cls_or_self, type):
... return 'Class method with id {}'.format(id)
... else:
... return 'Instance method with id {}'.format(cls_or_self.id)
...
>>> Foo.details(42)
'Class method with id 42'
>>> f = Foo()
>>> f.id = 42
>>> f.details()
'Instance method with id 42'
函数本身的测试有点繁琐;你可以从how property objects operate 中取出一片叶子并附加一个单独的函数来处理类绑定案例:
class class_or_instance_method(object):
def __init__(self, instf, clsf=None, doc=None):
self.instf = instf
self.clsf = clsf
self.cmdescriptor = classmethod(clsf or instf)
if doc is None:
doc = instf.__doc__
self.__doc__ = doc
def __get__(self, instance, cls=None):
if instance is None:
return self.cmdescriptor.__get__(None, cls)
return self.instf.__get__(instance, cls)
def classmethod(self, clsf):
return type(self)(self.instf, clsf, doc=self.__doc__)
def instancemethod(self, instf):
return type(self)(instf, self.clsf, doc=self.__doc__)
这将为类或实例调用初始修饰函数(就像上面描述符的实现一样),但是当您使用 @methodname.classmethod 装饰器时,它允许您注册一个可选的、单独的函数来处理与类的绑定:
class Foo(object):
@class_or_instance_method
def details(self):
# called on an instance
@details.classmethod
def details(cls, id):
# called on a class, takes mandatory id argument
这有一个额外的好处,现在你可以给两个方法实现不同的参数; Foo.details() 在上面采用 id 参数,而 instance.details() 没有:
>>> class Foo(object):
... @class_or_instance_method
... def details(self):
... return 'Instance method with id {}'.format(self.id)
... @details.classmethod
... def details(self, id):
... return 'Class method with id {}'.format(id)
...
>>> Foo.details(42)
'Class method with id 42'
>>> f = Foo()
>>> f.id = 42
>>> f.details()
'Instance method with id 42'