【问题标题】:Dynamically assign special methods to objects but not classes in Python将特殊方法动态分配给对象而不是 Python 中的类
【发布时间】:2011-01-08 22:26:27
【问题描述】:

我想做以下事情:

class A(object): pass

a = A()
a.__int__ = lambda self: 3

i = int(a)

不幸的是,这会引发:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string or a number, not 'A'

这似乎只在我将“特殊”方法分配给类 A 而不是它的实例时才有效。有什么办法吗?

我想到的一种方法是:

def __int__(self):
    # No infinite loop
    if type(self).__int__.im_func != self.__int__.im_func:
        return self.__int__()
    raise NotImplementedError()

但这看起来相当难看。

谢谢。

【问题讨论】:

    标签: python dynamic methods


    【解决方案1】:

    Python 总是在类上查找特殊方法,而不是实例(除了旧的,也称为“遗留”类的类——它们已被弃用,并且在 Python 3 中已经消失,因为它具有古怪的语义主要来自于在实例上查找特殊方法,所以你真的不想想使用它们,相信我!-)。

    要创建一个特殊的类,它的实例可以有相互独立的特殊方法,你需要给每个实例一个自己的类——然后你可以在实例的(个体)类上分配特殊的方法在不影响其他实例的情况下,从此过上幸福的生活。如果你想让它看起来像你正在为实例分配一个属性,而实际上是分配给每个实例的个性化类的一个属性,你可以通过一个特殊的__setattr__ 实现来实现,当然。

    这是一个简单的例子,带有明确的“分配给类”语法:

    >>> class Individualist(object):
    ...   def __init__(self):
    ...     self.__class__ = type('GottaBeMe', (self.__class__, object), {})
    ... 
    >>> a = Individualist()
    >>> b = Individualist()
    >>> a.__class__.__int__ = lambda self: 23
    >>> b.__class__.__int__ = lambda self: 42
    >>> int(a)
    23
    >>> int(b)
    42
    >>> 
    

    这是一个花哨的版本,你“让它看起来像”你将特殊方法分配为实例属性(当然在幕后它仍然属于类):

    >>> class Sophisticated(Individualist):
    ...   def __setattr__(self, n, v):
    ...     if n[:2]=='__' and n[-2:]=='__' and n!='__class__':
    ...       setattr(self.__class__, n, v)
    ...     else:
    ...       object.__setattr__(self, n, v)
    ... 
    >>> c = Sophisticated()
    >>> d = Sophisticated()
    >>> c.__int__ = lambda self: 54
    >>> d.__int__ = lambda self: 88
    >>> int(c)
    54
    >>> int(d)
    88
    

    【讨论】:

    • 谢谢。这看起来比使用 Thomas 的方法更干净,因为我想拥有所有的特殊方法。速度也应该提高。然后,我将覆盖基类中的 new
    • @MTsoul,当然,如果您愿意,您可以使用 __new__如果你愿意,可以*重新分配self.__class__)。
    • 如果 c 和 d 都更改了它们的类属性,代码示例如何工作?一个类的所有实例不是共享相同的类属性吗?这就是我想使用 new 的原因,并为每个需要自己的特殊方法的对象创建一个新类。
    • @MTsoul, assert c.__class__ is not d.__class__:这是因为__init__Sophisticated 继承自 Individualist)有一个声明 self.__class__ = ...,它为每个成员创建并设置了一个全新的类对象。 __new__ 可以和__init__ 一样好,但我更喜欢在可行的情况下使用__init__(比如这里!),而__new__ 仅用于__init__ 做不到的工作。
    • 啊,真是太棒了!非常感谢,亚历克斯。
    【解决方案2】:

    唯一适用于新型类的方法是在类上拥有一个调用实例属性(如果存在)的方法:

    class A(object):
        def __int__(self):
            if '__int__' in self.__dict__:
                return self.__int__()
            raise ValueError
    
    a = A()
    a.__int__ = lambda: 3
    int(a)
    

    注意a.__int__ 不是方法(只有作为 属性的函数才会成为方法),所以self 不会被隐式传递。

    【讨论】:

    • 我应该补充一点,您不必像使用特殊方法一样调用从特殊方法调用的实例属性——事实上,您可能不应该那样做。
    【解决方案3】:

    关于覆盖 __int__ 的细节我没有什么要补充的。但我注意到关于你的样本的一件事值得讨论。

    当您手动为对象分配新方法时,“self”不会自动传入。我已修改您的示例代码以使我的观点更清楚:

    class A(object): pass
    
    a = A()
    a.foo = lambda self: 3
    
    a.foo()
    

    如果您运行此代码,它会引发异常,因为您将 0 个参数传递给“foo”,而 1 是必需的。如果您删除“自我”,它就可以正常工作。

    如果 Python 必须在对象的类中查找方法并且它找到的函数是“正常”函数时,它才会自动将“self”添加到参数中。 (“异常”函数的示例:类方法、可调用对象、绑定方法对象。)如果将可调用对象粘贴到对象本身,它们将不会自动获得“自我”。

    如果您想要在那里,请使用闭包。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-06
      • 1970-01-01
      • 2013-01-13
      • 2021-04-28
      • 2017-07-13
      • 2018-02-07
      • 1970-01-01
      • 2023-03-30
      相关资源
      最近更新 更多