What Is a Clean "Pythonic" Way to Implement Multiple Constructors

What is a clean pythonic way to implement multiple constructors?

Actually None is much better for "magic" values:

class Cheese():
def __init__(self, num_holes = None):
if num_holes is None:
...

Now if you want complete freedom of adding more parameters:

class Cheese():
def __init__(self, *args, **kwargs):
#args -- tuple of anonymous arguments
#kwargs -- dictionary of named arguments
self.num_holes = kwargs.get('num_holes',random_holes())

To better explain the concept of *args and **kwargs (you can actually change these names):

def f(*args, **kwargs):
print 'args: ', args, ' kwargs: ', kwargs

>>> f('a')
args: ('a',) kwargs: {}
>>> f(ar='a')
args: () kwargs: {'ar': 'a'}
>>> f(1,2,param=3)
args: (1, 2) kwargs: {'param': 3}

http://docs.python.org/reference/expressions.html#calls

Multiple constructors: the Pythonic way?

You can't have multiple methods with same name in Python. Function overloading - unlike in Java - isn't supported.

Use default parameters or **kwargs and *args arguments.

You can make static methods or class methods with the @staticmethod or @classmethod decorator to return an instance of your class, or to add other constructors.

I advise you to do:

class F:

def __init__(self, timestamp=0, data=None, metadata=None):
self.timestamp = timestamp
self.data = list() if data is None else data
self.metadata = dict() if metadata is None else metadata

@classmethod
def from_file(cls, path):
_file = cls.get_file(path)
timestamp = _file.get_timestamp()
data = _file.get_data()
metadata = _file.get_metadata()
return cls(timestamp, data, metadata)

@classmethod
def from_metadata(cls, timestamp, data, metadata):
return cls(timestamp, data, metadata)

@staticmethod
def get_file(path):
# ...
pass

⚠ Never have mutable types as defaults in python. ⚠
See here.

How to implement multiple constructors in python?

Alternate constructors are the most common use case for class methods. Your "real" __init__ is often then the lowest common denominator for the various class methods.

class Triangle(object):
def __init__(self, v1, v2, v3):
self.v1 = v1
self.v2 = v2
self.v3 = v3

# This is just here to demonstrate, since it is just
# wrapping the built-in __new__ for no good reason.
@classmethod
def by_vertices(cls, vertices):
# Make sure there are exactly three vertices, though :)
return cls(*vertices)

@staticmethod
def sidesToVertices(sides):
# converts sides to vertices
return v1, v2, v3

@classmethod
def by_sides(cls, sides):
return cls(*sides_to_vertices(sides))

def area(self):
# calculate the are using vertices
...
return result

The, to get an instance of Triangle, you can write any of the following:

t = Triangle(p1, p2, p3)
t = Triangle.by_vertices(p1, p2, p3) # Same as the "direct" method
t = Triangle.by_sides(s1, s2, s3)

The only difference here is that Triangle(p1, p2, p3) hides the implicit call to Triangle.__new__, which is a class method just like by_vertices and by_sides. (In fact, you could simply define by_vertices = __new__.) In all three cases, Python implicitly calls Triangle.__init__ on whatever cls returns.

(Note that by_vertices generates a specific triangle, while by_sides could generate any number of "equivalent" triangles that differ only by position and rotation relative to the origin. Conversely, by_sides could be thought to generate the "real" triangle, with by_vertices specifying a triangle in a particular position. None of this is particularly relevant to the question at hand.)


Tangential background.

t = Triangle(v1, v2, v3) is the "normal" method, but what does this mean? Triangle is a class, not a function. To answer this, you need to know about metaclasses and the __call__ method.

__call__ is used to make an instance callable. my_obj(args) becomes syntactic sugar for my_obj.__call__(args), which itself, like all instance methods, is syntactic sugar (to a first approximation) for type(my_obj).__call__(my_obj, args).

You've heard that everything in Python is an object. This is true for classes as well; every class object is an instance of its metaclass, the
type of types. The default is type, so Triangle(v1, v2, v3) would desugar to Triangle.__call__(v1, v2, v3) or type.__call__(Triangle, v1, v2, v3).

With that out of the way, what does type.__call__ do? Not much. It just calls the appropriate class method __new__ for its first argument. That is, type.__call__(Triangle, v1, v2, v3) == Triangle.__new__(v1, v2, v3). Python then implicitly calls __init__ on the return value of Triangle.__new__ if it is, in fact, an instance of Triangle. Thus, you can think of type.__call__ as being defined like

def __call__(cls, *args, **kwargs):
obj = cls.__new__(*args, **kwargs)
if isinstance(obj, cls):
cls.__init__(obj, *args, **kwargs)
return obj

What is a clean, Pythonic way of setting multiple members in a constuctor from one method in Python?

This boils down to stylistic preferences. I personally don't have qualms with having some of the attributes defined outside of __init__(...), and so would opt for something like:

class TriangleData():

def __init__(self, contour_data: np.ndarray):
self.get_triangle_data(contour_data)

def get_triangle_data(self, contour_data):
# calculations
self.vertices = vertices_final
self.triangle_area = triangle_area
self.contours = contours

and disable any linter warnings that didn't like it. You could also opt for something like:

class TriangleData():

def __init__(self, contour_data: np.ndarray):
self.vertices, self.triangle_area, self.contours = self.get_triangle_data(contour_data)

def get_triangle_data(self, contour_data):
# calculations
return vertices_final, triangle_area, contours

which I think should satify the linter as-is.

Can I use two constructor methods in Python?

In Python you wouldn't overload the constructor, but give the name argument a default value:

class Person:
def __init__(self, name="No Name"):
self.name = name

Is it not possible to define multiple constructors in Python?

Unlike Java, you cannot define multiple constructors. However, you can define a default value if one is not passed.

def __init__(self, city="Berlin"):
self.city = city

Best practice to implement multiple constructors in python

UPDATE:

The more pythonic way of doing multiple constructors is with @classmethod, as suggested by Jim. Raymond Hettinger did a talk on Python's class development toolkit at Pycon 2013, where he talked about multiple constructors using @classmethod.

class Line:
def __init__(self, a, b, noSlope):
self.a = a
self.b = b
self.noSlope = noSlope

@classmethod
def fromPoints(cls, point1, point2):
deltaX = point2[0] - point1[0]
deltaY = point2[1] - point1[1]
if deltaX == 0:
return cls(point1[0], 0, True)
else:
a = deltaY / deltaX
b = point1[1] - a * point1[0]
return cls(a, b, False)

@classmethod
def fromVector(cls, vector, point):
if vector[0] == 0:
return cls(point1[0], 0, True)
else:
a = vector[1] / vector[0]
b = point1[1] - a * point1[0]
return cls(a, b, False)

line = Line.fromPoints((0,0), (1,1))

Similar to self, the cls parameter for the @classmethod functions is implicitly passed as the calling class (in the example above, it would be Line). This is used to accommodate future subclasses using the additional constructors; it removes potential bugs from accidentally bypassing a subclass's implementation of a constructor by hard coding the base class in place of cls.


ORIGINAL POST:

If you want to enforce the use of your constructors, you can make them static methods, and have them return an instance of your class.

class line:
def __init__(self, a, b, noSlope):
self.a = a
self.b = b
self.noSlope = noSlope

@staticmethod
def lineFromPoints(point1, point2):
deltaX = point2[0] - point1[0]
deltaY = point2[1] - point1[1]
if deltaX == 0:
return line(point1[0], 0, True)
else:
a = deltaY / deltaX
b = point1[1] - a * point1[0]
return line(a, b, False)

@staticmethod
def lineFromVector(vector, point):
if vector[0] == 0:
return line(point1[0], 0, True)
else:
a = vector[1] / vector[0]
b = point1[1] - a * point1[0]
return line(a, b, False)

# Create instance of class
myLine = line.lineFromPoints((0,0), (1,1))

EDIT:

If you want to force use of your constructors over the use of Line.__init__, you can use the following factory to hide direct instantiation of the Line class:

class LineFactory:
class Line:
def __init__(self, a, b, noSlope):
self.a = a
self.b = b
self.noSlope = noSlope

@staticmethod
def fromPoints(point1, point2):
deltaX = point2[0] - point1[0]
deltaY = point2[1] - point1[1]
if deltaX == 0:
return LineFactory.Line(point1[0], 0, True)
else:
a = deltaY / deltaX
b = point1[1] - a * point1[0]
return LineFactory.Line(a, b, False)

@staticmethod
def fromVector(vector, point):
if vector[0] == 0:
return LineFactory.Line(point1[0], 0, True)
else:
a = vector[1] / vector[0]
b = point1[1] - a * point1[0]
return LineFactory.Line(a, b, False)

# Create line
line = LineFactory.fromPoints((0,0), (1,1))

more than one constructor in a python class

There is no such thing as method overloading in python. Hence, it is impossible to have multiple constructors as you desire. You can however make the arguments optional.

class History():
def __init__(self, input_dir=None, output_file=None, temp_file=None):
self._input_dir = input_dir
self._output_file = output_file
self.temp_file = temp_file

def identify_ID(self):
'''Identifies the ID'''

This will alow any combination of your arguments to work. This would leave the method you are calling up to making sense of the instance's variables.



Related Topics



Leave a reply



Submit