940

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?

martineau
  • 119,623
  • 25
  • 170
  • 301
winsmith
  • 20,791
  • 9
  • 39
  • 49

15 Answers15

965

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

Elias Zamaria
  • 96,623
  • 33
  • 114
  • 148
vartec
  • 131,205
  • 36
  • 218
  • 244
  • 109
    For those interested, `kwargs` stands for *keyword arguments* (seems logic once you know it). :) – tleb Aug 03 '16 at 06:29
  • 53
    There are moments that `*args` and `**kwargs` are an overkill. At most constructors, you want to know what your arguments are. – user989762 Apr 28 '18 at 09:26
  • 1
    @user989762 Yes! For sure! – Captain Jack Sparrow Apr 15 '20 at 00:30
  • 11
    @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. – GlenRSmith Jul 22 '20 at 15:36
  • 31
    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. – Tom H Nov 12 '20 at 13:40
  • @user989762 same issue in R's documentation. So many functions with elipsis '...' arguments. Extends those trips to the docs... – Wassadamo Jul 17 '21 at 01:34
  • that breaks intellisense right ? – John Sohn Sep 25 '21 at 00:57
949

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()
wjandrea
  • 28,235
  • 9
  • 60
  • 81
Ber
  • 40,356
  • 16
  • 72
  • 88
  • +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). – rmbianchi Dec 09 '11 at 13:35
  • 46
    @rmbianchi: The accepted answer may be more in line with other languages, but it is also less pythonic: `@classmethod`s are the pythonic way of implementing multiple contstructors. – Ethan Furman Mar 22 '12 at 01:34
  • @EthanFurman: I like your cheese and thanks for the improved code. This is really better, using the __init__() constructor directly. – Ber Mar 26 '12 at 16:49
  • Ingenious. So the class methods are static? – Brian Peterson Apr 20 '13 at 20:36
  • 25
    @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. – Ber Apr 22 '13 at 11:10
  • 3
    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](https://docs.python.org/3.4/library/abc.html#abc.abstractclassmethod). I would also argue that this approach is more explicit, which goes with [The Zen of Python](http://legacy.python.org/dev/peps/pep-0020/). – mach Sep 04 '14 at 13:16
  • 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 `if`s 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. – Regis May Jun 18 '16 at 09:03
  • @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. – Ber Jun 23 '16 at 14:17
  • That is exactly what I am taking about: Because of that a more complex implementation is required. – Regis May Jun 28 '16 at 15:27
  • A great example of this is in GeoPandas: https://github.com/geopandas/geopandas/blob/master/geopandas/geodataframe.py – Rafael Zayas Jan 18 '18 at 17:52
  • 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? . – piedpiper Jul 23 '18 at 10:17
  • 4
    @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. – Ber Aug 23 '18 at 11:37
  • This answer was very helpful but I refuse to upvote past 666 – AGML Apr 01 '20 at 14:41
  • 3
    @RegisMay (1/2) Rather than having a bunch of `if`s 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 Jun 22 '20 at 20:28
  • (2/2) Python also lets us make [properties](https://docs.python.org/3/howto/descriptor.html#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." – Nathaniel Jones Jun 22 '20 at 20:28
  • @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". – Regis May Jun 23 '20 at 16:12
  • 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)` – Eduardo Pignatelli Feb 11 '22 at 16:40
  • @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)` – Ber Feb 14 '22 at 09:14
  • @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++. – Eduardo Pignatelli Feb 14 '22 at 16:00
72

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
Neil G
  • 32,138
  • 39
  • 156
  • 257
Andrzej Pronobis
  • 33,828
  • 17
  • 76
  • 92
  • 15
    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? – Alexey Feb 20 '18 at 14:11
  • 1
    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. – Neil G Mar 01 '20 at 23:22
  • 1
    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 Winch Oct 30 '20 at 16:36
31

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 )
Yes - that Jake.
  • 16,725
  • 14
  • 70
  • 96
21

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...

Ferdinand Beyer
  • 64,979
  • 15
  • 154
  • 145
  • 6
    Factory functions use _cls_: use _cls_ instead of _Cheese_. If not, what is the point of using class methods instead of static methods? – rools Aug 02 '18 at 11:53
19

Those are good ideas for your implementation, but if you are presenting a cheese making interface to a user. They don't care how many holes the cheese has or what internals go into making cheese. The user of your code just wants "gouda" or "parmesean" right?

So why not do this:

# cheese_user.py
from cheeses import make_gouda, make_parmesean

gouda = make_gouda()
paremesean = make_parmesean()

And then you can use any of the methods above to actually implement the functions:

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

def make_gouda():
    return Cheese()

def make_paremesean():
    return Cheese(num_holes=15)

This is a good encapsulation technique, and I think it is more Pythonic. To me this way of doing things fits more in line more with duck typing. You are simply asking for a gouda object and you don't really care what class it is.

Brad C
  • 720
  • 8
  • 14
  • 2
    I tend to opt for this approach because it is remarkably similar to the [Factory Method pattern](http://en.wikipedia.org/wiki/Factory_method_pattern). – 2rs2ts May 30 '13 at 00:21
  • 6
    `make_gouda, make_parmesan` should be classmethods of `class Cheese` – smci Jul 17 '18 at 23:11
15

Overview

For the specific cheese example, I agree with many of the other answers about using default values to signal random initialization or to use a static factory method. However, there may also be related scenarios that you had in mind where there is value in having alternative, concise ways of calling the constructor without hurting the quality of parameter names or type information.

Since Python 3.8 and functools.singledispatchmethod can help accomplish this in many cases (and the more flexible multimethod can apply in even more scenarios). (This related post describes how one could accomplish the same in Python 3.4 without a library.) I haven't seen examples in the documentation for either of these that specifically shows overloading __init__ as you ask about, but it appears that the same principles for overloading any member method apply (as shown below).

"Single dispatch" (available in the standard library) requires that there be at least one positional parameter and that the type of the first argument be sufficient to distinguish among the possible overloaded options. For the specific Cheese example, this doesn't hold since you wanted random holes when no parameters were given, but multidispatch does support the very same syntax and can be used as long as each method version can be distinguish based on the number and type of all arguments together.

Example

Here is an example of how to use either method (some of the details are in order to please mypy which was my goal when I first put this together):

from functools import singledispatchmethod as overload
# or the following more flexible method after `pip install multimethod`
# from multimethod import multidispatch as overload


class MyClass:

    @overload  # type: ignore[misc]
    def __init__(self, a: int = 0, b: str = 'default'):
        self.a = a
        self.b = b

    @__init__.register
    def _from_str(self, b: str, a: int = 0):
        self.__init__(a, b)  # type: ignore[misc]

    def __repr__(self) -> str:
        return f"({self.a}, {self.b})"


print([
    MyClass(1, "test"),
    MyClass("test", 1),
    MyClass("test"),
    MyClass(1, b="test"),
    MyClass("test", a=1),
    MyClass("test"),
    MyClass(1),
    # MyClass(),  # `multidispatch` version handles these 3, too.
    # MyClass(a=1, b="test"),
    # MyClass(b="test", a=1),
])

Output:

[(1, test), (1, test), (0, test), (1, test), (1, test), (0, test), (1, default)]

Notes:

  • I wouldn't usually make the alias called overload, but it helped make the diff between using the two methods just a matter of which import you use.
  • The # type: ignore[misc] comments are not necessary to run, but I put them in there to please mypy which doesn't like decorating __init__ nor calling __init__ directly.
  • If you are new to the decorator syntax, realize that putting @overload before the definition of __init__ is just sugar for __init__ = overload(the original definition of __init__). In this case, overload is a class so the resulting __init__ is an object that has a __call__ method so that it looks like a function but that also has a .register method which is being called later to add another overloaded version of __init__. This is a bit messy, but it please mypy becuase there are no method names being defined twice. If you don't care about mypy and are planning to use the external library anyway, multimethod also has simpler alternative ways of specifying overloaded versions.
  • Defining __repr__ is simply there to make the printed output meaningful (you don't need it in general).
  • Notice that multidispatch is able to handle three additional input combinations that don't have any positional parameters.
teichert
  • 3,963
  • 1
  • 31
  • 37
  • 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. – Erik Sep 07 '21 at 12:23
10

Use num_holes=None as a default, instead. Then check for whether num_holes is None, and if so, randomize. That's what I generally see, anyway.

More radically different construction methods may warrant a classmethod that returns an instance of cls.

Devin Jeanpierre
  • 92,913
  • 4
  • 55
  • 79
  • Is "classmethod" literal? Or do you mean [class method](https://en.wikipedia.org/wiki/Method_(computer_programming)#Class_methods)? – Peter Mortensen Jan 06 '21 at 00:53
9

The best answer is the one above about default arguments, but I had fun writing this, and it certainly does fit the bill for "multiple constructors". Use at your own risk.

What about the new method.

"Typical implementations create a new instance of the class by invoking the superclass’s new() method using super(currentclass, cls).new(cls[, ...]) with appropriate arguments and then modifying the newly-created instance as necessary before returning it."

So you can have the new method modify your class definition by attaching the appropriate constructor method.

class Cheese(object):
    def __new__(cls, *args, **kwargs):

        obj = super(Cheese, cls).__new__(cls)
        num_holes = kwargs.get('num_holes', random_holes())

        if num_holes == 0:
            cls.__init__ = cls.foomethod
        else:
            cls.__init__ = cls.barmethod

        return obj

    def foomethod(self, *args, **kwargs):
        print "foomethod called as __init__ for Cheese"

    def barmethod(self, *args, **kwargs):
        print "barmethod called as __init__ for Cheese"

if __name__ == "__main__":
    parm = Cheese(num_holes=5)
mluebke
  • 8,588
  • 7
  • 35
  • 31
  • 12
    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. – Yes - that Jake. Mar 30 '09 at 16:07
  • 1
    @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? – Reti43 Dec 14 '14 at 09:04
  • 12
    @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. – javawizard Dec 14 '14 at 09:23
  • Indeed, there is no reason to modify the definition of the *class* just to handle creation of one *instance* of the class. – chepner Jul 26 '20 at 16:55
4

I'd use inheritance. Especially if there are going to be more differences than number of holes. Especially if Gouda will need to have different set of members then Parmesan.

class Gouda(Cheese):
    def __init__(self):
        super(Gouda).__init__(num_holes=10)


class Parmesan(Cheese):
    def __init__(self):
        super(Parmesan).__init__(num_holes=15) 
Michel Samia
  • 4,273
  • 2
  • 24
  • 24
  • 1
    Inheritance *might* be appropriate, but it's really an orthogonal issue to what is being asked. – chepner Jul 26 '20 at 16:57
3

This is how I solved it for a YearQuarter class I had to create. I created an __init__ which is very tolerant to a wide variety of input.

You use it like this:

>>> from datetime import date
>>> temp1 = YearQuarter(year=2017, month=12)
>>> print temp1
2017-Q4
>>> temp2 = YearQuarter(temp1)
>>> print temp2
2017-Q4
>>> temp3 = YearQuarter((2017, 6))
>>> print temp3
2017-Q2 
>>> temp4 = YearQuarter(date(2017, 1, 18))
>>> print temp4
2017-Q1
>>> temp5 = YearQuarter(year=2017, quarter = 3)
>>> print temp5
2017-Q3

And this is how the __init__ and the rest of the class looks like:

import datetime


class YearQuarter:

    def __init__(self, *args, **kwargs):
        if len(args) == 1:
            [x]     = args

            if isinstance(x, datetime.date):
                self._year      = int(x.year)
                self._quarter   = (int(x.month) + 2) / 3
            elif isinstance(x, tuple):
                year, month     = x

                self._year      = int(year)

                month           = int(month)

                if 1 <= month <= 12:
                    self._quarter   = (month + 2) / 3
                else:
                    raise ValueError

            elif isinstance(x, YearQuarter):
                self._year      = x._year
                self._quarter   = x._quarter

        elif len(args) == 2:
            year, month     = args

            self._year      = int(year)

            month           = int(month)

            if 1 <= month <= 12:
                self._quarter   = (month + 2) / 3
            else:
                raise ValueError

        elif kwargs:

            self._year      = int(kwargs["year"])

            if "quarter" in kwargs:
                quarter     = int(kwargs["quarter"])

                if 1 <= quarter <= 4:
                    self._quarter     = quarter
                else:
                    raise ValueError
            elif "month" in kwargs:
                month   = int(kwargs["month"])

                if 1 <= month <= 12:
                    self._quarter     = (month + 2) / 3
                else:
                    raise ValueError

    def __str__(self):
        return '{0}-Q{1}'.format(self._year, self._quarter)
Elmex80s
  • 3,428
  • 1
  • 15
  • 23
  • 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.`. – Mike O'Connor Dec 30 '19 at 22:40
  • 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 Jul 26 '20 at 16:59
  • @chepner I gave it a huge update. Please tell me what you think. – Elmex80s Jul 27 '20 at 10:16
  • 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 Jul 27 '20 at 14:18
  • 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__`. – chepner Jul 27 '20 at 14:34
  • @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. – Elmex80s Jul 27 '20 at 15:35
  • 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. – chepner Jul 27 '20 at 15:39
3

Since my initial answer was criticised on the basis that my special-purpose constructors did not call the (unique) default constructor, I post here a modified version that honours the wishes that all constructors shall call the default one:

class Cheese:
    def __init__(self, *args, _initialiser="_default_init", **kwargs):
        """A multi-initialiser.
        """
        getattr(self, _initialiser)(*args, **kwargs)

    def _default_init(self, ...):
        """A user-friendly smart or general-purpose initialiser.
        """
        ...

    def _init_parmesan(self, ...):
        """A special initialiser for Parmesan cheese.
        """
        ...

    def _init_gouda(self, ...):
        """A special initialiser for Gouda cheese.
        """
        ...

    @classmethod
    def make_parmesan(cls, *args, **kwargs):
        return cls(*args, **kwargs, _initialiser="_init_parmesan")

    @classmethod
    def make_gouda(cls, *args, **kwargs):
        return cls(*args, **kwargs, _initialiser="_init_gouda")
PiCTo
  • 924
  • 1
  • 11
  • 23
Alexey
  • 3,843
  • 6
  • 30
  • 44
  • 2
    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. – chepner Jul 26 '20 at 17:05
1
class Cheese:
    def __init__(self, *args, **kwargs):
        """A user-friendly initialiser for the general-purpose constructor.
        """
        ...

    def _init_parmesan(self, *args, **kwargs):
        """A special initialiser for Parmesan cheese.
        """
        ...

    def _init_gauda(self, *args, **kwargs):
        """A special initialiser for Gauda cheese.
        """
        ...

    @classmethod
    def make_parmesan(cls, *args, **kwargs):
        new = cls.__new__(cls)
        new._init_parmesan(*args, **kwargs)
        return new

    @classmethod
    def make_gauda(cls, *args, **kwargs):
        new = cls.__new__(cls)
        new._init_gauda(*args, **kwargs)
        return new
Alexey
  • 3,843
  • 6
  • 30
  • 44
  • 3
    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). – smci Jul 17 '18 at 23:18
  • 1
    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. – Alexey Jul 18 '18 at 08:35
  • 2
    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 – smci Jul 18 '18 at 21:42
  • "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 Jul 19 '18 at 06:48
  • As to the question why i would want them, well, even `namedtuple`-produced classes have two constructors: the default one and [`_make`](https://docs.python.org/3/library/collections.html#collections.somenamedtuple._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 Jul 19 '18 at 06:55
  • 1
    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 Jul 19 '18 at 07:48
  • @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.) – Alexey Jul 19 '18 at 07:49
  • 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. – smci Jul 20 '18 at 23:59
  • @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. – Alexey Jul 21 '18 at 11:34
  • 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. – smci Jul 22 '18 at 01:02
  • 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. – ivirshup Jan 18 '21 at 07:30
1

I do not see a straightforward answer with an example yet. The idea is simple:

  • use __init__ as the "basic" constructor as python only allows one __init__ method
  • use @classmethod to create any other constructors and call the basic constructor

Here is a new try.

 class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def fromBirthYear(cls, name, birthYear):
        return cls(name, date.today().year - birthYear)

Usage:

p = Person('tim', age=18)
p = Person.fromBirthYear('tim', birthYear=2004)
Tim C.
  • 91
  • 6
0

Here (drawing on this earlier answer, the pure Python version of classmethod in the docs, and as suggested by this comment) is a decorator that can be used to create multiple constructors.

from types import MethodType
from functools import wraps

class constructor:
    def __init__(self, func):

        @wraps(func)                      
        def wrapped(cls, *args, **kwargs):
            obj = cls.__new__(cls)        # Create new instance but don't init
            super(cls, obj).__init__()    # Init any classes it inherits from
            func(obj, *args, **kwargs)    # Run the constructor with obj as self
            return obj                
        
        self.wrapped = wrapped

    def __get__(self, _, cls):
        return MethodType(self.wrapped, cls)   # Bind this constructor to the class 
        
    
class Test:
    def __init__(self, data_sequence):
        """ Default constructor, initiates with data sequence """
        self.data = [item ** 2 for item in data_sequence]
        
    @constructor
    def zeros(self, size):
        """ Initiates with zeros """
        self.data = [0 for _ in range(size)]
           
a = Test([1,2,3])
b = Test.zeros(100)

This seems the cleanest way in some cases (see e.g. multiple dataframe constructors in Pandas), where providing multiple optional arguments to a single constructor would be inconvenient: for example cases where it would require too many parameters, be unreadable, be slower or use more memory than needed. However, as earlier comments have argued, in most cases it is probably more Pythonic to route through a single constructor with optional parameters, adding class methods where needed.

Stuart
  • 9,597
  • 1
  • 21
  • 30