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.