类和实例方法位于同一个命名空间中,您不能重复使用这样的名称;最后的定义id
在这种情况下将会获胜。
类方法将继续在实例上工作,但是,有no need创建一个单独的实例方法;只需使用:
class X:
@classmethod
def id(cls):
return cls.__name__
因为该方法继续绑定到类:
>>> class X:
... @classmethod
... def id(cls):
... return cls.__name__
...
>>> X.id()
'X'
>>> X().id()
'X'
这是明确记录的:
它可以在类上调用(例如C.f()
)或在实例上(例如C().f()
)。除了其类之外,该实例将被忽略。
如果您确实需要区分绑定到类和实例
如果您需要一种根据使用地点而有不同工作方式的方法;在类上访问时绑定到类,在实例上访问时绑定到实例,您需要创建一个自定义描述符对象.
The 描述符API https://docs.python.org/3/reference/datamodel.html#implementing-descriptorsPython 如何将函数绑定为方法,并绑定classmethod
类的对象;看到描述符指南 https://docs.python.org/3/howto/descriptor.html.
您可以通过创建一个具有以下属性的对象来为方法提供自己的描述符__get__
方法。这是一个简单的方法,它根据上下文切换方法绑定的内容,如果第一个参数为__get__
is None
,那么描述符被绑定到一个类,否则它被绑定到一个实例:
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>'
另一种实现可以使用two函数,一个用于绑定到类时,另一个用于绑定到实例时:
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)
这是一个带有可选实例方法的类方法。像使用一样使用它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 值,其中模型中的列对象像这样切换行为;看到他们的混合属性文档 https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html#module-sqlalchemy.ext.hybrid。其实现遵循与我完全相同的模式hybridmethod
上面的类。
以下是根据请求提供的上述内容的类型提示版本。这些要求您的项目具有typing_extensions https://pypi.org/project/typing-extensions/安装:
from typing import Generic, Callable, TypeVar, overload
from typing_extensions import Concatenate, ParamSpec, Self
_T = TypeVar("_T")
_R_co = TypeVar("_R_co", covariant=True)
_R1_co = TypeVar("_R1_co", covariant=True)
_R2_co = TypeVar("_R2_co", covariant=True)
_P = ParamSpec("_P")
class class_or_instancemethod(classmethod[_T, _P, _R_co]):
def __get__(
self, instance: _T, type_: type[_T] | None = None
) -> Callable[_P, _R_co]:
descr_get = super().__get__ if instance is None else self.__func__.__get__
return descr_get(instance, type_)
class hybridmethod(Generic[_T, _P, _R1_co, _R2_co]):
fclass: Callable[Concatenate[type[_T], _P], _R1_co]
finstance: Callable[Concatenate[_T, _P], _R2_co] | None
__doc__: str | None
__isabstractmethod__: bool
def __init__(
self,
fclass: Callable[Concatenate[type[_T], _P], _R1_co],
finstance: Callable[Concatenate[_T, _P], _R2_co] | None = None,
doc: str | None = 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: Callable[Concatenate[type[_T], _P], _R1_co]) -> Self:
return type(self)(fclass, self.finstance, None)
def instancemethod(self, finstance: Callable[Concatenate[_T, _P], _R2_co]) -> Self:
return type(self)(self.fclass, finstance, self.__doc__)
@overload
def __get__(self, instance: None, cls: type[_T]) -> Callable[_P, _R1_co]: ...
@overload
def __get__(self, instance: _T, cls: type[_T] | None = ...) -> Callable[_P, _R1_co] | Callable[_P, _R2_co]: ...
def __get__(self, instance: _T, cls: type[_T] | None = None) -> Callable[_P, _R1_co] | Callable[_P, _R2_co]:
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)