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
How to Crop an Image in Opencv Using Python
Pyaudio Installation Error - 'Command 'Gcc' Failed with Exit Status 1'
How to Create a User in Linux Using Python
How to Make Python Script Press 'Enter' When Prompted on Shell
Where Does Python Tempfile Writes Its Files
Differencebetween C.Utf-8 and En_Us.Utf-8 Locales
Why am I Getting Socket.Gaierror: [Errno -2] from Python Httplib
How to Import Lldb in a Python Script
Frequency Counts for Unique Values in a Numpy Array
How to Read a (Static) File from Inside a Python Package
Get Local Network Interface Addresses Using Only Proc
Find the Oldest File (Recursively) in a Directory
"Valueerror: _Type_ 'V' Not Supported" Error After Installing Pyreadline
What Does --Enable-Optimizations Do While Compiling Python