
Python: Common Newbie Mistakes, Part 1 - Nurdok
http://blog.amir.rachum.com/post/54770419679/python-common-newbie-mistakes-part-1
======
RyanZAG
Ouch, that is one extremely non-user friendly feature going on there. The only
reason it works at all is because most people use default for strings/numbers
only.

That functionality really should be changed for the next major python release,
and have default evaluate each time the function is called - which is how
99.9% of people using it expect it to work.

~~~
mickeyp
You don't change behaviour like that once it's been introduced. That will just
break existing code in very subtle ways.

There's plenty of code out there that rely on default values being assigned a
mutable value and changing it -- even in a major point release -- would be far
worse off. Python's been around for 20 years; it's too late to make a change
like that.

It IS a "wart" in that you need to be aware of it to know that it's an issue,
but once you are, it is an obvious thing to spot.

~~~
Camillo
They made other compatibility-breaking changes in Python 3, though. It'd be
interesting to know why this particular feature was kept. I guess from another
point of view it's simpler to have everything in the `def` line executed when
that line is reached (which is when the function is defined).

------
stormbrew
The most frustrating thing that bit me when I was first learning python was
the error when you pass the wrong number of arguments to a method:

    
    
        >>> class A(object):
        ...   def method(self, a): pass
        ... 
        >>> A().method()
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        TypeError: method() takes exactly 2 arguments (1 given)
    

Once you understand more about how method dispatch works it makes sense, but
it's really confusing to be told you were doing something you don't think you
are.

~~~
_ZeD_
pass to python 3 :D

    
    
        >>> class C:
        ...   def m(self,a):
        ...     pass
        ...
        >>> C().m()
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        TypeError: m() missing 1 required positional argument: 'a'
        >>>

~~~
stormbrew
Nice. I haven't committed this particular error in python 3 yet, so it's good
to know they made it more sane.

------
smegel
Wow I have been using Python for years and didn't realize default values were
only created when defined. I don't think I have ever been bitten by it, but it
would have been one helluva bug to track down.

~~~
gizmo686
I've been bitten by that exactly once. In order to track it down, I had to
tear out pieces of my program to issolate where it is, to the point where I
was working with a single function that did nothing other than mutate a dict
that was a default value and print said value; and code that called it twice.

------
akoumjian
I've been coding in Python for 8 years now and only this year came up against
this behavior. I had a good long laugh after realizing what had just eaten up
my previous couple hours.

I actually figured it out by printing out the id of the object being modified
in my code, and when seeing it was the same had to really scratch my head.
Default args are initialized when the function (or similarly, class) is
defined, not when executed.

------
NiceOneBrah
As a newbie to Python, my biggest issue when coming up to speed on a new
project is chasing down the types of arguments to and return values from
functions. Previously I was a Java programmer and while I don't miss its
verbosity, I do miss how I always knew what types I was working with. Does
anybody have any tips that make this easier?

~~~
nknighthb
The biggest tip: The Python interactive shell is your friend. Use it to play
with things you're not sure of. IPython[1] is a more advanced Python shell you
may like. Lots of people also swear by pdb, the Python debugger. Personally, I
get less use out of it than others, but YMMV.

But overall, this is a problem involving documentation, design and naming, and
your own mindset.

Ideally you would never really be put in the position of not knowing, because
it would be clearly documented. Of course, we all know how well that tends to
work.

Second, well-designed systems with good naming practices will make it fairly
obvious most of the time what sort of arguments are expected, and what you can
expect to get back. (This is related to the next point.)

Finally, coming from a "static" language, your brain just isn't trained to
perform its own type inference and deal with duck typing. This is mostly a
matter of practice, you get better at it. But if you aren't already familiar
with various kinds of typing, be sure to do some reading. A better
understanding of the differences will help make sense of what you're seeing.

[1] [http://ipython.org/](http://ipython.org/)

~~~
sethammons
Another alternative is bpython

------
nknighthb
A simple fix to the time/now example is also useful for demonstrating first-
class functions:

    
    
        def print_now(now=time.time):
            print now()

~~~
herge
The classic way is:

    
    
        def print_now(now=None):
            now = now or time.time()
            print now

~~~
mnordhoff
I hope "now" is never 1970-01-01 00:00:00.

Edit:

    
    
        if now is None:
            now = time.time()

~~~
herge
Or even more paranoid:

import mock

def print_now(now=mock.sentinel.now): now = now if now != mock.sentinel.now
else time.time()

~~~
mnordhoff
I don't have experience with mock, but I've used object() -- literally -- as a
sentinel.

    
    
        _sentinel = object()
    
        def print_now(now=_sentinel):
            if now is _sentinel:
                now = time.time()
            ...

------
lightcatcher
When I saw the title for this post, my first thought was "hmm, mutable default
arguments are pretty tricky, hopefully that's somewhere on the list". I wasn't
disappointed.

------
tome
Cool, it looks like the Rachums are Israeli Pythonista brothers:

[https://news.ycombinator.com/item?id=5998675](https://news.ycombinator.com/item?id=5998675)

~~~
Nurdok
Yeah, we are!

------
arms
I went into this thinking "This'll be an obvious example - one I'm sure I
wouldn't make."

I was wrong, and now I know better. Thank you.

------
pyoung
Ha, that's funny. Just ran into this issue a few days ago. Spent an hour or
two trying to figure what was going on, thought I was going crazy. Finally
caved and asked an experienced python programmer, and they immediately pointed
out that lists are mutable. Goes to show the value of working with experienced
programmers.

------
bryanh
A safe pattern for this without the None check is keyword expansion which will
define defaults when a function is called.

    
    
        def foo(**kwargs):
            numbers = kwargs.pop('numbers', [])
            numbers.append(9)
            print numbers

------
ak217
I would put the unfortunate scoping design above mutable defaults on the list
of things that confused me.

Also, I really don't like the decision to make Python 2 disobey the system
locale and use ascii as the default encoding. Lots of Python programs out
there have broken i18n for no good reason because of that (nobody starts
teaching python by telling you to use .decode('utf-8')/.encode('utf-8') when
you pipe data, and by the time you realize what's wrong, you often already
have code out there.)

------
rhizome31
Also worth mentioning is the closure gotcha: when you create functions in a
loop, the functions' context points to the context of the latest iteration of
the loop. Other languages might have this issue as well and provide
alternatives based on map-style constructs. In Python, you can take advantage
of the behaviour described by the OP to make your inner functions have their
own context, which is initialized when the function gets created.

~~~
wahnfrieden
In other words, function definitions create scope.

------
t1m
Note that tuples are immutable in python. I usually use tuples as default
values for collections:

def f(x=()): x = list(x) ...

def g(y=(())) y = dict(y) ...

f() is the pattern for lists, g() is for dicts. Note that you can pass in
regular lists to f() and regular dicts to g().

------
kevinwuhoo
I've encountered a similar bug before.

    
    
      >>> arrays = [[]] * 5
      >>> print arrays
      ... [[], [], [], [], []]
      >>> arrays[0].append(1)
      >>> print arrays
      ... [[1], [1], [1], [1], [1]]

~~~
icebraining
That's not a bug, it's just the result of Python variables being references to
values, instead of actual values. When you write "[[]]", what you're doing is
creating a list that holds a reference to another list.

If you use a variable it's easier to understand:

    
    
      a = []
      arrays = [a] * 5
    

What you have now is [a, a, a, a, a], so if you modify one of the lists, it's
no wonder they all get modified: they're references to the same list.

------
Siecje
Why not use parenthesis with print so that it works for python 2 and python 3?

------
jlas
Cool. Related:
[https://news.ycombinator.com/item?id=5925467](https://news.ycombinator.com/item?id=5925467)

------
mjhea0
you could also use a generator function -

    
    
        def foo(num=[0]):
            num[0] = num[0] + 1
            return num[0]

~~~
tome
Huh? How's that a generator function?

~~~
wahnfrieden
It's not a generator, but his intention is clear - mutable keyword arguments
allow for some clever tricks.

~~~
jzwinck
They sure do--that's why we shouldn't have them. Instead:

    
    
      class Foo:
          def __init__(self):
              self.n = 0
    
          def __call__(self):
              self.n += 1
              return self.n
    
      foo = Foo()

------
sgdnogb2n
I expected far more from the article. It's not like this hasn't been blogged
about before.

------
thrownaway2424
I agree. Python is a common newbie mistake.

~~~
lostlogin
Newbie here with limited Python experience. What would you suggest? I have no
projects in mind, just want to learn and have been mashing away at Python for
months. What ever it is I make needs to run on a Mac or iDevice - via browser
is fine.

~~~
randomchars
Python is fine, parent is just a troll. If you're interested in OSX or iOS
programming Objective-C would be the obvious choice.

~~~
lostlogin
It was suggested to me that I get a grasp of basic concepts before going
there. I found it fairly easy to make a very very basic application with
series of buttons that worked etc via objective c and the interface builder,
but had no idea what I was doing when I hit code (which was the whole point of
playing around). I have a better understanding of some coding basics now that
I have done a small amount of python (Zed Shaw). Maybe it's time I went back
to objective c - you've got me thinking... Thanks.

~~~
randomchars
You're going to miss the interpreter. I started programming with python too,
and now I'm moving onto Java (for Android), and while static typing is nice, I
really miss being able to just try things out in a few seconds. Working with a
GUI framework is very different than web development and CLI.

~~~
thrownaway2424
Yeah, hacking around in ipython is nice, but you don't have to sacrifice all
of it for static typing. play.golang.org is fairly quick if you just want to
try something.

