在Python中动态为对象而非类赋予特殊方法

4 投票
3 回答
1042 浏览
提问于 2025-04-15 18:25

我想要做以下事情:

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()

不过那看起来有点难看。

谢谢。

3 个回答

0

我对如何重写 __int__ 的具体细节没有什么要补充的。不过,我注意到你示例中的一个点值得讨论。

当你手动给一个对象分配新方法时,“self”并不会自动传入。我修改了你的示例代码,以便更清楚地说明我的观点:

class A(object): pass

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

a.foo()

如果你运行这段代码,会抛出一个异常,因为你给“foo”传入了0个参数,而它需要1个参数。如果你去掉“self”,代码就能正常运行。

在Python中,只有当它需要在对象的类中查找方法,并且找到的是一个“普通”函数时,才会自动在参数前加上“self”。(“不普通”的函数示例包括类方法、可调用对象和绑定方法对象。)如果你把可调用的东西直接放到对象里,它们就不会自动带上“self”。

如果你想要“self”在那儿,可以使用闭包。

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 并不会被自动传递。

8

在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

撰写回答