What is a clean "pythonic" way to implement multiple constructors?

412,458

Solution 1

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

Solution 2

Using num_holes=None as the default is fine if you are going to have just __init__.

If you want multiple, independent "constructors", you can provide these as class methods. These are usually called factory methods. In this case you could have the default for num_holes be 0.

class Cheese(object):
    def __init__(self, num_holes=0):
        "defaults to a solid cheese"
        self.number_of_holes = num_holes

    @classmethod
    def random(cls):
        return cls(randint(0, 100))

    @classmethod
    def slightly_holey(cls):
        return cls(randint(0, 33))

    @classmethod
    def very_holey(cls):
        return cls(randint(66, 100))

Now create object like this:

gouda = Cheese()
emmentaler = Cheese.random()
leerdammer = Cheese.slightly_holey()

Solution 3

One should definitely prefer the solutions already posted, but since no one mentioned this solution yet, I think it is worth mentioning for completeness.

The @classmethod approach can be modified to provide an alternative constructor which does not invoke the default constructor (__init__). Instead, an instance is created using __new__.

This could be used if the type of initialization cannot be selected based on the type of the constructor argument, and the constructors do not share code.

Example:

class MyClass(set):

    def __init__(self, filename):
        self._value = load_from_file(filename)

    @classmethod
    def from_somewhere(cls, somename):
        obj = cls.__new__(cls)  # Does not call __init__
        super(MyClass, obj).__init__()  # Don't forget to call any polymorphic base class initializers
        obj._value = load_from_somewhere(somename)
        return obj

Solution 4

All of these answers are excellent if you want to use optional parameters, but another Pythonic possibility is to use a classmethod to generate a factory-style pseudo-constructor:

def __init__(self, num_holes):

  # do stuff with the number

@classmethod
def fromRandom(cls):

  return cls( # some-random-number )

Solution 5

Why do you think your solution is "clunky"? Personally I would prefer one constructor with default values over multiple overloaded constructors in situations like yours (Python does not support method overloading anyway):

def __init__(self, num_holes=None):
    if num_holes is None:
        # Construct a gouda
    else:
        # custom cheese
    # common initialization

For really complex cases with lots of different constructors, it might be cleaner to use different factory functions instead:

@classmethod
def create_gouda(cls):
    c = Cheese()
    # ...
    return c

@classmethod
def create_cheddar(cls):
    # ...

In your cheese example you might want to use a Gouda subclass of Cheese though...

Share:
412,458
winsmith
Author by

winsmith

omsn

Updated on May 07, 2022

Comments

  • winsmith
    winsmith about 2 years

    I can't find a definitive answer for this. As far as I know, you can't have multiple __init__ functions in a Python class. So how do I solve this problem?

    Suppose I have a class called Cheese with the number_of_holes property. How can I have two ways of creating cheese objects...

    1. One that takes a number of holes like this: parmesan = Cheese(num_holes = 15).
    2. And one that takes no arguments and just randomizes the number_of_holes property: gouda = Cheese().

    I can think of only one way to do this, but this seems clunky:

    class Cheese():
        def __init__(self, num_holes = 0):
            if (num_holes == 0):
                # Randomize number_of_holes
            else:
                number_of_holes = num_holes
    

    What do you say? Is there another way?

  • Yes - that Jake.
    Yes - that Jake. over 15 years
    This is the sort of code that gives me nightmares about working in dynamic languages--not to say that there's anything inherently wrong with it, only that it violates some key assumptions I would make about a class.
  • rmbianchi
    rmbianchi over 12 years
    +1 for the nice example of @classmethod. But as answer to the original question I prefer the accepted solution, because in my opinion it is more in the direction of having multiple constructors (or overloading them, in other languages).
  • Ethan Furman
    Ethan Furman over 12 years
    @rmbianchi: The accepted answer may be more in line with other languages, but it is also less pythonic: @classmethods are the pythonic way of implementing multiple contstructors.
  • Ber
    Ber over 12 years
    @EthanFurman: I like your cheese and thanks for the improved code. This is really better, using the __init__() constructor directly.
  • Brian Peterson
    Brian Peterson about 11 years
    Ingenious. So the class methods are static?
  • Ber
    Ber about 11 years
    @Bepetersn There are instance methods (the normal ones), which have an instance object referenced as self. Then there are class methods (using @classmethod) which have a reference to the class object as cls. An finally there are static methods (declared with @staticmethod) which have neither of those references. Static methods are just like functions at module level, except they live in the class' name space.
  • 2rs2ts
    2rs2ts about 11 years
    I tend to opt for this approach because it is remarkably similar to the Factory Method pattern.
  • mach
    mach almost 10 years
    An advantage of this method over the accepted solution is that it easily allows to specify abstract constructors and enforce implementation of them, especially with python 3 in which the usage of @abstractmethod and @classmethod on the same factory function is possible and is built into the language. I would also argue that this approach is more explicit, which goes with The Zen of Python.
  • Reti43
    Reti43 over 9 years
    @javawizard Would it be easy to explain in a comment what makes it non thread-safe, or give a pointer so I can read about it somewhere else?
  • javawizard
    javawizard over 9 years
    @Reti43 Say two threads try to create cheeses at the same time, one with Cheese(0) and one with Cheese(1). It's possible that thread 1 might run cls.__init__ = cls.foomethod, but then thread 2 might run cls.__init__ = cls.barmethod before thread 1 gets any further. Both threads will then end up calling barmethod, which isn't what you want.
  • Regis May
    Regis May about 8 years
    That means: Instead of simply implementing one constructor for every specific construction required I need to implement a class method. This method then redirects to a single constructor where I then need to have a great variety of ifs to distinguish between the different ways of construction. One of the commentators wrote: "Simply beautiful." Sorry, but I really don't know if this approach is more beautiful than having multiple constructors.
  • Ber
    Ber about 8 years
    @RegisMay don't forget that in Python, there is only one __init__(self) method per class, so you cannot have more than one constructor in a class.
  • Regis May
    Regis May about 8 years
    That is exactly what I am taking about: Because of that a more complex implementation is required.
  • tleb
    tleb almost 8 years
    For those interested, kwargs stands for keyword arguments (seems logic once you know it). :)
  • Rafael Zayas
    Rafael Zayas over 6 years
    A great example of this is in GeoPandas: github.com/geopandas/geopandas/blob/master/geopandas/…
  • Alexey
    Alexey over 6 years
    This is the solution that indeed provides independent constructors instead of fiddling with __init__'s arguments. However, could you provide some references, please, that this method is somehow officially approved or supported? How safe and reliable is it to call directly __new__ method?
  • user989762
    user989762 about 6 years
    There are moments that *args and **kwargs are an overkill. At most constructors, you want to know what your arguments are.
  • smci
    smci almost 6 years
    make_gouda, make_parmesan should be classmethods of class Cheese
  • smci
    smci almost 6 years
    No. This is utterly unPythonic, it's like Java masquerading behind Python syntax. You want one single __init__ method, and the other class methods either call it as-is (cleanest) or handle special initialization actions via any helper classmethods and setters you need (ideally none).
  • Alexey
    Alexey almost 6 years
    I do not want a single __init__ method when I have multiple constructors with different initialisation routines. I do not see why someone would want it. "the other class methods either call it as-is" -- call what? The __init__ method? That would be strange to call __init__ explicitely IMO.
  • smci
    smci almost 6 years
    Alexey, it is utterly unPythonic to have multiple constructors, as in multiple _init... methods (see other answers on this question.) Worse still, in this case you don't even need to: you haven't shown how the code for _init_parmesan, _init_gouda differ, so there is zero reason not to common-case them. Anyway, the Pythonic way to do that is to supply non-default args to *args or **kwargs (e.g. Cheese(..., type='gouda'...), or if that can't handle everything, put the general code in __init__ and the less-commonly-used code in a classmethod make_whatever... and have it cal setters
  • Alexey
    Alexey almost 6 years
    "it is utterly unPythonic to have multiple constructors" -- the original question is still "What is a clean, pythonic way to have multiple constructors in Python?". I only showed how to have them, not why i would want them.
  • Alexey
    Alexey almost 6 years
    As to the question why i would want them, well, even namedtuple-produced classes have two constructors: the default one and _make. There are other examples when inheriting from built-in classes where using multiple constructors would be the only option, because despatching inside __init__ would not be an option.
  • Alexey
    Alexey almost 6 years
    Even when multiple initialisation routines can be achieved with the single default constructor by some (possibly awkward) dispatch inside __init__, if the routines are completely independent, i will call them _init_from_foo, _init_from_bar, etc, and call them from __init__ after dispatching by isinstance or by other tests.
  • Alexey
    Alexey almost 6 years
    @smci, I still do not understand your original comment: did you suggest to call __init__ explicitly from some class methods? (That would be a bit strange IMO.)
  • smci
    smci almost 6 years
    No. The standard Python way for about a decade is for the non-default make_... or from_... methods to be classmethods which call Cheese(), and thus implicitly its __init__ method, possibly with non-default args. They then return the new Cheese() instance, modified as necessary. This is what I've been saying.
  • Alexey
    Alexey almost 6 years
    @smci, I think i understand your point now, thanks for the explanation. I see a certain uniformity in such approach. I am not convinced yet though that the restriction of having to always pass through the default constructor (FooClass(...)) is not artificial. In particular, i wonder if Python classes realised in C (like NumPy classes) respect that restriction.
  • smci
    smci almost 6 years
    no idea about classes implemented in C, you might ask that as a separate question. I imagine there's a nuance for wrapped C classes that can also be directly instantiated from C without thunking back in and out of Python.
  • piedpiper
    piedpiper almost 6 years
    In the init method defined above, it is clear that it is held in 'number_of_holes' -- not so for the @classmethod 'constructors'. Which data member holds the number of holes? .
  • rools
    rools almost 6 years
    Factory functions use cls: use cls instead of Cheese. If not, what is the point of using class methods instead of static methods?
  • Ber
    Ber almost 6 years
    @ashu The other constructors call the __init__() method by instantiating the class via cls(...). Therefore, the number_of_holes is always used in the same way.
  • Mike O'Connor
    Mike O'Connor over 4 years
    I have used this effectively but with classes of my own instead of Python types. Given __init__(self, obj) I test inside __init__ with if str(obj.__class__.__name__) == 'NameOfMyClass': ... elif etc..
  • Neil G
    Neil G over 4 years
    I did things this way and then came here to ask the above question to see if my way was right. You still need to call super otherwise this won't work in cooperative multiple inheritance, so I added the line to your answer.
  • AGML
    AGML over 4 years
    This answer was very helpful but I refuse to upvote past 666
  • Captain Jack Sparrow
    Captain Jack Sparrow about 4 years
    @user989762 Yes! For sure!
  • Nathaniel Jones
    Nathaniel Jones about 4 years
    @RegisMay (1/2) Rather than having a bunch of ifs in __init__(), the trick is to have each of the unique factory methods handle their own unique aspects of initialization, and have __init__() accept only the fundamental pieces of data that define an instance. For example, Cheese might have attributes volume and average_hole_radius in addition to number_of_holes. __init__() would accept these three values. Then you could have a class method with_density() that randomly chooses the fundamental attributes to match a given density, subsequently passing them on to __init__().
  • Nathaniel Jones
    Nathaniel Jones about 4 years
    (2/2) Python also lets us make properties, attributes which are resolved when accessed. So you could likewise have a density property that's calculated from the fundamental attributes. The result is separation of concerns by having __init__() handle the raw data while any number of other factory methods/properties are permitted to handle various usage scenarios. I imagine this is why some might be commenting, "Simply beautiful."
  • Regis May
    Regis May about 4 years
    @NathanielJones Yes, I know, that's the detour you have to take. And find an individual name for every single way of construction though all the factory methods do exactly the same. Which is not "simply beautiful". Especially as I can not force the user to use the factory methods only. It might be something - but not "simply beautiful".
  • GlenRSmith
    GlenRSmith almost 4 years
    @user989762 Yeah, this approach is not self-documenting at all (how many times have you tried to use a library and tried to intuited the usage from method signatures only to discover you have to do a code dive to see what arguments are expected/allowed?) Moreover, now your implementation takes on the added burden of argument checking, including the choice of whether to accept or except (teehee) unsupported arguments.
  • chepner
    chepner almost 4 years
    Indeed, there is no reason to modify the definition of the class just to handle creation of one instance of the class.
  • chepner
    chepner almost 4 years
    Inheritance might be appropriate, but it's really an orthogonal issue to what is being asked.
  • chepner
    chepner almost 4 years
    This really isn't very Pythonic. __init__ should take a year and a quarter directly, rather than a single value of unknown type. A class method from_date can handle extracting a year and quarter from a datetime.date value, then calling YearQuarter(y, q). You could define a similar class method from_tuple, but that hardly seems worth doing since you could simply call YearQuarter(*t).
  • chepner
    chepner almost 4 years
    The idea of a class method is to separate creating a special instance into two independent pieces: first, you define a generic __init__ that can handle initializing Cheese without having to know about special kinds of cheeses. Second, you define a class method that generates the appropriate arguments to the generic __init__ for certain special cases. Here, you are basically reinventing parts of inheritance.
  • Elmex80s
    Elmex80s almost 4 years
    @chepner I gave it a huge update. Please tell me what you think.
  • chepner
    chepner almost 4 years
    It's still a mess (even more so than before) of special cases. __init__ shouldn't responsible for analyzing every possible set of values you might use to create an instance. def __init__(self, year, quarter): self._year = year; self._quarter = quarter: that's it (though may be with some range checking on quarter). Other class methods handle the job of mapping a different argument or arguments to a year and a quarter that can be passed to __init__.
  • chepner
    chepner almost 4 years
    For example, from_year_month takes a month m, maps it to a quarter q, then calls YearQuarter(y, q). from_date extracts the year and the month from the date instance, then calls YearQuarter._from_year_month. No repetition, and each method is responsible for one specific way of generating a year and a quarter to pass to __init__.
  • Elmex80s
    Elmex80s almost 4 years
    @chepner thanks for your feedback but I still strongly believe this is for users of the class (not the creator) a pleasant and Pythonic way of using it.
  • chepner
    chepner almost 4 years
    This is for the user as well. Class methods provide individual methods with self-documenting names, rather than a single entry point that could do any number of things.
  • Tom Winch
    Tom Winch over 3 years
    I wonder if one could define a decorator 'constructor' (that wraps up the new and super stuff) and then do: @constructor def other_init(self, stuff): self.stuff = stuff
  • Tom H
    Tom H over 3 years
    For folks from google in 2020, scroll down this page a bit - the answer by 'Ber' further down is solid and more pythonic than this route for most scenarios.
  • Peter Mortensen
    Peter Mortensen over 3 years
    Is "classmethod" literal? Or do you mean class method?
  • ivirshup
    ivirshup over 3 years
    I really like this solution. It has the noted advantage of letting you add initialization methods to a pre-existing class that already exists while keeping reasonably clean code. In theory, it's nice to have an inner constructor that everything goes through, but in practice sometimes the existing __init__'s API sucks.
  • Wassadamo
    Wassadamo almost 3 years
    @user989762 same issue in R's documentation. So many functions with elipsis '...' arguments. Extends those trips to the docs...
  • Erik
    Erik almost 3 years
    Thank you for this answer and the reference to multimethod package. In some situations multiple dispatch just feels so natural. Having worked in Julia for a while, it is something I miss in Python.
  • John Sohn
    John Sohn almost 3 years
    that breaks intellisense right ?
  • Eduardo Pignatelli
    Eduardo Pignatelli over 2 years
    This is very not flexible and not what in programming is most commonly meant by "multiple constructor". This technique uses the __init__ function anyway, and it is bound to its argument. This is not possible, for example, despite being a very common use case: def foo(property_different_from_holes)
  • Ber
    Ber over 2 years
    @EduardoPignatelli You can add more parameters to __init__() and provide suitable default vales, as in def __init__(self, num_holes=0, color='cheezy_yellow'): which will allow you to define like @classmethod def colored(cls, color): return cls(color=color)
  • Eduardo Pignatelli
    Eduardo Pignatelli over 2 years
    @Ber Yeah, that's the pattern I am using right know, but, still, it is a workaround, not a proper solutions like you would find in .NET, or C++.