作者 | Ari Joury
译者 | 王德朕
审校 | Noe
无论是行业领袖还是学术研究人员,都吹捧Python是编程新手最好的语言之一。他们没有错,但这并不意味着Python不会让编程新手们感到困惑。
以动态类型为例,看起来令人惊讶,Python 可以自己计算出变量可能获得的值类型,而且不需要浪费一行代码来声明类型,这样更快。
一开始是这样的,然后你在某一行搞砸了,继而导致你的整个项目在运行之前就崩溃了。
公平地说,其它语言许多都使用动态类型,但对于 Python 来说,这仅仅是一个糟糕清单的开始。
隐式声明变量会使得代码变得一团糟
几年前,当我开始攻读博士学位时,我想进一步开发一个由同事编写的现有软件,我了解它的基本原理,甚至我的同事写了一篇关于它的文档。
但我仍然需要阅读成千上万行的Python代码,以确保我知道每部分代码做了什么,从而可以把我想到的新功能放在那里,这就是问题所在......
整个代码中到处都是未被声明的变量,为了理解每个变量的用途,我必须在整个文件中搜索它,更常见的是在整个项目中搜索它。
还有一个复杂的情况,变量通常在函数内部被调用,但是当函数被调用时,又会有其他的东西被调用……还有一个情况,一个变量可以与一个类交织在一起,这个类与另一个类的另一个变量相关联,而另一个类又影响着一个完全不同的类……你明白了吧。
有这种经历的不止我一个,《Python之禅》中明确表示,显式要比隐式好,但是在Python中做隐式变量太容易了,特别是在大型项目中,很快就会遇到麻烦。
可变类型无处不在--甚至在函数中也是如此
在Python中,你可以通过提供默认值来定义具有可选参数的函数,不必再显式声明,像这样:
def add_five(a, b=0):
return a + b + 5
我知道这是个闹着玩的例子,但是你现在可以用一个或者两个参数来调用这个函数,它还是可以工作的:
add_five(3) # 返回 8
add_five(3,4) # 返回 12
它能运行,是因为表达式 b = 0将 b 定义为一个整数,而整数是不可变的:
def add_element(list=[]):
list.append("foo")
return list
add_element() # 返回 ["foo"],符合预期
到目前为止,一切正常,但是如果再次执行它会发生什么?
add_element() # returns ["foo", "foo"]! wtf!
因为参数是一个列表,即列表 ["foo"] 已经存在,Python 只是把它的东西附加到那个列表中,这样做是因为列表与整数不同,列表是可变的类型。
常言道: “疯狂就是一再重复相同的事情,却期望得到不同的结果”(这句话常常被误认为是阿尔伯特· 爱因斯坦说的)。也可以说,Python 加上可选参数,加上可变对象简直是疯了。
类变量也不安全
如果你认为这些问题仅限于可变对象作为可选参数的情况,那就错了。
如果你进行面向对象编程(几乎所有人都是这样),那么类在Python代码中无处不在,有史以来,类最有用的特性之一是——继承。
这只是一个花哨的说法,如果你有一个具有某些属性的父类,你可以创建一个子类继承其属性,像这样:
class parent(object):
x = 1
class firstchild(parent):
pass
class secondchild(parent):
pass
print(parent.x, firstchild.x, secondchild.x) # 返回 1 1 1
这不是一个特别好的例子,所以不要将其复制到你的代码项目中。关键是,子类继承了x=1,因此我们可以调用它,并得到与父类相同的结果。
而且,如果我们改变了一个子类的x属性,它应该只改变那个子类。就像你在青少年时期染了头发,它不会改变你父母或你兄弟姐妹的头发,这样就可以了。
firstchild.x = 2
print(parent.x, firstchild.x, secondchild.x) # 返回 1 2 1
你小时候妈妈染头发的时候发生了什么? 你的头发没变,对吧?
parent.x = 3
print(parent.x, firstchild.x, secondchild.x) # 返回3 2 3
这是因为 Python 的方法解析顺序,只要没有特殊的说明,子类继承了父类的一切,所以,在Python世界中,如果你不提前抗议,妈妈在做她的头发时就会给你染发。
作用域有时候会反过来
接下来这个关卡已经绊倒我很多次了。
在 Python 中,如果在函数内部定义变量,那么这个变量不会在函数外部工作,有人说这超出了作用域:
def myfunction(number):
basenumber = 2
return basenumber*number
basenumber
## Oh no! This is the error:
# Traceback (most recent call last):
# File "", line 1, in
# NameError: name 'basenumber' is not defined
这应该是相当直观的(不,我没有在这一点上绊倒)。
那反过来呢?我的意思是,如果我在函数外面定义一个变量,然后在函数内部引用它,会怎么样?
x = 2
def add_5():
x = x + 5
print(x)
add_5()
## Oh dear...
# Traceback (most recent call last):
# File "", line 1, in
# File "", line 2, in add_y
# UnboundLocalError: local variable 'x' referenced before assignment
奇怪吧?如果阿尔伯特生活在一个有树的世界里,并且阿尔伯特生活在一所房子里,那么阿尔伯特想必是知道树是什么样子的?(树是x,阿尔伯特的房子是add_ 5(),阿尔伯特是5……)
我曾多次碰到这个问题,在一个类中,定义被另一个类调用的函数时,我花了很长时间才找到问题的根源。
这背后的想法是,函数内部的x与外部的x是不同的,所以你不能就这样改变它。就像如果阿尔伯特只是梦想着把树变成橙色,那当然不会让树实际变成橙色。
幸运的是,这个问题有一个简单的解决方案,只要在 x 之前添加一个 global!
x = 2
def add_5():
global x
x = x + 5
print(x)
add_5() # works!
因此,如果你认为作用域只能保护函数内部的变量不受外部世界的影响,那么请再考虑一下。在 Python 中,外部世界受到局部变量的保护,就像阿尔伯特不能用他思想的力量把树涂成橙色一样。
在迭代列表时修改列表
我自己也遇到过几次这样的胡说八道。
想想这个:
mynumbers = [x for x in range(10)]
# this is [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for x in range(len(mynumbers)):
if mynumbers[x]%3 == 0:
mynumbers.remove(mynumbers[x])
## Ew!
# Traceback (most recent call last):
# File "", line 2, in
# IndexError: list index out of range
这个循环不起作用,因为它每隔一段时间就会删除列表中的一个元素。因此,列表的末端会向前移动,那么就不可能到达10号元素了,因为它已经不在那里了!
一个简单但方便的解决方案,为所有要删除的元素分配一个不实用的值,然后在下一步中删除它们。
但有一个更好的解决办法:
mynumbers = [x for x in range(10) if x%3 != 0]
# that's what we wanted! [1, 2, 4, 5, 7, 8]
就一行代码!
注意,我们已经在上面的案例中,使用了 Python 列表解析式来调用列表。
它是方括号[] 中的表达式,是循环的简写形式,列表解析式通常比常规循环快一点,如果你处理的是大型数据集,这很酷。
在这里,我们只是添加了一个 if 子句 来告诉列表解析式,它不应该包含被3整除的数字。
与上面描述的一些现象不同,即使初学者一开始可能会在这个这问题上磕磕绊绊,列表解析也不是 Python 糟糕的设计,而是 Python 的天才设计。
地平线上的一些光亮
在过去,当遇到与 Python 相关的问题时,编码并不是唯一的痛苦。Python的执行速度也曾经慢得令人难以置信,比大多数语言都慢2到10倍。现在这种情况已经好了很多,例如,Numpy 包在处理列表、矩阵等等方面非常快。
使用Python,多进程也变得更加容易。这可以让你使用所有的2个、16个或多个核心的计算机,而不是只有一个。我已经在20个核心上运行过,它已经为我节省了数周的计算时间。
此外,随着机器学习在过去几年中取得进展,Python 已经表明,它还有很长的路要走。像 Pytorch 和 Tensorflow 这样的软件包使得机器学习变得非常容易,而其他语言正在努力跟上这一步。
这些年来 Python 已经变得更好了,然而,这一事实并不能保证一个美好的未来,Python仍然不是傻瓜式的,请谨慎地使用它。
译者介绍
王德朕,51CTO社区编辑,10年互联网产研经验,6年IT教培行业经验。原K12教育上市公司产品经理,技术博客专家,蓝桥签约作者,《滚雪球学Python》专栏作者,《爬虫100例》专栏特约作者,78技术人社区发起者。
原文标题:Python may be easy but it’s a goddamn mess
链接:https://thenextweb.com/news/python-may-be-easy-but-its-a-mess