5

I'm creating instances of a class Foo, and I'd like to be able to instantiate these in a general way from a variety of types. You can't pass Foo a dict or list. Note that Foo is from a 3rd party code base - I can't change Foo's code.

I know that type checking function arguments in Python is considered bad form. Is there a more Pythonic way to write the function below (i.e. without type checking)?

def to_foo(arg):
  if isinstance(arg, dict):
    return dict([(key,to_foo(val)) for key,val in arg.items()])
  elif isinstance(arg, list):
    return [to_foo(i) for i in arg]
  else:
    return Foo(arg)

Edit: Using try/except blocks is possible. For instance, you could do:

def to_foo(arg):
  try:
    return Foo(arg)
  except ItWasADictError:
    return dict([(key,to_foo(val)) for key,val in arg.items()])
  except ItWasAListError:
    return [to_foo(i) for i in arg]

I'm not totally satisfied by this for two reasons: first, type checking seems like it addresses more directly the desired functionality, whereas the try/except block here seems like it's getting to the same place but less directly. Second, what if the errors don't cleanly map like this? (e.g. if passing either a list or dict throws a TypeError)

Edit: a third reason I'm not a huge fan of the try/except method here is I need to go and find what exceptions Foo is going to throw in those cases, rather than being able to code it up front.

  • 1
    possible duplicate of [Is it Pythonic to check function argument types?](http://stackoverflow.com/questions/1950386/is-it-pythonic-to-check-function-argument-types) – aruisdante Aug 07 '14 at 03:48
  • 1
    In general, I would consider these to be three completely separate functions. They all return different types with no common interface, so I can't see a place where you wouldn't know a-priori the type you were going to be constructing ``Foo`` from, since you need to know how to handle the return of either a ``Foo``, a ``list(Foo)``, or a ``dict((str, Foo))``. – aruisdante Aug 07 '14 at 03:51
  • 1
    Also keep in mind that any type of type checking in python doesn't look along the inheritance chain, only the immediate instance type. This means your type checking will always fail for things that *act like* ``lists`` (say, a set, or any iterable really), or things that *act like* ``dicts``, like an ``OrderedDict`` or a ``defaultdict``. ``singledispatch`` is slightly better in that it will look along the inheritance chain for a common ancestor, but will still fail the *acts-like* test. – aruisdante Aug 07 '14 at 03:54
  • 1
    I agree that it can be known beforehand if the object passed is a list or dict, and handled appropriately. What I was hoping for is a function that elegantly handles most cases I'm interested in, i.e a single instance, a list, a dict, a list of lists, etc. It seems like the try/except block can do this, but that the exceptions aren't guaranteed to be unique for every individual type you could be interested in supporting. – user1336934 Aug 07 '14 at 05:37

2 Answers2

4

If you're using python 3.4 you can use functools.singledispatch, or a backport for a different python version

from functools import singledispatch

@singledispatch
def to_foo(arg):
    return Foo(arg)

@to_foo.register(list)
def to_foo_list(arg):
    return [Foo(i) for i in arg]

@to_foo.register(dict)
def to_foo_dict(arg):
    return {key: Foo(val) for key, val in arg.items()}

This is a fairly new construct for python, but a common pattern in other languages. I'm not sure you'd call this pythonic or not, but it does feel better than writing isinstances everywhere. Though, in practise, the singledispatch is probably just doing the isinstance checks for you internally.

Josh Smeaton
  • 47,939
  • 24
  • 129
  • 164
  • +1 - I think I'm more in favor of isinstance, still - it feels cleaner and more direct than having to define 3 different functions. – user1336934 Aug 07 '14 at 03:09
  • @user1336934 I agree with you for this particular use-case. I think I'd prefer singledispatch if there was a lot more code and logic in the various bodies. – Josh Smeaton Aug 07 '14 at 03:10
  • @JoshSmeaton "Though, in practise, the singledispatch is probably just doing the isinstance checks for you internally" from the source it looks like `singledispatch` just maintains a dictionary of functions where the key is the type passed to the `register` calls. So technically it is a hash table lookup and not an `isinstance` check. – ebarr Aug 07 '14 at 05:55
  • @ebarr thanks for mentioning that. I was curious and went and checked the source myself to see what method they used but forgot to update the answer. – Josh Smeaton Aug 07 '14 at 13:07
3

The pythonic way to deal with your issue is to go ahead and assume (first) that arg is Foo and except any error:

try:
    x = Foo(arg)
except NameError:
    #do other things

The phrase for this idea is "duck typing", and it's a popular pattern in python.

celeritas
  • 2,191
  • 1
  • 17
  • 28