There's no overloading in Python, so you can't "overload the constructor" for the simple reason that you can't overload anything.
There are two general ways to "simulate overloading" in Python, and both of them apply to overloaded constructors:1
- Write a single method that's flexible enough to take either set of parameters.
- Write two methods with different names.
The first one is pretty simple:
def __init__(self, date_or_day, month=None, year=None):
if isinstance(date_or_day, Date):
# do "copy" stuff
else:
# do "non-copy" stuff
… or, a minor variation:
def __init__(self, day=None, month=None, year=None, *, date=None):
if month is None:
# do "copy" stuff
else:
# do "non-copy" stuff
This works, but it can be a little clunky. (Think of the interface to range
, and how hard it is to write down clearly even after you know it.) And either way, you probably want to throw in some checks to raise TypeError("some message")
if the user does something nonsensical, like calling it with a date and a month.
If you prefer to require keyword arguments:
def __init__(self, day=None, month=None, year=None, *, date=None):
if date is not None:
# do "copy" stuff, maybe also assert that month and year are None
else:
# do "non-copy" stuff
… then the constructor can't be called as just Date(otherdate)
anymore, but it can be called as Date(date=otherdate)
, which may be clearer anyway, and you can avoid all the fiddly stuff with the first parameter having potentially two different meanings.
The second one may seem impossible at first, because the constructor has to be called as Date(…)
.
But you can use the "alternate constructor" idiom, by defining a classmethod that can be called explicitly as Date.from_somethingelse(…)
. This idiom is, in fact, heavily used in the types from the stdlib's datetime
library—e.g., datetime.datetime.now()
is an alternate constructor that creates a datetime
object representing right now.
So, for example:
def __init__(self, date=None):
if date is None:
# do "default constructor" stuff
else:
# do "copy" stuff
@classmethod
def from_dmy(cls, day, month, year):
self = cls()
# do "non-copy" stuff
One last point: copy constructors are rare in idiomatic Python, because they're really not as useful as in languages like Java.
First, there's nowhere for them to get applied implicitly—assignment isn't a copy, and even if it were, variables don't have types, values do, so there'd be no "hook" for you to specify what constructor you wanted. And if you want to explicitly copy something, you usually either add a copy
method (as with list
and dict
) or just use the copy
module.
What is somewhat common is a constructor that takes some wider range of things that includes your own type. For example, lists can be constructed from any iterable, including another list, and dicts can be constructed from any iterable of pairs or any mapping, including another dict.
1. Actually, in Python, __init__
methods aren't constructors, but initializers—they don't return an object, they get called on an already-constructed object and initialize it. If you actually need to write a constructor (typically for immutable objects, that you can't initialize after creation), that's __new__
.