Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.
from collections import namedtuple
import functools
import unittest


# The following example is contrived and over-simplified.


class Vehicle(object):

    def __init__(self, name, passengers):
        self.name = name
        self.passengers = passengers

    # This is how it's normally recommended to do alternate constructors.
    @classmethod
    def from_blueprint(cls, blueprint):
        '''
        Given a Blueprint instance, return a Vehicle instance.

        This is an alternate constructor. It ta
        '''
        name = blueprint.name

        # Pretend that getting "passengers" from "blueprint" is non-trivial
        # and it's code you would really like to be reused.
        passengers = blueprint.passengers

        # and/or pretend that there are 10 other attributes you need
        # to get from "blueprint" and pass to Vehicle.__init__

        return cls(name, passengers)



# Car is a Vehicle that adds two properties: "type" and "doors"
class Car(Vehicle):

    # Typical __init__ for a subclass. Nothing new here.
    def __init__(self, name, passengers, type, doors):
        # "type" could be "sports car" or "sedan"
        # "doors" could be "2" or "4"

        # Here's your typical call to the parent class __init__
        super(Car, self).__init__(name, passengers)
        self.type = type
        self.doors = doors

    @classmethod
    def from_blueprint(cls, blueprint):
        type = blueprint.type
        doors = blueprint.doors

        # This is where you run into issues. 
        # Call the parent constructor so that you can reuse its code.
        car = super(Car, cls).from_blueprint(blueprint)

        # OOPS! That super call will call Vehicle.from_blueprint(Car, blueprint)
        #       which will end up calling Car.__init__.
        #       But we've changed Car.__init__'s arguments,
        #       and Vehicle.from_blueprint doesn't know that,
        #       so Vehicle.from_something becomes unusable.




# This is a pattern I'm experimenting with.
# The previous example is an alternate constructor,
# while this is more of an alternate _initializer_.


# This is the "meat" of this code.
#
# This code is not fully implemented, it's just enough to convey
# the idea of the pattern.
class alt_init(object):
    '''
    Make "function" an alternate initializer.

    This is a descriptor that wraps "function".

    class C(object):
        @alt_init
        def from_foo(self, arg1, arg2, ...):
            ...

    When C.from_foo(...) is called, this descriptor will create
    an instance of C using C.__new__ and pass it to C.from_foo,
    which can then initialize the instance, as it would normally
    in __init__.
    '''

    def __init__(self, function):
        self.function = function

    def __get__(self, instance, owner):
        # If this is being called on a class (not an instance)
        # create an instance of the class.
        if not instance:
            instance = owner.__new__(owner)

        # Return a wrapped function that passes the instance to self.function
        # as the first argument.
        @functools.wraps(self.function)
        def wrapped(*args, **kwargs):
            self.function(instance, *args, **kwargs)
            return instance
        return wrapped


# This is a Vehicle that uses alt_init instead of classmethod.
class AltVehicle(object):

    def __init__(self, name, passengers):
        self.name = name
        self.passengers = passengers

    # The important thing to notice here is the method signature:
    # this receives an instance as the first argument, instead of a class.
    @alt_init
    def from_blueprint(self, blueprint):
        '''
        Given a Blueprint instance, initialize this AltVehicle instance.
        '''
        name = blueprint.name

        # Pretend that getting "passengers" from "blueprint" is non-trivial
        # and it's code you would really like to be reused.
        passengers = blueprint.passengers

        # and/or pretend that there are 10 other attributes you need
        # to get from "blueprint" and pass to AltVehicle.__init__.
        # I'm leaving that out for brevity.

        # Notice that here we're not calling cls(arg1, arg2, ...)
        # and we're not returning anything.
        AltVehicle.__init__(self, name, passengers)



class AltCar(AltVehicle):

    # Again, typical __init__, nothing new
    def __init__(self, name, passengers, type, doors):
        super(AltCar, self).__init__(name, passengers)
        self.type = type
        self.doors = doors

    @alt_init
    def from_blueprint(self, blueprint):
        self.type = blueprint.type
        self.doors = blueprint.doors

        # In this pattern, it's easier to call the parent's
        # alternate initializer, AltVehicle.from_blueprint
        super(AltCar, self).from_blueprint(blueprint)



# Downsides:
# - there's more duplication between __init__ and alternate initializers


class TestCase(unittest.TestCase):

    def setUp(self):
        self.Blueprint = namedtuple('Blueprint', 'name passengers type doors')
        self.blueprint = self.Blueprint('name', 2, 'car', 4)

    def test_Vehicle(self):
        # All is well so far
        a = Vehicle('name', 2)
        self.assertEqual(a.name, 'name')
        self.assertEqual(a.passengers, 2)

        b = Vehicle.from_blueprint(self.blueprint)
        self.assertEqual(b.name, 'name')
        self.assertEqual(b.passengers, 2)

    def test_Car(self):
        a = Car('name', 2, 'car', 4)
        self.assertEqual(a.name, 'name')
        self.assertEqual(a.passengers, 2)
        self.assertEqual(a.type, 'car')
        self.assertEqual(a.doors, 4)

        with self.assertRaises(TypeError):
            Car.from_blueprint(self.blueprint)

    def test_AltVehicle(self):
        a = AltVehicle('name', 2)
        self.assertEqual(a.name, 'name')
        self.assertEqual(a.passengers, 2)

        b = AltVehicle.from_blueprint(self.blueprint)
        self.assertEqual(b.name, 'name')
        self.assertEqual(b.passengers, 2)

    def test_AltCar(self):
        a = AltCar('name', 2, 'car', 4)
        self.assertEqual(a.name, 'name')
        self.assertEqual(a.passengers, 2)
        self.assertEqual(a.type, 'car')
        self.assertEqual(a.doors, 4)

        b = AltCar.from_blueprint(self.blueprint)
        self.assertEqual(b.name, 'name')
        self.assertEqual(b.passengers, 2)
        self.assertEqual(b.type, 'car')
        self.assertEqual(b.doors, 4)


if __name__ == '__main__':
    unittest.main()
share|improve this question
    
Do you have a use case? It would be worth describing the concrete problem you are solving. The level of abstraction makes it hard to understand. –  Gareth Rees Dec 9 '13 at 23:35
    
Could you explain your idea? (and put that in docstrings while you are at it) –  Janne Karila Dec 10 '13 at 6:48
    
I've attempted to make the example clearer. I can try again to explain things more if needed. Thanks! –  Alex Buchanan Dec 10 '13 at 19:33
1  
Is the vehicle/blueprint example your real problem? It still seems to involve a lot of "pretending". –  Gareth Rees Dec 10 '13 at 19:38
    
It's not my real problem, but I believe it represents it more simply. Anyway, I'll add a section describing my real world use case. –  Alex Buchanan Dec 10 '13 at 19:49
show 2 more comments

closed as unclear what you're asking by 200_success, Simon André Forsberg, Jeff Vanzella, Mat's Mug, amon Dec 11 '13 at 14:38

Please clarify your specific problem or add additional details to highlight exactly what you need. As it's currently written, it’s hard to tell exactly what you're asking. See the How to Ask page for help clarifying this question.If this question can be reworded to fit the rules in the help center, please edit the question.