user712624
user712624

Reputation:

Accessing list of Python objects by object attribute

I don't know if what I am trying to do is so un-Pythonic that I'm simply trying to do it wrong, or if I don't know how to ask the question properly. It makes sense to me to be able to do this, but I have searched 15 different ways and can't find the answer.

What I want to do seems so simple: I have a list of objects. I want to access that list by a property of the objects. This code works:

class Fruit:
    def __init__(self, name, color):
        self.name = name
        self.color = color

    def __str__(self):
        return self.name    

class BaseballPlayer:
    def __init__(self, name, number, position):
        self.name = name
        self.number = number
        self.position = position

    def __str__(self):
        return self.name    

class ListByProperty(list):
    def __init__(self, property, list):
        super(ListByProperty, self).__init__(list)
        self.property = property

    def __getitem__(self, key):
        return [item for item in self if getattr(item, self.property) == key][0]

fruits = ListByProperty('name', [Fruit('apple', 'red'), Fruit('banana', 'yellow')])

baseballTeam = ListByProperty('position', [BaseballPlayer('Greg Maddux', 31, 'P'),
                                           BaseballPlayer('Javy Lopez', 8, 'C')])
teamByNumber = ListByProperty('number', baseballTeam)

print 'Apples are', fruits['apple'].color

pitcher = baseballTeam['P']
print 'The pitcher is #%s, %s' % (pitcher.number, pitcher)
print '#8 is', teamByNumber[8]

>>> Apples are red
The pitcher is #31, Greg Maddux
#8 is Javy Lopez

But do I really have to make my own list class to do something this simple? Is there no generic way other than looping or a listcomp? This seems like it should be a very common thing to do, to have a list of objects and access items in the list by a property of the objects. It seems like it should be commonly supported in a way similar to sorted(key=...).

Note that this is not the same case as needing a dict. In fact, the whole point of using a list of objects instead of a dict is to avoid having to do something like:

fruits = {'apple': Fruit('apple', 'red')}

...which requires you to type apple twice. It seems like there should be a generic way to do something like this:

print 'Apples are', fruits['apple'].color

...without having to subclass list.

And okay, you can build a dict like this:

fruits = [Fruit('apple', 'red'), Fruit('banana', 'yellow')]
fruits = {f.name: f for f in fruits}

Or you can one-line it, but that still seems...uh...syntactically sour? :)

The best way I've figured out so far is:

class DictByProperty(dict):
    def __init__(self, property, list):
        super(DictByProperty, self).__init__({getattr(i, property): i for i in list})
        self.property = property

fruits = DictByProperty('name', [Fruit('apple', 'red')])

Oh well, thanks, I've learned a lot already from this question.

Upvotes: 4

Views: 16040

Answers (2)

smac89
smac89

Reputation: 43128

class Fruit:
    def __init__(self, name, color):
        self.name = name
        self.color = color

fruits = dict(zip(['apple', 'banana'], [Fruit('apple', 'red'), Fruit('banana', 'yellow')]))

print("An apple is %s" % fruits['apple'].color)

OR:

fruits = {fruit.name : fruit for fruit in [Fruit('apple', 'red'), Fruit('banana', 'yellow')]}

print("An apple is %s" % fruits['apple'].color)

The following does infact produce a set:

fruits = {Fruit('apple', 'red'), Fruit('banana', 'yellow')}

Note the difference from the way I created the dict

fruits = [Fruit('apple', 'red'), Fruit('banana', 'yellow')]

print("An apple is %s" % fruits[fruits.index('apple')].color)

Doesn't work because your list contains Objects of type fruit not strings, and that is the same story here:

fruits = FruitList([Fruit('apple', 'red'), Fruit('banana', 'yellow')])

print("An apple is %s" % fruits['apple'].color)

To make the above method work, do the following:

class Fruit:
    def __init__(self, name, color):
        self.name = name
        self.color = color

    def __eq__(self, other):
        if isinstance(other, Fruit):
            return (self.name, self.color) == (other.name, other.color)
        return self.name == other

fruits = [Fruit('apple', 'red'), Fruit('banana', 'yellow')]
print("An apple is %s" % fruits[fruits.index('apple')].color)

I don't recommend this because in the case that your list happens to contain the string apple, then attribute call to color becomes invalid because strings do not have an attribute called color

Upvotes: 4

user14241
user14241

Reputation: 748

You could create an instance.

apple = Fruit('apple', 'red')
print(apple.color) # I use Python 3.x

I'm not sure I'm following your question. But maybe this helps.

edit: in an attempt to gain my reputation back...

you could use instances WITHIN a dictionary. For example...

banana = Fruit('banana', 'yellow')
apple = Fruit('apple', 'red')
fruits = {'apple':apple, 'banana':banana}

or if you prefer:

fruits = {'apple':Fruit('apple', 'red'), 'banana':Fruit('banana', 'yellow')}

either way, you can call a fruit's color simply with

print(fruits['banana'].color)

Upvotes: 1

Related Questions