views:

1375

answers:

14

What python-specific antipatterns do you know?

Could you also give an example, please.

+26  A: 
  • Mutable default arguments in functions or methods, like

    def test(elem, start_list=[]):
        start_list.append(elem)
        return start_list
    
    
    print test(1)
    print test(2)
    

    creates the output

    [1]
    [1, 2]
    

    which is generally not what you want.

  • Mixing tabs and spaces.

nikow
actually I find it quiet useful for cache.
vartec
If you want to cache then better use a function decorator. Did I really get I downvote for that?
nikow
Yes, but it's a great irritation for first time users :)
Aaron Digulla
Well, for the first time users event the fact that copy() behavior is not deepcopy() might be a problem. Especially for those used to languages where a = b is same as a = b.deepcopy() in Python
vartec
mixing tabs and spaces is more of an error than anti-pattern (syntax error in py3k, btw)
SilentGhost
@SilentGhost: True, good point, one could also consider it as an extreme violation of coding conventions. Unfortunatley it seems to be so common that it is a "pattern" ;-)
nikow
Wow I never realized this could happen. How should this be handled ideally? default arg as `None` and create new empty list if it's still `None`?
TM
@TM: Yes, that's the typical way to do it. Note that this behavior actually makes a lot of sense, there would be far greater problems if the default arguments were reevaluated each time the function is called.
nikow
+6  A: 
  • using list where it's possible to use generators;
  • using for with range to access via index, instead of directly iterating object;
  • excessive [ab]use of lambda functions;
vartec
Lambdas are bad? Just really bad performance or what?
Robert P
excessive use of lambdas makes code unreadable.
vartec
the "operator" module makes a lot of lambda's unnecessary.
Gregg Lind
+8  A: 

Excessive (ab)use of the reduce function.

S.Lott
+1, guilty of abusing lambda/filter/reduce myself.
Constantin
What's the Pythonic way? Write out a loop by hand?
Ken
Yes. Reduce will often lead to horrible performance problems because of the way the function is applied through the list. Writing your own using itertools is MUCH faster.
S.Lott
Wow, that's too bad. Reduce and reduce-like operators are fantastic tools in other languages.
Robert P
@Robert P: be sure (really sure) what reduce does before you apply a poorly-thought out function. You'll often find that a function that includes it's own loop has created an **O**(_n_ ^ 2) (or worse) reduce operation.
S.Lott
And yet, it also seems unPythonic to build something by hand which exists in the stdlib because of *possible* performance problems. (I've never seen a reduce() call in a profile.) If I wanted it to run fast even at the cost of writing more code, I wouldn't be using Python.
Ken
@Ken: I'm not talking about *possible* performance problems. People use reduce successfully all the time. People abuse reduce by unknowingly giving a function that performs terribly. It is overused. It is not evil, it is not useless. It is not wrong. It is abused.
S.Lott
+19  A: 

I would say that programming in Python as if it were some other language is an "anti-pattern" i see quite often.

For example, for Java/C# refugees it is using classes for everything:

class Util():
  @staticmethod
  def foo():
    ...

# this should be just a function;
# it can be placed in 'util' module
def foo():
  ...

Another case:

class Pair():
  def __init__(self, first, second):
    ...

pairs = [Pair(1, 2), Pair(3, 4)]

# usually built-in tuple is enough
pairs = [(1, 2), (3, 4)]
Constantin
agreed, I've always despised the whole class-as-namespace way of doing things.
Doug T.
In py2.6+ you could write: Pair = collections.namedtuple('Pair', 'first second') :-) Should be just as fast as a regular tuple, but gives you attribute access too.
John Fouhy
+24  A: 
  • Using preconditional checking (exception handling in Python is cheap)

YES:

def safe_divide_2(x, y):
    try:
        return x/y
    except ZeroDivisionError:  
        print "Divide-by-0 attempt detected"
        return None

NO:

def safe_divide_1(x, y):
    if y==0:
        print "Divide-by-0 attempt detected"
        return None
    else:
        return x/y
  • Not using list comprehensions (they are much cleaner and are faster)

YES:

def double_list(items):
    return [item * 2 for item in items]

NO:

def double_list(items):
    doubled_items=[]
    for item in items:
        doubled_items.append(item*2)
    return doubled_items
  • Returning lists instead of using generators (less memory usage and cleaner)

YES:

def gen():
    for i in range(10):
        yield i

for number in gen():
    print i #prints 0-9

NO:

#list comprehension would be used here, but I did a for loop for clarity
def gen():
    numlist=[]
    for i in range(10):
        numlist.append(i)
    return numlist

for number in gen():
    print i #prints 0-9
ryeguy
For the first example you mention, sometimes it is helpful to arg checking early in a function, so it'll fail immediately, and then fail with: raise ValueError, "y cannot be 0" to actually describe *why* the function went kerblooey.
Gregg Lind
+12  A: 

Inappropriate use of isinstance.

People coming from static language backgrounds often completely miss the simplicity and flexibility of Python's dynamic polymorphism (aka duck typing).

This answer to another question provides a helpful discussion on Python polymorphism ignorance.

zweiterlinde
+1: Bad Polymorphism. See http://stackoverflow.com/questions/423823/whats-your-favorite-programmer-ignorance-pet-peeve/423857#423857
S.Lott
I agree, but will occasionally use isinstance because hasattr(x, "y") and hasattr(x, "z") and hasattr(x, "i_hate_my_life") is a pain to type and hard to read. python could use a isducktype(x, y).
TokenMacGuy
How ducky does it have to be for "isducktype" to return true? maybe make a function: def hasattrs(x,*args): return all((hasattr(x,a) for a in args))
Gregg Lind
Nice. Reminds me of how templates in C++ are (if you squint) duck typed at compile time. I.e., just use the methods/attributes you need, rather than requiring that a formal inheritance requirement be satisfied.
zweiterlinde
+4  A: 

It's mentioned as part of nikow's answer but I thought it deserved a post of its own.

  • Mixing tabs and spaces for indentation.
David Locke
Do people actually see this in the wild? At my shop, I've looked at a lot of python code, and never once seen it.
Gregg Lind
Yeah, we do see it.
foljs
+6  A: 
for i in xrange(len(something)):
    workwith = something[i]
    # do things with workwith...

From vartec's answer, but I think it's good (bad?) enough to deserve its own answer.

Paul Fisher
Even worse is when I see this and `i` is used to index into a pair of matched lists - in this case, should use `zip`!
Paul McGuire
Also, generalizing to `for item in sequence:` style opens up the code to be used on non-indexable sequences, like sets and dicts, or to accept a generator method.
Paul McGuire
+4  A: 

Not using python functions ;)

value = 0
for car in cars:
    value += car.value
return value

# instead, do
return sum(car.value for car in cars)
webjunkie
you don't need list comprehension in the last line, generator will do just fine
SilentGhost
i.e.return sum(car.value for car in cars)
hughdbrown
oh, whoops, thanks a ton!
webjunkie
+10  A: 

Using Java-style getters and setters for every field:

def get_field(self): 
   return self.field
def set_field(self, val): 
   self.field = val

It's usually better just to access the field directly, and for more advanced usage you can smoothly transition to using property().

Kiv
Those kinds of getters/setters are useless in ANY language. You're just adding bloat to what is basically a public property.
ryeguy
@ryeguy: I disagree: in Java they leave your options open to change the implementation of the getter/setter without breaking all the code that uses it (as would happen if you changed a field that other classes were directly using).
Kiv
@Kiv which wouldn't be needed if Java had proper "properties", like in C# and Objective-C.
foljs
+4  A: 

A inexhaustible source of anti-patterns: see the Zope source code and all their contributions to the cheeseshop.

+5  A: 

The Decorate-Sort-Undecorate idiom in later versions of Python where you can just use the key parameter.

deco = [(key(item), i, item) for i, item in enumerate(items)]
deco.sort()
final = [item for _, _, item in deco]

versus:

final = sorted(items, key=key)
Kiv
+5  A: 

Using positional arguments to fill keyword parameters.

e.g. given:

def foo(x, a=1, b=2):
    # etc

calling it as:

foo(14, 21)

This always bugs me, though maybe it's because I have a short memory and without the clue of the keyword (a=21) I forget what the argument means.

This is particularly prevalent in wxPython code.

John Fouhy
I catch myself doing this all the time. It's a tough habit to break, though, b/c you don't immediately feel the effects of changing foo().
Jeremy Cantrell
I do this myself, it seems some frameworks even encourage this behavior in the docs... but I see your point, had never really thought about it since I was never altering the framework.
TM
+2  A: 

Using map() or a list comprehension to perform a repeated operation on a sequence of items, instead of a for loop:

map(list.sort, list_of_lists)
[ lst.sort() for lst in list_of_lists ]

The telltale sign is that these statements create a list that is not assigned to anything. Why not just make your intent clear, that you want to iterate over a sequence and apply an operation to each item:

for lst in list_of_lists:
    lst.sort()
Paul McGuire
did you mean to have `lst.sort()` there?
SilentGhost
I guess so, thanks @SG
Paul McGuire
@Paul: I guess for map you'd need to use `lambda x: x.sort` or some such. `sort` is not a class method.
SilentGhost
@SG: no, but it *is* an instance method, taking self as its first parameter, and each entry in list_of_lists is a list that gets passed as the parameter self. `lst.sort()` is equivalent to `list.sort(lst)`. Try it.
Paul McGuire