Python:使用__getattribute__按名称调用实例成员的方法

2 投票
3 回答
3595 浏览
提问于 2025-04-15 16:27

我有一个类,这个类里面有一个成员对象。我想像调用容器类的方法一样,调用这个成员对象的方法。

下面的例子虽然在不同的上下文中,但可以说明我想做的事情。

假设我们有两个类,分别代表两种不同的产品:玩具鞋子。每个类都有两个方法,比如说,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 个回答

1

有件有趣的事情……如果我先定义成员 self.product,如上所示,程序就能正常运行。但是,如果我先定义成员 self.box_code,程序又会出现递归循环的问题。有人知道这是为什么吗?

如果你先写 self.box_code = box_code,会发生这样的事情:

__setattr__ 被调用时,会尝试查找 self.product,但这个时候它还没有定义。因为没有定义,所以会调用 __getattr__ 来解决属性 "product",但在 __getattr__ 里面又有一个 self.product,这就导致了递归循环。

3

你可以使用 __getattr__ 来代替 __getattribute__,就像之前有人建议的那样。这个方法会在你访问一个不存在的属性时被调用。

如果你确实想要重写(几乎)所有的属性访问,那就继续使用 __getattribute__,并按照下面的方式来定义它:

def __getattribute__(self, name):
    return getattr(object.__getattribute__(self, 'product'), name)
2

使用 __getattr__ 而不是 __getattribute__。如果你用 __getattribute__,在评估 self.product 时会出现无限循环的问题。

__getattr__ 只有在找不到某个属性时才会被调用,所以不会引发这个问题。

撰写回答