2

What is a clean way to declare multiple constructors for one class?

For example, let's say I have an Item class. One way to create an Item (for example is)

item = Item(product_id, name, description,price)

another way to do the same thing might

item = Item(otherItem)

And then another way to do this.. maybe in some cases I don't have the price so I want to pass just

item = Item(product_id, name,description)

and yet another case might be

item = Item(product_id,price)

The other question I have is: There are some private variables which might be initialized during runtime. Let's say I have some random variable itemCount and I want to keep a track of it internally.

How do I declare it that I dont have to put it in initialization mode, but rather, somewhere in the run time.. I can do something like

self._count +=1

Thanks

Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
frazman
  • 32,081
  • 75
  • 184
  • 269

3 Answers3

4

The two most common approaches for providing multiple constructors are:

  1. class methods and
  2. factory functions

Here is an example taken from the standard library showing how collections.OrderedDict uses a classmethod to implement fromkeys() as an alternate class constructor:

@classmethod
def fromkeys(cls, iterable, value=None):
    '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S.
    If not specified, the value defaults to None.

    '''
    self = cls()
    for key in iterable:
        self[key] = value
    return self

As an example of the other common approach, here's a factory function used in symtable.py in the standard library:

class SymbolTableFactory:
    def __init__(self):
        self.__memo = weakref.WeakValueDictionary()

    def new(self, table, filename):
        if table.type == _symtable.TYPE_FUNCTION:
            return Function(table, filename)
        if table.type == _symtable.TYPE_CLASS:
            return Class(table, filename)
        return SymbolTable(table, filename)
Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • While you could do this, it seems like optional/named parameters are the more Pythonic way to accomplish the same thing. There's been very few times where I actually preferred multiple constructors to optional parameters. – Davy8 Nov 14 '11 at 22:43
1

You could use default arguments:

class Item(object):

  def __init__(self, product_id = None, name = None, description = None, price = None)
      ... implementation ...

You can substitute in any value you want for None, if the default value should be something different.

Example usage:

item1 = Item(product_id = 4, price = 13) # use the field name!
item2 = Item(name = "hammer", description = "used to belong to Thor")

For the copy constructor, item = Item(otherItem), @Raymond's suggestions of class methods and factory functions may be the most Pythonic way to go.


Update: here's a question about multiple constructors in Python. It also mentions using *args and **kwargs.

Community
  • 1
  • 1
Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
  • Hi.. But in this method.. lets say in initialization i dont want name.. but the description to pass on.. then how would this do that..like Item(123,description).. whether its a name or description?? how wil it figure out – frazman Nov 14 '11 at 22:36
  • 1
    Guido is strongly opposed to using optional/named parameters in that way. For example, that is why we have both itertools.ifilter() and itertools.ifilterfalse() instead of a single tool with a flag that switches behaviors. Likewise, the datetime class uses multiple separate constructors (now, from_ordinal, from_timestamp). If you're going to give advice to other Python programmers, it should match the Python way of doing things. Having a default argument for price is reasonable if priceless items can be created, but the *otheritem* case of cloning demands a classmethod. – Raymond Hettinger Nov 14 '11 at 23:22
1

Copying is usually done by a copy() method on instances instead of by providing a "copy constructor", so it's

item = other_item.copy()

instead of

item = Item(other_item)

All the other constructor signatures you mentioned easily be handled by default arguments and keyword arguments:

def __init__(self, product_id, name=None, description=None, price=None):
    ...

If you want another constructor with completely different code, than a classmethod is the right approach -- see the answer by Raymond Hettinger.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841