3

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?

Passiday
  • 7,573
  • 8
  • 42
  • 61
  • 1
    Why don't you use `*args` and `**kwargs`? – Cristian Ciupitu Mar 15 '13 at 13:59
  • User class methods as alternate constructors. See my anwer to this similar question: http://stackoverflow.com/questions/682504/what-is-a-clean-pythonic-way-to-have-multiple-constructors-in-python/682545#682545 – Ber Mar 15 '13 at 14:32
  • 2
    Also, don't use "{}" as a default parameter in functions or methods. To say it imn a few wors, it behaes as a a "C static" variable -each function invocation will see the same object as the previous invocations. – jsbueno Mar 15 '13 at 14:37
  • Thanks, @jsbueno for this valuable tip, I had no idea that this object is being reused in all instances. Not that my design stored reference to it, but this could save me from some head scratching in other project. – Passiday Mar 15 '13 at 21:18
  • @jsbueno: More completely: each function will see the same object unless the caller specifies a different object for that parameter. Certainly it should be used with care. – Ethan Furman Mar 15 '13 at 23:01

4 Answers4

3

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.

FatalError
  • 52,695
  • 14
  • 99
  • 116
  • "*you have a little heartburn about the idea of checking the actual instance types, because it breaks with the idea of duck typing*" -- your approach is very neat, I like it -- I just want to point out that it still checks instance types, in a generalized fashion. I've read it often times and I still don't really get why `isinstance()` should be a bad thing. – Dr. Jan-Philip Gehrcke Mar 15 '13 at 15:51
  • @Jan-PhilipGehrcke: Yep that's a good point. It's more like checking a "category of duck" rather than a "specific family of ducks". As far as `isinstance()` being bad -- I don't think it's bad either. It's just that using it it's easy to make your code more restrictive than it has to be about what kind of objects it will work with. – FatalError Mar 15 '13 at 16:14
  • Thanks, @FatalError, for pointing to this ABC module! In this particular project, I am kind of interested to keep the types pretty restricted. But it's very good to know there is such a neat tool that will qualify a duck decoy as fit for the task, rather than complain that it has no duck DNA. – Passiday Mar 15 '13 at 22:28
2

There is no function overloading in Python. Using if isinstance(...) in your __init__() method would be very simple to read and understand:

class Foo(object):
    def __init__(self, arg):
        if isinstance(arg, dict):
            ...
        elif isinstance(arg, list):
            ...
        else:
            raise Exception("big bang")
Dr. Jan-Philip Gehrcke
  • 33,287
  • 14
  • 85
  • 130
  • This would work. However, if I am totally honest, this doesn't strike me as very Pythonic. – NPE Mar 15 '13 at 15:42
  • I don't like the term "Pythonic" anymore. However, I am very happy to read about the actual point you want to make :-) -- so, what do you mean specifically? (No irony here, I am *very* open to your comments). – Dr. Jan-Philip Gehrcke Mar 15 '13 at 15:47
  • In short code it's very readable; when your code starts getting longer because each possible case requires lots of handling, it's no longer easy to read or maintain -- `classmethod`s are your best bet then. – Ethan Furman Mar 15 '13 at 16:36
2

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

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • 1
    This will break, as calling your "cls" without parameters will not pass the mandatory `init_dict` to your `__init__` – jsbueno Mar 15 '13 at 14:34
  • Let's not fight the `The most pythonic solution` war. It's hard to quantify. Would you mind removing this part from your answer? – Dr. Jan-Philip Gehrcke Mar 15 '13 at 15:31
  • @Jan-PhilipGehrcke: I would; I don't mind adding some explanation including what `pythonic` means to me, though. Hopefully that helps. – Ethan Furman Mar 15 '13 at 17:17
-1

You can use *args and **kwargs to do it. But if you want to know what type of parametr is you should use type() or isinstance().

Ellochka Cannibal
  • 1,750
  • 2
  • 19
  • 31