Python:使用__getattribute__按名称调用实例成员的方法
我有一个类,这个类里面有一个成员对象。我想像调用容器类的方法一样,调用这个成员对象的方法。
下面的例子虽然在不同的上下文中,但可以说明我想做的事情。
假设我们有两个类,分别代表两种不同的产品:玩具和鞋子。每个类都有两个方法,比如说,weight()
和description()
,这两个方法显然是用来返回物品的重量和描述。
现在我想把这些产品放进各自的盒子里,每个盒子里要么放一个玩具
,要么放一双鞋子
。
我希望仍然能够访问weight()
和description()
这两个方法,但我希望它们是盒子
实例的成员。同时,我不想在盒子
类里专门实现这两个方法,因为每当玩具
和鞋子
添加新方法时,我就不想在盒子
类里再实现更多的方法。
下面是一个让我满意的“最终结果”的例子:
# Define our products
product1 = Toy('plastic gun', 200)
product2 = Shoe('black leather shoes', 500)
# Now place them in boxes
box1 = Box(product1, box_code='A1234')
box2 = Box(product2, box_code='V4321')
# I want to know the contents of the boxes
box1.description()
box2.description()
# Desired results
>> plastic gun
>> black leather shoes
我的实现想法是这样的。
class Product():
def __init__(self, description, weight):
self.desc = description
self.wght = weight
def description(self):
return self.desc
def weight(self):
return self.weight
class Toy(Product):
pass
class Shoe(Product):
pass
class Box(object):
def __init__(self, product, box_code):
self.box_code = box_code
self.product = product
def __getattribute__(self, name):
# Any method that is accessed in the Box instance should be replaced by
# the correspondent method in self.product
return self.product.__getattribute__(name)
但是这样不行!如果我运行第一个代码块中显示的“期望”的例子,我会遇到无限递归的问题。那么,有什么优雅的解决办法呢?
注意,盒子
有自己的参数box_code
,而且将来我可能想给玩具
和鞋子
添加新方法(比如,促销代码等等),而不需要修改盒子
类。
好吧,大家,我期待着你们的回复。
更新:问题的解决方案
感谢Interjay和Mike Boer的帮助。
我只需要把盒子
类中__getattribute__
方法的定义替换成__getattr__
,这样就完美解决了,正如Interjay所建议的那样。盒子
类的修正定义是:
class Box(object):
def __init__(self, product, box_code):
self.box_code = box_code
self.product = product
def __getattr__(self, name):
# Any method that is accessed in the Box instance should be replaced by
# the correspondent method in self.product
return self.product.__getattribute__(name)
更新:使用__setattr__
来完善例子
在我修正了getattr
方法的问题后,我发现我的类在设置属性时并没有表现出我想要的行为。例如,如果我尝试:
box1.desc = 'plastic sword'
结果不是修改product1的描述(它被封装在box1里),而是在box1里创建了一个新的成员叫“desc”。为了解决这个问题,我必须定义__setattr__
函数,如下所示:
def __setattr__(self, name, value):
setattr(self.product, name, value)
但是,仅仅添加这个函数会导致递归调用,因为在__init__
中我尝试赋值self.product = product
,这会调用__setattr__
函数,而这个函数又找不到成员self.product
,所以又会调用__setattr__
,这样就无限递归下去了。
为了避免这个问题,我必须修改__init__
方法,用类的字典来赋值新的成员。盒子
类的完整定义是:
class Box(object):
def __init__(self, product, box_code):
self.__dict__['product'] = product
self.box_code = box_code
def __getattr__(self, name):
# Any method that is accessed in the Box instance should be replaced by
# the correspondent method in self.product
return getattr(self.product, name)
def __setattr__(self, name, value):
setattr(self.product, name, value)
现在有个有趣的事情……如果我先定义成员self.product
,如上所示,程序就正常工作。但是,如果我先定义成员self.box_code
,程序又会出现递归循环的问题。有人知道这是为什么吗?
3 个回答
有件有趣的事情……如果我先定义成员 self.product,如上所示,程序就能正常运行。但是,如果我先定义成员 self.box_code,程序又会出现递归循环的问题。有人知道这是为什么吗?
如果你先写 self.box_code = box_code
,会发生这样的事情:
__setattr__
被调用时,会尝试查找 self.product
,但这个时候它还没有定义。因为没有定义,所以会调用 __getattr__
来解决属性 "product"
,但在 __getattr__
里面又有一个 self.product
,这就导致了递归循环。
你可以使用 __getattr__
来代替 __getattribute__
,就像之前有人建议的那样。这个方法会在你访问一个不存在的属性时被调用。
如果你确实想要重写(几乎)所有的属性访问,那就继续使用 __getattribute__
,并按照下面的方式来定义它:
def __getattribute__(self, name):
return getattr(object.__getattribute__(self, 'product'), name)
使用 __getattr__
而不是 __getattribute__
。如果你用 __getattribute__
,在评估 self.product
时会出现无限循环的问题。
__getattr__
只有在找不到某个属性时才会被调用,所以不会引发这个问题。