Question

I have to create many subclasses of a given class. I don't want to do that manually (since the number of classes may be very big. I have an almost working solution... almost - look into the example below and please tell me what i do wrong.

The ZOO_CLASSES dictionary holds (and must hold) for each animal (string) the class we create.

The final solution MUST use inheritance from the Animal base class and objects of each animal species must be created using

one_lion = Lion()

or

one_lion = ZOO_CLASSES['Lion']()

The problem I've spotted is that (as the tests would show if you run them - instead of "True, False, This is Lion, This is Bear" I get "True, True, This is Parrot, This is Parrot". I think the problem is that when calling the constructor and evaluating its parameters (self and i) it takes their last assigned value (i="Parrot"). However, the objects I create are of the right class type and I don't see any other unexpected behaviour.

I am new to Python, by the way. :)

class Animal:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return "This is " + self.name

    def __eq__(self, other):
        return self.name == other.name

ZOO = ['Lion', 'Bear', 'Parrot'] # list with many animals

ZOO_CLASSES = {}
for i in ZOO:
    ZOO_CLASSES[i] = type(i, (Animal,), dict(__init__=lambda self: Animal.__init__(self, i)))

# or even tried:
for i in ZOO:
    def constructor(self):
        Animal.__init__(self, i)
    ZOO_CLASSES[i] = type(i, (Animal,), dict(__init__=constructor))

# each time it creates a different function (as should be) but the value of i stays to the last one in the list:
# tests:
print(ZOO_CLASSES["Lion"]() == ZOO_CLASSES["Lion"]()) # True
print(ZOO_CLASSES["Lion"]() == ZOO_CLASSES["Bear"]()) # False
print(str(ZOO_CLASSES["Lion"]())) # This is Lion
print(str(ZOO_CLASSES["Bear"]())) # This is Bear
# i.e. all times we call these classes (their constructors) - i gets evaluated as "Parrot" (last element of ZOO list)

###################
# I don't want to do this (because imagine if the list ZOO is really long,
# manually typing it would be stupid):
class Lion(Animal):
    def __init__(self):
        Animal.__init__(self, "Lion")

class Bear(Animal):
    def __init__(self):
        Animal.__init__(self, "Bear")

class Parrot(Animal):
    def __init__(self):
        Animal.__init__(self, "Parrot")
Was it helpful?

Solution

The problem is that i variable inside the lambda is evaluated only when you create an instance of the class you've defined in a loop. At this time the loop would finish and i would be set to the last item in the list - Parrot.

You should pass i to the lambda:

ZOO_CLASSES[i] = type(i, 
                      (Animal,), 
                      dict(__init__=lambda self, i=i: Animal.__init__(self, i)))
                                                 ^

Demo:

>>> class Animal:
...     def __init__(self, name):
...         self.name = name
... 
>>> ZOO = ['Lion', 'Bear', 'Parrot']
>>> ZOO_CLASSES = {}

>>> for i in ZOO:
...     ZOO_CLASSES[i] = type(i, (Animal,), dict(__init__=lambda self: Animal.__init__(self, i)))
... 
>>> i
'Parrot'
>>> ZOO_CLASSES["Lion"]().name
'Parrot'

>>> for i in ZOO:
...     ZOO_CLASSES[i] = type(i, (Animal,), dict(__init__=lambda self, i=i: Animal.__init__(self, i)))
... 
>>> ZOO_CLASSES["Lion"]().name
'Lion'

Also, thanks to @BrenBarn's comment, see a better explanation here: Python lambda closure scoping.

Hope that helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top