2

I have a class that contains a member object. I would like to call the member's methods as if they were methods of the container class.

The following example illustrates (however in a different context) what I would like to do.

Suppose we have two classes representing two different kinds of products: Toys and Shoes. Each class has two methods, let's say, weight() and description(), which return obviusly the item's weight and the item's description.

Now I want to place these products in individual Boxes - each box will contain either a single Toy either a single Shoe.

I want to still be able to access the methods weight() and description(), however I want them to be members of the Box instance. Also, I don't want to specifically implement the methods weight() and description() in the Box class, because I don't want to have to implement more methods in the Box class whenever a new method is added to Toy and Shoe.

Here is an example of the "end result" that would make me happy:

# 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

My implementation idea is this.

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)

However this doesn't work! If I run the "desired" example, shown in the first code block, I get an infinite recursion. So, how is the elegant way of doing this?

Notice that Box has its own parameter box_code, and that in the future I might want to add new methods to Toy and Shoes (for example, a promotional code, or whatever), without having to modify the class Box.

Okay guys, I am eagerly awaiting for your responses.


Update: Answer to the problem Thanks to Interjay and to Mike Boer for their help.

I just had to substitute the definition of the method __getattribute__ in the class Box by __getattr__ and it worked perfectly, as suggested by Interjay. The corrected definition of the class Box is:

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)

Update: Completing the example using __setattr__

After I corrected the problem with the getattr method, I noticed that my classes weren't having the desired behavior when I wanted to set an attribute. For example, if I tried:

box1.desc = 'plastic sword'

instead of changing the description of product1 (which was incapsulated in box1), a new member called "desc" was created in box1. In order to solve this, I had to define the __setattr__ function, as shown below:

def __setattr__(self, name, value):
    setattr(self.product, name, value)

However, just adding this function creates a recursive call, because in __init__ I try to assign self.product = product' which calls the function __setattr__, which does not find the member self.product, so calls the function __setattr__ again, recursively ad infinitum.

In order to avoid this problem, I have to change the __init__ method in order to assign the new member using the class' dictionary. The complete definition of the class Box is then:

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)

Now a funny thing... If I define first the member self.product, as shown above, the program works normally. However, if I try to define first the member self.box_code, the program suffers the recursive loop problem again. Does anybody know why?

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
Felipe Ferri
  • 3,488
  • 2
  • 33
  • 48

3 Answers3

3

You could use __getattr__ instead of __getattribute__ as has been suggested. That will be called for any attribute access for an attribute that does not exist.

If you do really want to override (nearly) ALL attribute access, then continue to use __getattribute__ and define it thusly:

def __getattribute__(self, name):
    return getattr(object.__getattribute__(self, 'product'), name)
Mike Boers
  • 6,665
  • 3
  • 31
  • 40
2

Use __getattr__ instead of __getattribute__. If you use __getattribute__, you will get infinite recursion when evaluating self.product.

__getattr__ only gets called when the attribute wasn't otherwise found, so won't cause this.

interjay
  • 107,303
  • 21
  • 270
  • 254
1

Now a funny thing... If I define first the member self.product, as shown above, the program works normally. However, if I try to define first the member self.box_code, the program suffers the recursive loop problem again. Does anybody know why?

If you write self.box_code = box_code first, this is what happens:

__setattr__ gets called and tries to lookup self.product which is undefined. Because it is undefined __getattr__ gets called to resolve the attribute "product" but within __getattr__ there is another self.product which causes it to become a recursive loop.

Sze
  • 11
  • 2