Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The most pythonic way to implement two constructors

Pardon incompetence of style from Python novice here.

I have a class that takes one parameter for establishing the initial data. There are two ways how the initial data can come in: either a list of strings, or a dictionary with string keys and integer values.

Right now I implement only one version of the constructor, the one that takes the dictionary for parameter, with {} as default value. The list parameter init is implemented as a method, ie

myClass = MyClass()
myClass.initList(listVar)

I can surely live with this, but this certainly is not perfect. So, I decided to turn here for some Pythonic wisdom: how such polymorphic constructors should be implemented? Should I try and fail to read initData.keys() in order to sniff if this is dictionary? Or maybe sniffing parameter types to implement lousy polymorphism where it's not welcome by design is considered non-pythonic?

like image 852
Passiday Avatar asked Dec 02 '25 12:12

Passiday


2 Answers

In an ideal world you'd write one constructor that could take either a list or dict without knowing the difference (i.e. duck typed). Of course, this isn't very realistic since these are pretty different ducks.

Understandably, too, you have a little heartburn about the idea of checking the actual instance types, because it breaks with the idea of duck typing. But, in python 2.6 an interesting module called abc was introduced which allows the definition of "abstract base classes". To be considered an instance of an abstract base class, one doesn't actually have to inherit from it, but rather only has to implement all its abstract methods.

The collections module includes some abstract base classes that would be of interest here, namely collections.Sequence and collections.Mapping. Thus you could write your __init__ functions like:

def __init__(self, somedata):
    if isinstance(somedata, collections.Sequence):
        # somedata is a list or some other Sequence
    elif isinstance(somedata, collections.Mapping):
        # somedata is a dict or some other Mapping

http://docs.python.org/2/library/collections.html#collections-abstract-base-classes contains the specifics of which methods are provided by each ABC. If you stick to these, then your code can now accept any object which fits one of these abstract base classes. And, as far as taking the builtin dict and list types, you can see that:

>>> isinstance([], collections.Sequence)
True
>>> isinstance([], collections.Mapping)
False
>>> isinstance({}, collections.Sequence)
False
>>> isinstance({}, collections.Mapping)
True

And, almost by accident, you just made it work for tuple too. You probably didn't care if it was really a list, just that you can read the elements out of it. But, if you had checked isinstance(somedata, list) you would have ruled out tuple. This is what using an ABC buys you.

like image 96
FatalError Avatar answered Dec 04 '25 00:12

FatalError


As @Jan-PhilipGehrcke notes, pythonic can be hard to quantify. To me it means:

  • easy to read
  • easy to maintain
  • simple is better than complex is better than complicated
  • etcetera, etcetera, and so forth (see the Zen of Python for the complete list, which you get by typing import this in the interpreter)

So, the most pythonic solution depends on what you have to do for each supported initializer, and how many of them you have. I would say if you have only a handful, and each one can be handled by only a few lines of code, then use isinstance and __init__:

class MyClass(object):

    def __init__(self, initializer):
        """
        initialize internal data structures with 'initializer'
        """
        if isinstance(initializer, dict):
            for k, v in itit_dict.items():
                # do something with k & v
                setattr(self, k, v)
        elif isinstance(initializer, (list, tuple)):
            for item in initializer:
                setattr(self, item, None)

On the other hand, if you have many possible initializers, or if any one of them requires a lot of code to handle, then you'll want to have one classmethod constructor for each possible init type, with the most common usage being in __init__:

class MyClass(object):

    def __init__(self, init_dict={}):
        """
        initialize internal data structures with 'init_dict'
        """
        for k, v in itit_dict.items():
            # do something with k & v
            setattr(self, k, v)

    @classmethod
    def from_sequence(cls, init_list):
        """
        initialize internal  data structures with 'init_list'
        """
        result = cls()
        for item in init_list:
            setattr(result, item, None)
        return result

This keeps each possible constructor simple, clean, and easy to understand.

As a side note: using mutable objects as defaults (like I do in the above __init__) needs to be done with care; the reason is that defaults are only evaluated once, and then whatever the result is will be used for every subsequent invocation. This is only a problem when you modify that object in your function, because those modifications will then be seen by every subsequent invocation -- and unless you wanted to create a cache that's probably not the behavior you were looking for. This is not a problem with my example because I am not modifying init_dict, just iterating over it (which is a no-op if the caller hasn't replaced it as it's empty).

like image 27
Ethan Furman Avatar answered Dec 04 '25 02:12

Ethan Furman