学习用 Python 编程时要避免的 3 个错误
Python

> 
> 这些错误会造成很麻烦的问题，需要数小时才能解决。
> 
> 
> 


![](/data/attachment/album/201708/16/000829y5u4wm1kou8d55z6.jpg)


当你做错事时，承认错误并不是一件容易的事，但是犯错是任何学习过程中的一部分，无论是学习走路，还是学习一种新的编程语言都是这样，比如学习 Python。


为了让初学 Python 的程序员避免犯同样的错误，以下列出了我学习 Python 时犯的三种错误。这些错误要么是我长期以来经常犯的，要么是造成了需要几个小时解决的麻烦。


年轻的程序员们可要注意了，这些错误是会浪费一下午的！


### 1、 可变数据类型作为函数定义中的默认参数


这似乎是对的？你写了一个小函数，比如，搜索当前页面上的链接，并可选将其附加到另一个提供的列表中。



```
def search_for_links(page, add_to=[]):
    new_links = page.search_for_links()
    add_to.extend(new_links)
    return add_to

```

从表面看，这像是十分正常的 Python 代码，事实上它也是，而且是可以运行的。但是，这里有个问题。如果我们给 `add_to` 参数提供了一个列表，它将按照我们预期的那样工作。但是，如果我们让它使用默认值，就会出现一些神奇的事情。


试试下面的代码：



```
def fn(var1, var2=[]):
    var2.append(var1)
    print var2

fn(3)
fn(4)
fn(5)

```

可能你认为我们将看到：



```
[3]
[4]
[5]

```

但实际上，我们看到的却是：



```
[3]
[3, 4]
[3, 4, 5]

```

为什么呢？如你所见，每次都使用的是同一个列表，输出为什么会是这样？在 Python 中，当我们编写这样的函数时，这个列表被实例化为函数定义的一部分。当函数运行时，它并不是每次都被实例化。这意味着，这个函数会一直使用完全一样的列表对象，除非我们提供一个新的对象：



```
fn(3, [4])

```


```
[4, 3]

```

答案正如我们所想的那样。要想得到这种结果，正确的方法是：



```
def fn(var1, var2=None):
    if not var2:
        var2 = []
    var2.append(var1)
```

或是在第一个例子中：



```
def search_for_links(page, add_to=None):
    if not add_to:
        add_to = []
    new_links = page.search_for_links()
    add_to.extend(new_links)
    return add_to
```

这将在模块加载的时候移走实例化的内容，以便每次运行函数时都会发生列表实例化。请注意，对于不可变数据类型，比如[**元组**](https://docs.python.org/2/library/functions.html?highlight=tuple#tuple)、[**字符串**](https://docs.python.org/2/library/string.html)、[**整型**](https://docs.python.org/2/library/functions.html#int)，是不需要考虑这种情况的。这意味着，像下面这样的代码是非常可行的：



```
def func(message="my message"):
    print message
```

### 2、 可变数据类型作为类变量


这和上面提到的最后一个错误很相像。思考以下代码：



```
class URLCatcher(object):
    urls = []

    def add_url(self, url):
        self.urls.append(url)
```

这段代码看起来非常正常。我们有一个储存 URL 的对象。当我们调用 add\_url 方法时，它会添加一个给定的 URL 到存储中。看起来非常正确吧？让我们看看实际是怎样的：



```
a = URLCatcher()
a.add_url('http://www.google.com')
b = URLCatcher()
b.add_url('http://www.bbc.co.hk')
```

b.urls：



```
['http://www.google.com', 'http://www.bbc.co.uk']

```

a.urls：



```
['http://www.google.com', 'http://www.bbc.co.uk']

```

等等，怎么回事？！我们想的不是这样啊。我们实例化了两个单独的对象 `a` 和 `b`。把一个 URL 给了 `a`，另一个给了 `b`。这两个对象怎么会都有这两个 URL 呢？


这和第一个错例是同样的问题。创建类定义时，URL 列表将被实例化。该类所有的实例使用相同的列表。在有些时候这种情况是有用的，但大多数时候你并不想这样做。你希望每个对象有一个单独的储存。为此，我们修改代码为：



```
class URLCatcher(object):
    def __init__(self):
        self.urls = []

    def add_url(self, url):
        self.urls.append(url)
```

现在，当创建对象时，URL 列表被实例化。当我们实例化两个单独的对象时，它们将分别使用两个单独的列表。


### 3、 可变的分配错误


这个问题困扰了我一段时间。让我们做出一些改变，并使用另一种可变数据类型 - [**字典**](https://docs.python.org/2/library/stdtypes.html?highlight=dict#dict)。



```
a = {'1': "one", '2': 'two'}

```

现在，假设我们想把这个字典用在别的地方，且保持它的初始数据完整。



```
b = a

b['3'] = 'three'
```

简单吧？


现在，让我们看看原来那个我们不想改变的字典 `a`：



```
{'1': "one", '2': 'two', '3': 'three'}

```

哇等一下，我们再看看 **b**？



```
{'1': "one", '2': 'two', '3': 'three'}

```

等等，什么？有点乱……让我们回想一下，看看其它不可变类型在这种情况下会发生什么，例如一个**元组**：



```
c = (2, 3)
d = c
d = (4, 5)

```

现在 `c` 是 `(2, 3)`，而 `d` 是 `(4, 5)`。


这个函数结果如我们所料。那么，在之前的例子中到底发生了什么？当使用可变类型时，其行为有点像 **C** 语言的一个指针。在上面的代码中，我们令 `b = a`，我们真正表达的意思是：`b` 成为 `a` 的一个引用。它们都指向 Python 内存中的同一个对象。听起来有些熟悉？那是因为这个问题与先前的相似。其实，这篇文章应该被称为「可变引发的麻烦」。


列表也会发生同样的事吗？是的。那么我们如何解决呢？这必须非常小心。如果我们真的需要复制一个列表进行处理，我们可以这样做：



```
b = a[:]

```

这将遍历并复制列表中的每个对象的引用，并且把它放在一个新的列表中。但是要注意：如果列表中的每个对象都是可变的，我们将再次获得它们的引用，而不是完整的副本。


假设在一张纸上列清单。在原来的例子中相当于，A 某和 B 某正在看着同一张纸。如果有个人修改了这个清单，两个人都将看到相同的变化。当我们复制引用时，每个人现在有了他们自己的清单。但是，我们假设这个清单包括寻找食物的地方。如果“冰箱”是列表中的第一个，即使它被复制，两个列表中的条目也都指向同一个冰箱。所以，如果冰箱被 A 修改，吃掉了里面的大蛋糕，B 也将看到这个蛋糕的消失。这里没有简单的方法解决它。只要你记住它，并编写代码的时候，使用不会造成这个问题的方式。


字典以相同的方式工作，并且你可以通过以下方式创建一个昂贵副本：



```
b = a.copy()

```

再次说明，这只会创建一个新的字典，指向原来存在的相同的条目。因此，如果我们有两个相同的列表，并且我们修改字典 `a` 的一个键指向的可变对象，那么在字典 b 中也将看到这些变化。


可变数据类型的麻烦也是它们强大的地方。以上都不是实际中的问题；它们是一些要注意防止出现的问题。在第三个项目中使用昂贵复制操作作为解决方案在 99% 的时候是没有必要的。你的程序或许应该被改改，所以在第一个例子中，这些副本甚至是不需要的。


*编程快乐！在评论中可以随时提问。*




---


作者简介：


Pete Savage - Peter 是一位充满激情的开源爱好者，在过去十年里一直在推广和使用开源产品。他从 Ubuntu 社区开始，在许多不同的领域自愿参与音频制作领域的研究工作。在职业经历方面，他起初作为公司的系统管理员，大部分时间在管理和建立数据中心，之后在 Red Hat 担任 CloudForms 产品的主要测试工程师。




---


via: <https://opensource.com/article/17/6/3-things-i-did-wrong-learning-python>


作者：[Pete Savage](https://opensource.com/users/psav) 译者：[polebug](https://github.com/polebug) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
It's never easy to admit when you do things wrong, but making errors is part of any learning process, from learning to walk to learning a new programming language, such as Python.

Here's a list of three things I got wrong when I was learning Python, presented so that newer Python programmers can avoid making the same mistakes. These are errors that either I got away with for a long time or that that created big problems that took hours to solve.

Take heed young coders, some of these mistakes are afternoon wasters!

## 1. Mutable data types as default arguments in function definitions

It makes sense right? You have a little function that, let's say, searches for links on a current page and optionally appends it to another supplied list.

```
``````
def search_for_links(page, add_to=[]):
new_links = page.search_for_links()
add_to.extend(new_links)
return add_to
```


On the face of it, this looks like perfectly normal Python, and indeed it is. It works. But there are issues with it. If we supply a list for the **add_to** parameter, it works as expected. If, however, we let it use the default, something interesting happens.

Try the following code:

```
``````
def fn(var1, var2=[]):
var2.append(var1)
print var2
fn(3)
fn(4)
fn(5)
```


You may expect that we would see:

**[3]
[4]
[5]**

But we actually see this:

**[3]
[3, 4]
[3, 4, 5]**

Why? Well, you see, the same list is used each time. In Python, when we write the function like this, the list is instantiated as part of the function's definition. It is not instantiated each time the function is run. This means that the function keeps using the exact same list object again and again, unless of course we supply another one:

```
````fn(3, [4])`


**[4, 3]**

Just as expected. The correct way to achieve the desired result is:

```
``````
def fn(var1, var2=None):
if not var2:
var2 = []
var2.append(var1)
```


Or, in our first example:

```
``````
def search_for_links(page, add_to=None):
if not add_to:
add_to = []
new_links = page.search_for_links()
add_to.extend(new_links)
return add_to
```


This moves the instantiation from module load time so that it happens every time the function runs. Note that for immutable data types, like [ tuples](https://docs.python.org/2/library/functions.html?highlight=tuple#tuple),

[, or](https://docs.python.org/2/library/string.html)

**strings**[, this is not necessary. That means it is perfectly fine to do something like:](https://docs.python.org/2/library/functions.html#int)

**ints**```
``````
def func(message="my message"):
print message
```


## 2. Mutable data types as class variables

Hot on the heels of the last error is one that is very similar. Consider the following:

```
``````
class URLCatcher(object):
urls = []
def add_url(self, url):
self.urls.append(url)
```


This code looks perfectly normal. We have an object with a storage of URLs. When we call the **add_url** method, it adds a given URL to the store. Perfect right? Let's see it in action:

```
``````
a = URLCatcher()
a.add_url('http://www.google.')
b = URLCatcher()
b.add_url('http://www.bbc.co.')
```


**b.urls
[' http://www.google.com', 'http://www.bbc.co.uk']**

**a.urls
[' http://www.google.com', 'http://www.bbc.co.uk']**

Wait, what?! We didn't expect that. We instantiated two separate objects, **a** and **b**. **A** was given one URL and **b** the other. How is it that both objects have both URLs?

Turns out it's kinda the same problem as in the first example. The URLs list is instantiated when the class definition is created. All instances of that class use the same list. Now, there are some cases where this is advantageous, but the majority of the time you don't want to do this. You want each object to have a separate store. To do that, we would modify the code like:

```
``````
class URLCatcher(object):
def __init__(self):
self.urls = []
def add_url(self, url):
self.urls.append(url)
```


Now the URLs list is instantiated when the object is created. When we instantiate two separate objects, they will be using two separate lists.

## 3. Mutable assignment errors

This one confused me for a while. Let's change gears a little and use another mutable datatype, the [ dict](https://docs.python.org/2/library/stdtypes.html?highlight=dict#dict).

```
``````
a = {'1': "one", '2': 'two'}
```


Now let's assume we want to take that **dict** and use it someplace else, leaving the original intact.

```
``````
b = a
b['3'] = 'three'
```


Simple eh?

Now let's look at our original dict, **a**, the one we didn't want to modify:

```
````{'1': "one", '2': 'two', '3': 'three'}`


Whoa, hold on a minute. What does **b** look like then?

```
````{'1': "one", '2': 'two', '3': 'three'}`


Wait what? But… let's step back and see what happens with our other immutable types, a **tuple** for instance:

```
``````
c = (2, 3)
d = c
d = (4, 5)
```


Now **c** is:

**(2, 3)**

While **d** is:

**(4, 5)**

That functions as expected. So what happened in our example? When using mutable types, we get something that behaves a little more like a pointer from C. When we said **b = a** in the code above, what we really meant was: **b** is now also a reference to **a**. They both point to the same object in Python's memory. Sound familiar? That's because it's similar to the previous problems. In fact, this post should really have been called, "The Trouble with Mutables."

Does the same thing happen with lists? Yes. So how do we get around it? Well, we have to be very careful. If we really need to copy a list for processing, we can do so like:

```
````b = a[:]`


This will go through and copy a reference to each item in the list and place it in a new list. But be warned: If any objects in the list are mutable, we will again get references to those, rather than complete copies.

Imagine having a list on a piece of paper. In the original example, Person A and Person B are looking at the same piece of paper. If someone changes that list, both people will see the same changes. When we copy the references, each person now has their own list. But let's suppose that this list contains places to search for food. If "fridge" is first on the list, even when it is copied, both entries in both lists point to the same fridge. So if the fridge is modified by Person A, by say eating a large gateaux, Person B will also see that the gateaux is missing. There is no easy way around this. It is just something that you need to remember and code in a way that will not cause an issue.

Dicts function in the same way, and you can create this expensive copy by doing:

```
````b = a.copy()`


Again, this will only create a new dictionary pointing to the same entries that were present in the original. Thus, if we have two lists that are identical and we modify a mutable object that is pointed to by a key from dict 'a', the dict object present in dict 'b' will also see those changes.

The trouble with mutable data types is that they are powerful. None of the above are real problems; they are things to keep in mind to prevent issues. The expensive copy operations presented as solutions in the third item are unnecessary 99% of the time. Your program can and probably should be modified so that those copies are not even required in the first place.

*Happy coding! And feel free to ask questions in the comments.*

## 7 Comments