user1336934
user1336934

Reputation: 61

How to avoid type checking arguments to Python function

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.

Upvotes: 5

Views: 1814

Answers (2)

celeritas
celeritas

Reputation: 2281

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.

Upvotes: 3

Josh Smeaton
Josh Smeaton

Reputation: 48720

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.

Upvotes: 5

Related Questions