Frage

I have the following problem that I will attempt to illustrate with the following example.

class Brick():
    def __init__(self):
        self.weight = 1

class House():
    def __init__(self, number_bricks):
        self.bricks = [Brick() for i in range(number_bricks)]

    def get_weight(self):
        return reduce(lambda x,y: x+y, [brick.weight for brick in self.bricks])

But now suppose I create a new kind of Brick, StrongBrick, so that I make a house, a subclass StrongHouse, where StrongBrick plays exactly the same role in StrongHouse as Brick plays in House. How can I do this in a nice way (not just retyping all the class definitions)?

So the basic idea is, how can I change a class which is composed of some objects to the same class but composed of say a subclass of the original member objects?

Thanks very much for any help you can give me.

War es hilfreich?

Lösung

You could have a factory (a brickyard?) and pass that to House.__init__().

class Brick(object): pass

class StrongBrick(Brick): pass

class House(object):
    def __init__(self, brick_factory, num_bricks):
        self.bricks = [brick_factory() for i in range(num_bricks)]

house = House(Brick, 10000)
strong_house = House(StrongBrick, 10000)

As you can see, subclassing House isn't even necessary to be able to construct houses from different types of bricks.

Andere Tipps

There are various ways to do this. You could make the relevant Brick class an attribute of the House class:

class House(object):
    brick_class = Brick

    def __init__(self, number_bricks):
        self.bricks = [self.brick_class() for i in range(number_bricks)]


class StrongHouse(House):
    brick_class = StrongBrick

Or, you could pass in the Brick class you want to use when constructing the House:

class House(object):

    def __init__(self, brick_class, number_bricks):
        self.bricks = [brick_class() for i in range(number_bricks)]

One nice pattern could be this:

class Brick(object):
    weight = 1

class StrongBrick(Brick):
    weight = 42

class House(object):
    brick_type = Brick

    def __init__(self, number_bricks):
        self.bricks = [self.brick_type() for i in range(number_bricks)]

    def get_weight(self):
        return reduce(lambda x, y: x + y, [brick.weight for brick in self.bricks])

class StrongHouse(House):
    brick_type = StrongBrick

Another is to make a function making a factory, and using an argument for the brick_type with default value:

class House(object):
    def __init__(self, number_bricks, brick_type=Brick):
        self.bricks = [brick_type() for i in range(number_bricks)]

    def get_weight(self):
        return reduce(lambda x, y: x + y, [brick.weight for brick in self.bricks])

def make_house_factory(brick_type):
    def factory(number_bricks):
        return House(number_bricks, brick_type)

    return factory

StrongHouse = make_house_factory(StrongBrick)

Of course all such objects would be instances of the House only, even though I named StrongHouse here so that it resembles a class name.

But now suppose I create a new kind of Brick, StrongBrick, so that I make a house, a subclass StrongHouse, where StrongBrick plays exactly the same role in StrongHouse as Brick plays in House. How can I do this in a nice way (not just retyping all the class definitions)?

As all of the other answers have explained, you really don't want to create this parallel hierarchy at all. But to answer your direct question: You can create classes dynamically, so you can create a parallel hierarchy without copying and pasting all the class definitions. Classes are, after all, first-class objects.

Again, let me stress that you almost certainly don't want to do this, and I'm just showing that it is possible.

def make_house_class(brick_type):
    class NewHouse(House):
        def __init__(self, number_bricks):
            self.bricks = [brick_type() for i in range(number_bricks)]
    return NewHouse

Now, you could statically create all the house types:

StrongHouse = make_house_class(StrongBrick)
CheapHouse = make_house_class(CheapHouse)
# ...

… or maybe build them dynamically from a collection of all of your brick type:

brick_types = (StrongBrick, CheapBrick)
house_types = {brick_type: make_house_class(brick_type) for brick_type in brick_types}

… or even add some hacky introspection to just create a new FooHouse type for every FooBrick type in the current module:

for name, value in globals().items():
    if name.endswith('Brick') and name != 'Brick' and isinstance(value, type):
        globals()[name.replace('Brick', 'House')] = make_house_class(value)

… or even create them on the fly as needed in the factory-maker:

def make_house_factory(brick_type):
    house_type = make_house_class(brick_type)
    def factory(number_bricks):
        return house_type(number_bricks, brick_type)    
    return factory

… or even the generated factory:

def make_house_factory(brick_type):
    def factory(number_bricks):
        return make_house_class(brick_type)(number_bricks, brick_type)
    return factory

Add a parameter to the House.__init__ so that you can specify the Brick type:

import functools
class Brick():
    def __init__(self):
        self.weight = 1

class StrongBrick():
    def __init__(self):
        self.weight = 10

class House():
    def __init__(self, number_bricks,brick_type=Brick):
        self.bricks = [brick_type() for i in range(number_bricks)]

    def get_weight(self):
        return reduce(lambda x,y: x+y, [brick.weight for brick in self.bricks])

#not a new class, but an alias with a different default brick_type
StrongHouse = functools.partial(House,brick_type=StrongBrick) 
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top