现在的位置: 首页 > 综合 > 正文

Python: Common Newbie Mistakes, Part 1

2014年09月14日 ⁄ 综合 ⁄ 共 4730字 ⁄ 字号 评论关闭

In the past few months I’ve been helping some people who are new to Python to get to know the language. I found that there are some pitfalls that almost everyone meet when they’re new to the language, so I decided to share my advice with you. Each part of this
series will focus on a different common mistake, describe what causes it and offer a solution.

Using a Mutable Value as a Default Value

This one definitely deserves its place at the top of the list. Not only is the reason for this “bug” subtle, it’s also very hard to debug. Consider the following snippet1:

1
2
3
def foo(numbers=[]):
    numbers.append(9)
    print numbers

So we take a list (defaults to an empty list), add 9 to it and print it.

1
2
3
4
5
6
>>> foo()
[9]
>>> foo(numbers=[1,2])
[1, 2, 9]
>>> foo(numbers=[1,2,3])
[1, 2, 3, 9]

Seems good, right? Except this is what happens when we call foo without
numbers:

1
2
3
4
5
6
7
8
>>> foo() # first time, like before
[9]
>>> foo() # second time
[9, 9]
>>> foo() # third time...
[9, 9, 9]
>>> foo() # WHAT IS THIS BLACK MAGIC?!
[9, 9, 9, 9]

So what’s happening here? Our instincts tell us that whenever we call foo without numbers,
then numbers will be assigned with
an empty list. This is wrong. Default values for functions in Python are instantiated when the function is defined, not when it’s called.

Still, you’d ask, why isn’t the same pre-calculated value reassigned every time I call the function? Well, every time you specify a default value for a function, Python stores that value. If you call the function and override that default, the stored value
is not used. When you don’t override it (like in our example), Python makes the name you defined (numbers,
in our case) reference the stored value. It does not copy the stored value to your variable. This may be a difficult concept for beginners, so imagine that instead of two distinct variables (one – the internal, initial default value and the other – the current,
local variable), what we have in fact is two names that allow us to interact with the same value. So whenever numbers changes,
it also changes Python’s memory of what the initial value is.

The solution here is this2:

1
2
3
4
5
def foo(numbers=None):
    if numbers is None:
        numbers = []
    numbers.append(9)
    print numbers

More often than not, when people hear about this, they ask how come other default values do work as expected. Consider this:

1
2
3
def foo(count=0):
    count += 1
    print count

When we run it, it works percisely as expected:

1
2
3
4
5
6
7
8
9
10
>>> foo()
1
>>> foo()
1
>>> foo(2)
3
>>> foo(3)
4
>>> foo()
1

Why is that? The “secret” here is not in the default value assignment, but in the value itself. An integer is an immutable type. While, like the empty list, it is executed in the definition of the function, it cannot be changed. When we do count
+= 1
 we do not change the original value of count. We simply make the name count point
to a different value. However, when we donumbers.append(9) we
do change the original list. Hence, the confusion.

There is another variety of this same problem when you try to call a function as the default value:

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

As before, while the value of time.time() is
immutable, it is calculated only when the function is defined, so it’ll always return the same time – the time when the function code was parsed by Python:

1
2
3
4
5
6
>>> print_now()
1373121487.91
>>> print_now()
1373121487.91
>>> print_now()
1373121487.91

在之前几个月里,我教一些不了解Python的孩子来慢慢熟悉这门语言。渐渐地,我发现了一些几乎所有Python初学者都会犯的错误,所以我决定跟来跟大家分享我的建议。这个系列的每个部分都会关注不同的常见错误,描述如何产生这种错误的,并且提供解决的方法。

用一个可变的值作为默认值

这是一个绝对值得放在第一个来说的问题。不仅仅是因为产生这种BUG的原因很微妙,而且这种问题也很难检查出来。思考一下下面的代码片段:

  1. def foo(numbers=[]): 
  2.     numbers.append(9
  3.     print numbers 

在这里,我们定义了一个 list (默认为空),给它加入9并且打印出来。

  1. >>> foo() 
  2. [9
  3. >>> foo(numbers=[1,2]) 
  4. [129
  5. >>> foo(numbers=[1,2,3]) 
  6. [1239

看起来还行吧?可是当我们不输入number 参数来调用 foo 函数时,神奇的事情发生了:

  1. >>> foo() # first time, like before 
  2. [9
  3. >>> foo() # second time 
  4. [99
  5. >>> foo() # third time... 
  6. [999
  7. >>> foo() # WHAT IS THIS BLACK MAGIC?! 
  8. [9999

那么,这是神马情况?直觉告诉我们无论我们不输入 number 参数调用 foo 函数多少次,这里的9应该被分配进了一个空的 list。这是错的!在Python里,函数的默认值实在函数定义的时候实例化的,而不是在调用的时候。

那么我们仍然会问,为什么在调用函数的时候这个默认值却被赋予了不同的值?因为在你每次给函数指定一个默认值的时候,Python都会存储这个值。 如果在调用函数的时候重写了默认值,那么这个存储的值就不会被使用。当你不重写默认值的时候,那么Python就会让默认值引用存储的值(这个例子里的 numbers)。它并不是将存储的值拷贝来为这个变量赋值。这个概念可能对初学者来说,理解起来会比较吃力,所以可以这样来理解:有两个变量,一个是内 部的,一个是当前运行时的变量。现实就是我们有两个变量来用相同的值进行交互,所以一旦
numbers 的值发生变化,也会改变Python里面保存的初始值的记录。

那么解决方案如下:

  1. def foo(numbers=None): 
  2.     if numbers is None
  3.         numbers = [] 
  4.     numbers.append(9
  5.     print numbers 

通常,当人们听到这里,大家会问另一个关于默认值的问题。思考下面的程序:

  1. def foo(count=0): 
  2.     count += 1 
  3.     print count 

当我们运行它的时候,其结果完全是我们期望的:

  1. >>> foo() 
  2. 1 
  3. >>> foo() 
  4. 1 
  5. >>> foo(2
  6. 3 
  7. >>> foo(3
  8. 4 
  9. >>> foo() 
  10. 1 

这又是为啥呢?其秘密不在与默认值被赋值的时候,而是这个默认值本身。整型是一种不可变的变量。跟 list 类型不同,在函数执行的过程中,整型变量是不能被改变的。当我们执行 count+=1 这句话时,我们并没有改变 count 这个变量原有的值。而是让 count 指向了不同的值。可是,当我们执行 numbers.append(9) 的时候,我们改变了原有的 list 。因而导致了这种结果。下面是在函数里使用默认值时会碰到的另一种相同问题:

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

跟前面一样,time.time() 的值是可变的,那么它只会在函数定义的时候计算,所以无论调用多少次,都会返回相同的事件 — 这里输出的事件是程序被Python解释运行的时间。

  1. >>> print_now() 
  2. 1373121487.91 
  3. >>> print_now() 
  4. 1373121487.91 
  5. >>> print_now() 
  6. 1373121487.91 

* 这个问题和它的解决方案在 Python 2.x 和 3.x 里都是类似的,在Python 3.x 里面唯一的不同,是里面的print 表达式应该是函数调用的方式(print(numbers))。

* 大家应该注意到我在解决方案里用了 if  numbers is None 而不是 if not numbers 。这是另一种常见的错误,我准备在接下来的文章里面介绍。

抱歉!评论已关闭.