我想这样做:

class X:

    @classmethod
    def id(cls):
        return cls.__name__

    def id(self):
        return self.__class__.__name__

现在为类或其实例调用id():

>>> X.id()
'X'
>>> X().id()
'X'

很明显,这个确切的代码不起作用,但有没有类似的方法使它起作用?

或者有没有其他方法可以让这种行为没有太多"黑客"的东西?

推荐答案

类和实例方法位于同一名称空间中,不能重复使用这样的名称;在这种情况下,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())上调用.实例被忽略,但其类除外.

如果确实需要区分绑定到类和实例

如果你需要一种不同的方法,根据它在哪里被使用;在类上访问时绑定到类,在实例上访问时绑定到实例,您需要创建一个自定义的descriptor object.

descriptor API是Python如何将函数绑定为方法,并将classmethod个对象绑定到类;见descriptor howto.

通过创建一个包含__get__个方法的对象,可以为方法提供自己的描述符.下面是一个基于上下文切换方法绑定内容的简单方法,如果__get__的第一个参数是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)

这是一个带有可选实例方法的classmethod.像使用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类完全相同的模式.

Python-3.x相关问答推荐

如何翻转以列形式给出的日期间隔并提取多个重叠时段内每小时的音量?

为什么我的Selenium脚本在密码元素上失败?

链接列未延伸到数据框的末尾

为什么不能用格式字符串 '-' 绘制点?

将值从函数传递到标签

无法理解此递归函数的分配和环境用法

使用正确的数据类型时,使用 Cerberus 验证 JSON 架构会引发错误

每个数据行中每个数据帧值的总和

Python:获取未绑定的类方法

判断是否存在大文件而不下载它

PySpark python 问题:Py4JJavaError: An error occurred while calling o48.showString

asyncio.Semaphore RuntimeError: Task got Future 附加到不同的循环

Python3 mysqlclient-1.3.6(又名 PyMySQL)的用法?

sys.stdin.readline() 读取时没有提示,返回 'nothing in between'

如何编写可 Select 充当常规函数的 asyncio 协程?

为什么 2to3 将 mydict.keys() 更改为 list(mydict.keys())?

在 ubuntu 20.04 中安装 libpq-dev 时出现问题

为 Python 3 和 PyQt 构建可执行文件

python - Pandas - Dataframe.set_index - 如何保留旧的索引列

在 PyCharm 中配置解释器:请使用不同的 SDK 名称