1

I have the following (obviously simplified) class

class A(object)
    def __init__(self, a):
        self.a = a
        self.b = 'b'
        # Other class attributes are added


class B(list):
    """
    Some customization of this class...
    """
    pass

BB = B([A(i) for i in range(10)])

I want to do

B.a

and get a list of all the a attributes from every contained item in B. I know in order to do this, I need to overwrite __getattr__, but I'm not sure the best way to implement this. This needs to be generic as B doesn't know any of the attributes of A that may need to be accessed.

Can someone offer some advice on the implementation of this idea?

1 Answer 1

5

General Solution:

If you wish for this to work across the board, then you can override __getattr__() as you thought:

class A(object):
    def __init__(self, a):
        self.a = a
        self.b = a-1

class B(list):
    """
    Some customization of this class...
    """
    def __getattr__(self, name):
        return (getattr(item, name) for item in self)

bb = B([A(i) for i in range(10)])
print(list(bb.a))
print(list(bb.b))

Giving us:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8]

Note that __getattr__() only gets called if the attribute doesn't already exist. So, if you set bb.b to another value, you will get that instead:

bb = B([A(i) for i in range(10)])
bb.b = 5
print(list(bb.a))
print(bb.b)

Gives us:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
5

Example to show the lack of need for B to know about it's contents:

>>> import datetime
>>> b = B([datetime.date(2012, 1, 1), datetime.date(2012, 2, 2), datetime.date(2012, 3, 3)])
>>> list(b.month)
[1, 2, 3]

Original Answer:

The easiest way to do this is with a generator expression.

class B(list):
    """
    Some customization of this class...
    """
@property
def a(self):
    return (item.a for item in self)

This generator expression is the equivalent of:

@property
def a(self):
    for item in self:
        yield item.a

I also used the property() builtin as a decorator to make B.a act as an attribute rather than a function.

We can then do:

bb = B([A(i) for i in range(10)])
print(list(bb.a))

and get:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

You could use a list comprehension ([item.a for item in self]) if you definitely wanted a list rather than an iterator, but generally the iterator is more useful, and can be easily made into a list (as shown above).

Note you could also do this even more simply by assigning the generator expression:

class B(list):
        """
        Some customization of this class...
        """
    def __init__(self, *args):
        super(B, self).__init__(*args)
        self.a = (item.a for item in self)

However, this means the generator will be exhausted after the first use, so I would advise against it.

Sign up to request clarification or add additional context in comments.

6 Comments

This is a good idea (I've never used the property decorator before). The problem is that the attribute of A is not always 'a'. (I failed to mention that in my original question.) Can I make this more generic for any attribute name of A?
@Lattywire That requires that B knows about A (which it doesn't).
@Jeremy Why does it require B knowing anything about A?
@Lattywire, because you have to pass the attributes of A to B's constructor as part of the args. I guess B doesn't have to know about A, but the programmer has to know everything of A that will be accessed through B.
@Jeremy: I think you are looking at the wrong part of my answer, the new bit is at the top. Edit: I added an example.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.