当你做错事时,承认过错并不是一件简略的事,可是犯错是任何学习过程中的一部分,无论是学习走路,仍是学习一种新的编程言语都是这样,比方学习 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
这将在模块加载的时分移走实例化的内容,以便每次运转函数时都会产生列表实例化。请留意,关于不行变数据类型,比方元组、字符串、整型,是不需求考虑这种状况的。这意味着,像下面这样的代码对错常可行的:
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、 可变的分配过错
这个问题困扰了我一段时间。让咱们做出一些改动,并运用另一种可变数据类型 – 字典。
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% 的时分是没有必要的。你的程序或许应该被改改,所以在第一个比方中,这些副本乃至是不需求的。