classmethod 和 instancemethod 同名
Same name for classmethod and instancemethod
我想做这样的事情:
class X:
@classmethod
def id(cls):
return cls.__name__
def id(self):
return self.__class__.__name__
现在为 class 或其实例调用 id()
:
>>> X.id()
'X'
>>> X().id()
'X'
显然,这个确切的代码不起作用,但有没有类似的方法让它起作用?
或者任何其他解决方法来获得这种行为而没有太多“hacky”的东西?
Class 和实例方法位于同一个命名空间中,您不能重复使用这样的名称; id
的最后定义将在这种情况下获胜。
class 方法将继续在实例上工作,但是,不需要 创建一个单独的实例方法;只需使用:
class X:
@classmethod
def id(cls):
return cls.__name__
因为该方法继续绑定到 class:
>>> class X:
... @classmethod
... def id(cls):
... return cls.__name__
...
>>> X.id()
'X'
>>> X().id()
'X'
这是明确记录的:
It can be called either on the class (such as C.f()
) or on an instance (such as C().f()
). The instance is ignored except for its class.
如果您确实需要区分绑定到 class 和实例
如果您需要一种方法根据其使用位置不同来工作;在 class 上访问时绑定到 class,在实例上访问时绑定到实例,您需要创建一个自定义 描述符对象 .
descriptor API is how Python causes functions to be bound as methods, and bind classmethod
objects to the class; see the descriptor howto.
您可以通过创建具有 __get__
方法的对象来为方法提供自己的描述符。这是一个简单的方法,它根据上下文切换方法绑定的内容,如果 __get__
的第一个参数是 None
,则描述符被绑定到 class,否则它正在绑定到一个实例:
class class_or_instancemethod(classmethod):
def __get__(self, instance, type_):
descr_get = super().__get__ if instance is None else self.__func__.__get__
return descr_get(instance, type_)
这重用了 classmethod
并且仅重新定义了它处理绑定的方式,委托了 instance is None
的原始实现,否则委托给了标准函数 __get__
实现。
请注意,在方法本身中,您可能需要测试它绑定的内容。 isinstance(firstargument, type)
是一个很好的测试:
>>> class X:
... @class_or_instancemethod
... def foo(self_or_cls):
... if isinstance(self_or_cls, type):
... return f"bound to the class, {self_or_cls}"
... else:
... return f"bound to the instance, {self_or_cls"
...
>>> X.foo()
"bound to the class, <class '__main__.X'>"
>>> X().foo()
'bound to the instance, <__main__.X object at 0x10ac7d580>'
另一种实现可以使用两个函数,一个用于绑定到class,另一个用于绑定到实例:
class hybridmethod:
def __init__(self, fclass, finstance=None, doc=None):
self.fclass = fclass
self.finstance = finstance
self.__doc__ = doc or fclass.__doc__
# support use on abstract base classes
self.__isabstractmethod__ = bool(
getattr(fclass, '__isabstractmethod__', False)
)
def classmethod(self, fclass):
return type(self)(fclass, self.finstance, None)
def instancemethod(self, finstance):
return type(self)(self.fclass, finstance, self.__doc__)
def __get__(self, instance, cls):
if instance is None or self.finstance is None:
# either bound to the class, or no instance method available
return self.fclass.__get__(cls, None)
return self.finstance.__get__(instance, cls)
这是一个带有可选实例方法的class方法。像使用 property
对象一样使用它;用 @<name>.instancemethod
:
装饰实例方法
>>> class X:
... @hybridmethod
... def bar(cls):
... return f"bound to the class, {cls}"
... @bar.instancemethod
... def bar(self):
... return f"bound to the instance, {self}"
...
>>> X.bar()
"bound to the class, <class '__main__.X'>"
>>> X().bar()
'bound to the instance, <__main__.X object at 0x10a010f70>'
就个人而言,我的建议是谨慎使用;基于上下文改变行为的完全相同的方法使用起来可能会造成混淆。但是,有一些用例,例如 SQLAlchemy 区分 SQL 对象和 SQL 值,其中模型中的列对象会像这样切换行为;查看他们的 Hybrid Attributes documentation。此实现遵循与我上面的 hybridmethod
class 完全相同的模式。
我不知道你的实际用例是什么,但你可以使用描述符来做这样的事情:
class Desc(object):
def __get__(self, ins, typ):
if ins is None:
print 'Called by a class.'
return lambda : typ.__name__
else:
print 'Called by an instance.'
return lambda : ins.__class__.__name__
class X(object):
id = Desc()
x = X()
print x.id()
print X.id()
输出
Called by an instance.
X
Called by a class.
X
可以非常简洁地完成,方法是将方法的实例绑定版本显式绑定到实例(而不是class)。 Python 将在调用 Class().foo()
时调用在 Class().__dict__
中找到的实例属性(因为它在 class' 之前搜索实例的 __dict__
),并且 [=调用 Class.foo()
时在 Class.__dict__
中找到 35=] 绑定方法。
这有很多潜在的用例,尽管它们是否是反模式还有待商榷:
class Test:
def __init__(self):
self.check = self.__check
@staticmethod
def check():
print('Called as class')
def __check(self):
print('Called as instance, probably')
>>> Test.check()
Called as class
>>> Test().check()
Called as instance, probably
或者...假设我们希望能够滥用 map()
:
class Str(str):
def __init__(self, *args):
self.split = self.__split
@staticmethod
def split(sep=None, maxsplit=-1):
return lambda string: string.split(sep, maxsplit)
def __split(self, sep=None, maxsplit=-1):
return super().split(sep, maxsplit)
>>> s = Str('w-o-w')
>>> s.split('-')
['w', 'o', 'w']
>>> Str.split('-')(s)
['w', 'o', 'w']
>>> list(map(Str.split('-'), [s]*3))
[['w', 'o', 'w'], ['w', 'o', 'w'], ['w', 'o', 'w']]
在您的示例中,您可以简单地完全删除第二个方法,因为静态方法和 class 方法做同样的事情。
如果您希望他们做不同的事情:
class X:
def id(self=None):
if self is None:
# It's being called as a static method
else:
# It's being called as an instance method
"types" 提供了自 Python 3.4 以来非常有趣的内容:DynamicClassAttribute
它并没有 100% 实现您的想法,但它似乎是密切相关的,您可能需要稍微调整一下我的 metaclass 但是,大体上,您可以拥有它;
from types import DynamicClassAttribute
class XMeta(type):
def __getattr__(self, value):
if value == 'id':
return XMeta.id # You may want to change a bit that line.
@property
def id(self):
return "Class {}".format(self.__name__)
这将定义您的 class 属性。对于实例属性:
class X(metaclass=XMeta):
@DynamicClassAttribute
def id(self):
return "Instance {}".format(self.__class__.__name__)
这可能有点矫枉过正,尤其是如果您想远离 metaclasses。这是我想探索的技巧,所以我只是想分享这个隐藏的宝石,以防你能打磨它并让它发光!
>>> X().id
'Instance X'
>>> X.id
'Class X'
瞧...
(Python 3 only) 阐述a pure-Python implementation of @classmethod
, we can declare an @class_or_instance_method
as a decorator, which is actually a class implementing the attribute descriptor protocol的想法:
import inspect
class class_or_instance_method(object):
def __init__(self, f):
self.f = f
def __get__(self, instance, owner):
if instance is not None:
class_or_instance = instance
else:
class_or_instance = owner
def newfunc(*args, **kwargs):
return self.f(class_or_instance, *args, **kwargs)
return newfunc
class A:
@class_or_instance_method
def foo(self_or_cls, a, b, c=None):
if inspect.isclass(self_or_cls):
print("Called as a class method")
else:
print("Called as an instance method")
我想做这样的事情:
class X:
@classmethod
def id(cls):
return cls.__name__
def id(self):
return self.__class__.__name__
现在为 class 或其实例调用 id()
:
>>> X.id()
'X'
>>> X().id()
'X'
显然,这个确切的代码不起作用,但有没有类似的方法让它起作用?
或者任何其他解决方法来获得这种行为而没有太多“hacky”的东西?
Class 和实例方法位于同一个命名空间中,您不能重复使用这样的名称; id
的最后定义将在这种情况下获胜。
class 方法将继续在实例上工作,但是,不需要 创建一个单独的实例方法;只需使用:
class X:
@classmethod
def id(cls):
return cls.__name__
因为该方法继续绑定到 class:
>>> class X:
... @classmethod
... def id(cls):
... return cls.__name__
...
>>> X.id()
'X'
>>> X().id()
'X'
这是明确记录的:
It can be called either on the class (such as
C.f()
) or on an instance (such asC().f()
). The instance is ignored except for its class.
如果您确实需要区分绑定到 class 和实例
如果您需要一种方法根据其使用位置不同来工作;在 class 上访问时绑定到 class,在实例上访问时绑定到实例,您需要创建一个自定义 描述符对象 .
descriptor API is how Python causes functions to be bound as methods, and bind classmethod
objects to the class; see the descriptor howto.
您可以通过创建具有 __get__
方法的对象来为方法提供自己的描述符。这是一个简单的方法,它根据上下文切换方法绑定的内容,如果 __get__
的第一个参数是 None
,则描述符被绑定到 class,否则它正在绑定到一个实例:
class class_or_instancemethod(classmethod):
def __get__(self, instance, type_):
descr_get = super().__get__ if instance is None else self.__func__.__get__
return descr_get(instance, type_)
这重用了 classmethod
并且仅重新定义了它处理绑定的方式,委托了 instance is None
的原始实现,否则委托给了标准函数 __get__
实现。
请注意,在方法本身中,您可能需要测试它绑定的内容。 isinstance(firstargument, type)
是一个很好的测试:
>>> class X:
... @class_or_instancemethod
... def foo(self_or_cls):
... if isinstance(self_or_cls, type):
... return f"bound to the class, {self_or_cls}"
... else:
... return f"bound to the instance, {self_or_cls"
...
>>> X.foo()
"bound to the class, <class '__main__.X'>"
>>> X().foo()
'bound to the instance, <__main__.X object at 0x10ac7d580>'
另一种实现可以使用两个函数,一个用于绑定到class,另一个用于绑定到实例:
class hybridmethod:
def __init__(self, fclass, finstance=None, doc=None):
self.fclass = fclass
self.finstance = finstance
self.__doc__ = doc or fclass.__doc__
# support use on abstract base classes
self.__isabstractmethod__ = bool(
getattr(fclass, '__isabstractmethod__', False)
)
def classmethod(self, fclass):
return type(self)(fclass, self.finstance, None)
def instancemethod(self, finstance):
return type(self)(self.fclass, finstance, self.__doc__)
def __get__(self, instance, cls):
if instance is None or self.finstance is None:
# either bound to the class, or no instance method available
return self.fclass.__get__(cls, None)
return self.finstance.__get__(instance, cls)
这是一个带有可选实例方法的class方法。像使用 property
对象一样使用它;用 @<name>.instancemethod
:
>>> class X:
... @hybridmethod
... def bar(cls):
... return f"bound to the class, {cls}"
... @bar.instancemethod
... def bar(self):
... return f"bound to the instance, {self}"
...
>>> X.bar()
"bound to the class, <class '__main__.X'>"
>>> X().bar()
'bound to the instance, <__main__.X object at 0x10a010f70>'
就个人而言,我的建议是谨慎使用;基于上下文改变行为的完全相同的方法使用起来可能会造成混淆。但是,有一些用例,例如 SQLAlchemy 区分 SQL 对象和 SQL 值,其中模型中的列对象会像这样切换行为;查看他们的 Hybrid Attributes documentation。此实现遵循与我上面的 hybridmethod
class 完全相同的模式。
我不知道你的实际用例是什么,但你可以使用描述符来做这样的事情:
class Desc(object):
def __get__(self, ins, typ):
if ins is None:
print 'Called by a class.'
return lambda : typ.__name__
else:
print 'Called by an instance.'
return lambda : ins.__class__.__name__
class X(object):
id = Desc()
x = X()
print x.id()
print X.id()
输出
Called by an instance.
X
Called by a class.
X
可以非常简洁地完成,方法是将方法的实例绑定版本显式绑定到实例(而不是class)。 Python 将在调用 Class().foo()
时调用在 Class().__dict__
中找到的实例属性(因为它在 class' 之前搜索实例的 __dict__
),并且 [=调用 Class.foo()
时在 Class.__dict__
中找到 35=] 绑定方法。
这有很多潜在的用例,尽管它们是否是反模式还有待商榷:
class Test:
def __init__(self):
self.check = self.__check
@staticmethod
def check():
print('Called as class')
def __check(self):
print('Called as instance, probably')
>>> Test.check()
Called as class
>>> Test().check()
Called as instance, probably
或者...假设我们希望能够滥用 map()
:
class Str(str):
def __init__(self, *args):
self.split = self.__split
@staticmethod
def split(sep=None, maxsplit=-1):
return lambda string: string.split(sep, maxsplit)
def __split(self, sep=None, maxsplit=-1):
return super().split(sep, maxsplit)
>>> s = Str('w-o-w')
>>> s.split('-')
['w', 'o', 'w']
>>> Str.split('-')(s)
['w', 'o', 'w']
>>> list(map(Str.split('-'), [s]*3))
[['w', 'o', 'w'], ['w', 'o', 'w'], ['w', 'o', 'w']]
在您的示例中,您可以简单地完全删除第二个方法,因为静态方法和 class 方法做同样的事情。
如果您希望他们做不同的事情:
class X:
def id(self=None):
if self is None:
# It's being called as a static method
else:
# It's being called as an instance method
"types" 提供了自 Python 3.4 以来非常有趣的内容:DynamicClassAttribute
它并没有 100% 实现您的想法,但它似乎是密切相关的,您可能需要稍微调整一下我的 metaclass 但是,大体上,您可以拥有它;
from types import DynamicClassAttribute
class XMeta(type):
def __getattr__(self, value):
if value == 'id':
return XMeta.id # You may want to change a bit that line.
@property
def id(self):
return "Class {}".format(self.__name__)
这将定义您的 class 属性。对于实例属性:
class X(metaclass=XMeta):
@DynamicClassAttribute
def id(self):
return "Instance {}".format(self.__class__.__name__)
这可能有点矫枉过正,尤其是如果您想远离 metaclasses。这是我想探索的技巧,所以我只是想分享这个隐藏的宝石,以防你能打磨它并让它发光!
>>> X().id
'Instance X'
>>> X.id
'Class X'
瞧...
(Python 3 only) 阐述a pure-Python implementation of @classmethod
, we can declare an @class_or_instance_method
as a decorator, which is actually a class implementing the attribute descriptor protocol的想法:
import inspect
class class_or_instance_method(object):
def __init__(self, f):
self.f = f
def __get__(self, instance, owner):
if instance is not None:
class_or_instance = instance
else:
class_or_instance = owner
def newfunc(*args, **kwargs):
return self.f(class_or_instance, *args, **kwargs)
return newfunc
class A:
@class_or_instance_method
def foo(self_or_cls, a, b, c=None):
if inspect.isclass(self_or_cls):
print("Called as a class method")
else:
print("Called as an instance method")