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()
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.
|
|||||||||||||||||||||
closed as unclear what you're asking by 200_success♦, Simon André Forsberg, Jeff Vanzella, Mat's Mug, amon Dec 11 '13 at 14:38Please 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. |
|||||||||||||||||||||
|