views:

2100

answers:

6

Duplicate of What is the best (idiomatic) way to check the type of a Python variable?


Sometimes checking of arguments in Python is necessary. e.g. I have a function which accepts either the address of other node in the network as the raw string address or class Node which encapsulates the other node's information.

I use type(0 function as in:

if type(n) == type(Node):
    do this
elif type(n) == type(str)
    do this

Is this a good way to do this?

A: 

Why not use the type directly:

type('a') is str
unbeknown
-1: no mention that typechecking is a bad idea
nosklo
+10  A: 

Use isinstance(). Sample:

if isinstance(n, unicode):
    #do this
else if isinstance(n, Node):
    #do that
Johannes Weiß
isinstance() also checks for subclasses.
AKX
isinstance(n, unicode) won't match plain str objects in python 2.x. Compare against basestring instead, which matches either str or unicode: isinstance(n, basestring)
Jarret Hardie
-1: no mention that typechecking is a bad idea
nosklo
A: 
>>> isinstance('a', str)
True
>>> isinstance(n, Node)
True
SilentGhost
-1: no mention that typechecking is a bad idea
nosklo
A: 

Sounds like you're after a "generic function" - one which behaves differently based on the arguments given. It's a bit like how you'll get a different function when you call a method on a different object, but rather than just using the first argument (the object/self) to lookup the function you instead use all of the arguments.

Turbogears uses something like this for deciding how to convert objects to JSON - if I recall correctly.

There's a article here on using the dispatcher package for this sort of thing:

http://www.ibm.com/developerworks/library/l-cppeak2/

From that article:

import dispatch
@dispatch.generic()
def doIt(foo, other):
    "Base generic function of 'doIt()'"
@doIt.when("isinstance(foo,int) and isinstance(other,str)")
def doIt(foo, other):
    print "foo is an unrestricted int |", foo, other
@doIt.when("isinstance(foo,str) and isinstance(other,int)")
def doIt(foo, other):
    print "foo is str, other an int |", foo, other
@doIt.when("isinstance(foo,int) and 3<=foo<=17 and isinstance(other,str)")
def doIt(foo, other):
    print "foo is between 3 and 17 |", foo, other
@doIt.when("isinstance(foo,int) and 0<=foo<=1000 and isinstance(other,str)")
def doIt(foo, other):
    print "foo is between 0 and 1000 |", foo, other

John Montgomery
-1: ugly! *vomits* and no mention that typechecking is a bad idea
nosklo
This is uglier and less maintainable than just hacking it out with typechecking IMO; an attempt to graft overloaded functions onto a language where it just doesn't fit very well.
bobince
+3  A: 

You can also use a try catch to type check if necessary:

def my_function(this_node):
    try:
        # call a method/attribute for the Node object
        if this_node.address:
             # more code here
             pass
    except AttributeError, e:
        # either this is not a Node or maybe it's a string, 
        # so behavior accordingly
        pass

You can see an example of this in Beginning Python in the second about generators (page 197 in my edition) and I believe in the Python Cookbook. Many times catching an AttributeError or TypeError is simpler and apparently faster. Also, it may work best in this manner because then you are not tied to a particular inheritance tree (e.g., your object could be a Node or it could be something other object that has the same behavior as a Node).

Rob
+1: attribute checking won't hurt code reuse, so it is better than typechecking, although having a function with different behaviours based on types is not a good idea.
nosklo
As long as the different behaviours are similar (at least in concept), I don't see what the big deal is. We all seem to think that it's OK for + (a.k.a. operator+() in C++) to work on numbers or strings, yet it actually has different behaviours based on types.
RobH
In my example, the + operator is, of course, implemented by two different functions that overload the operator. Unfortunately, since variables in Python are not typed, you can't overload functions based solely on type.
RobH
+2  A: 

*sigh*

No, typechecking arguments in python is not necessary. It is never necessary.

If your code accepts addresses as rawstring or as a Node object, your design is broken.

That comes from the fact that if you don't know already the type of an object in your own program, then you're doing something wrong already.

Typechecking hurts code reuse and reduces performance. Having a function that performs different things depending on the type of the object passed is bug-prone and has a behavior harder to understand and maintain.

You have the following saner options:

1) Make a Node object constructor that accepts rawstrings, or a function that converts strings in Node objects. Make your function assume the argument passed is a Node object. That way, if you need to pass a string to the function, you just do:

myfunction(Node(some_string))

That's your best option, it is clean, easy to understand and maintain. Anyone reading the code immediatelly understands what is happening, and you don't have to typecheck.

2) Make two functions, one that accepts Node objects and one that accepts rawstrings. You can make one call the other internally, in the most convenient way (myfunction_str can create a Node object and call myfunction_node, or the other way around).

3) Make Node objects have a __str__ method and inside your function, call str() on the received argument. That way you always get a string by coercion.

In any case, don't typecheck. It is completely unnecessary and has only downsides. Refactor your code instead in a way you don't need to typecheck. You only get benefits in doing so, both in short and long run.

nosklo
If neither Node nor str is your own implementation (and they probably aren't here), you don't get to add methods like __str__ to support your alternative behaviours(*). In this case, typechecking is an obvious and acceptable choice, preferable to having to wrap every str instance.
bobince
(*: unless you're suggesting monkey-patching...)
bobince
@bobince: Note that adding __str__ is the 3rd option I've provided. I'd go with the 1st one in this case.
nosklo
How about checking the structure of data received over the wire (e.g., JSON)? I'd rather fail fast with a clearer error against expectations than later in my code where I assume a specific piece of info exists. Thoughts?
StevenC