在Python中动态为对象而非类赋予特殊方法
我想要做以下事情:
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 个回答
我对如何重写 __int__
的具体细节没有什么要补充的。不过,我注意到你示例中的一个点值得讨论。
当你手动给一个对象分配新方法时,“self”并不会自动传入。我修改了你的示例代码,以便更清楚地说明我的观点:
class A(object): pass
a = A()
a.foo = lambda self: 3
a.foo()
如果你运行这段代码,会抛出一个异常,因为你给“foo”传入了0个参数,而它需要1个参数。如果你去掉“self”,代码就能正常运行。
在Python中,只有当它需要在对象的类中查找方法,并且找到的是一个“普通”函数时,才会自动在参数前加上“self”。(“不普通”的函数示例包括类方法、可调用对象和绑定方法对象。)如果你把可调用的东西直接放到对象里,它们就不会自动带上“self”。
如果你想要“self”在那儿,可以使用闭包。
对于新式类,唯一有效的解决办法就是在类里面定义一个方法,这个方法会调用实例的属性(如果这个属性存在的话):
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
并不会被自动传递。
在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