In Java, you can use the builder pattern to provide a more readable means to instantiating a class with many parameters. In the builder pattern, one constructs a configurati
I have just come across the need for building this pattern and stumbled across this question. I realize how old this question is but might as well add my version of the builder pattern in case it is useful to other people.
I believe using a decorator to specify builder classes is the most ergonomic way to implement the builder pattern in python.
def buildermethod(func):
def wrapper(self, *args, **kwargs):
func(self, *args, **kwargs)
return self
return wrapper
class A:
def __init__(self):
self.x = 0
self.y = 0
@buildermethod
def set_x(self, x):
self.x = x
@buildermethod
def set_y(self, y):
self.y = y
a = A().set_x(1).set_y(2)
I disagree with @MechanicalSnail. I think a builder implementation similar to one referenced by the poster is still very useful in some cases. Named parameters will only allow you to simply set member variables. If you want to do something slightly more complicated, you're out of luck. In my example I use the classic builder pattern to create an array.
class Row_Builder(object):
def __init__(self):
self.row = ['' for i in range(170)]
def with_fy(self, fiscal_year):
self.row[FISCAL_YEAR] = fiscal_year
return self
def with_id(self, batch_id):
self.row[BATCH_ID] = batch_id
return self
def build(self):
return self.row
Using it:
row_FY13_888 = Row_Builder().with_fy('FY13').with_id('888').build()
The OP set themselves up for a fall by casting the Builder pattern as Java specific. It's not. It's in the Gang of Four's book and is potentially relevant to any object oriented language.
Unfortunately, even the Wikipedia article on the Builder pattern doesn't give it enough credit. It's not simply useful for code elegance. Builder patterns are a great way to create immutable objects that need to be mutable until they're used. Immutable state is especially critical in functional paradigms, making the Builder an excellent object-oriented pattern for python.
I've provided an an example Builder + ImmutableObject implementation below using the collections.namedtuple, borrowed and modified from "How to make an immutable object in python". I've kept the Builder fairly simple. However, setter functions could be provided that return the Builder itself to allow call chaining. Or @property syntax could be used in the Builder to provide attribute setters that check attribute validity prior to setting.
from collections import namedtuple
IMMUTABLE_OBJECT_FIELDS = ['required_function_result', 'required_parameter', 'default_parameter']
class ImmutableObjectBuilder(object):
def __init__(self, required_function, required_parameter, default_parameter="foo"):
self.required_function = required_function
self.required_parameter = required_parameter
self.default_parameter = default_parameter
def build(self):
return ImmutableObject(self.required_function(self.required_parameter),
self.required_parameter,
self.default_parameter)
class ImmutableObject(namedtuple('ImmutableObject', IMMUTABLE_OBJECT_FIELDS)):
__slots__ = ()
@property
def foo_property(self):
return self.required_function_result + self.required_parameter
def foo_function(self):
return self.required_function_result - self.required_parameter
def __str__(self):
return str(self.__dict__)
Example usage:
my_builder = ImmutableObjectBuilder(lambda x: x+1, 2)
obj1 = my_builder.build()
my_builder.default_parameter = "bar"
my_builder.required_parameter = 1
obj2 = my_builder.build()
my_builder.required_function = lambda x: x-1
obj3 = my_builder.build()
print obj1
# prints "OrderedDict([('required_function_result', 3), ('required_parameter', 2), ('default_parameter', 'foo')])"
print obj1.required_function_result
# prints 3
print obj1.foo_property
# prints 5
print obj1.foo_function()
# prints 1
print obj2
# prints "OrderedDict([('required_function_result', 2), ('required_parameter', 1), ('default_parameter', 'bar')])"
print obj3
# prints "OrderedDict([('required_function_result', 0), ('required_parameter', 1), ('default_parameter', 'bar')])"
In this example, I created three ImmutableObjects, all with different parameters. I've given the caller the ability to copy, modify, and pass around a mutable configuration in the form of the builder while still guaranteeing immutability of the built objects. Setting and deleting attributes on the ImmutableObjects will raise errors.
Bottom line: Builders are a great way to pass around something with mutable state that provides an object with immutable state when you're ready to use it. Or, stated differently, Builders are a great way to provide attribute setters while still ensuring immutable state. This is especially valuable in functional paradigms.
The builder pattern in Java can easily be achieved in python by using a variant of:
MyClass(self, required=True, someNumber=<default>, *args, **kwargs)
where required
and someNumber
are an example to show required params with a default value and then reading for variable arguments while handling the case where there might be None
In case you have not used variable arguments before, refer this
Design patterns can often be replaced with built-in language features.
You say "I wanted to have a more readable "means" to instantiating a class with many parameters.". In Java's case:
[A] use case for the builder pattern is when the constructor of the object to be built must take very many parameters. In such cases, it is often more convenient to lump such configuration parameters in a builder object (setMaxTemperature(int t), setMinTemperature(int t), set.. , etc. ) than to burden the caller with a long list of arguments to pass in the class's constructor..
But Python supports named parameters, so this is not necessary. You can just define a class's constructor:
class SomeClass(object):
def __init__(self, foo="default foo", bar="default bar", baz="default baz"):
# do something
and call it using named parameters:
s = SomeClass(bar=1, foo=0)
Note that you can freely reorder and omit arguments, just as with a builder in Java you can omit or reorder calls to the set
methods on the builder object.
Also worth stating is that Python's dynamic nature gives you more freedom over construction of objects (using __new__
etc.), which can replace other uses of the builder pattern.
you can use collections.namedtuple as your config object. namedtuple()
returns a new type representing a tuple, each of whose parameters has a given name, without having to write a boilerplate class. You can use objects of the resulting type in a similar way to Java builders. (Thanks to Paul McGuire for suggesting this.)
StringBuilder
A related pattern is Java's StringBuilder, which is used to efficiently construct an (immutable) String
in stages. In Python, this can be replaced with str.join
. For example:
final StringBuilder sb = new StringBuilder();
for(int i = 0; i < 100; i++)
sb.append("Hello(" + i + ")");
return sb.toString();
can be replaced with
return "".join("Hello({})".format(i) for i in range(100))
builder and constructor are not the same thing, builder is a concept, constructor is a programming syntax. There is no point to compare the two.
So sure you can implement the builder pattern with a constructor, a class method or a specialized class, there is no conflict, use whichever one suit your case.
Conceptually, builder pattern decouple the building process from the final object. Take a real world example of building a house. A builder may use a lot of tools and materials to build a house, but the final house need not have those tools and excess materials lying around after it is build.
Example:
woodboards = Stores.buy(100)
bricks = Stores.buy(200)
drills = BuilderOffice.borrow(4)
house = HouseBuilder.drills(drills).woodboards(woodboards).bricks(bricks).build()