Python 也许很友好,但也很容易得到一团槽
在混乱发生之前,它对初学者很友好
无论是行业领袖还是学术研究人员,都吹捧 Python 是编程新手最好的语言之一。他们没有错,但这并不意味着 Python 不会让编程新手感到困惑。
以动态类型为例,它看起来令人惊讶,Python 可以自己计算变量可能获得的值类型,不需要浪费一行代码来声明类型,这样更快。
一开始是这样的,然后你在某个行业搞砸了,然后你的整个项目在运行前就崩溃了。
公平地说,许多其他语言使用动态类型,但对于 Python 这只是坏名单的开始。
隐式声明变量会使代码变得一团糟
几年前,当我开始攻读博士学位时,我想进一步开发一个由同事编写的现有软件。我知道它的基本原理,甚至我的同事也写了一篇关于它的文档。
但我仍然需要阅读成千上万行 Python 为了确保我知道代码的每一部分都做了什么,这样我就可以把我想到的新功能放在那里,这就是问题所在…
整个代码中到处都是未声明的变量。为了理解每个变量的用途,我必须在整个文件中搜索,更常见的是在整个项目中搜索。
另一个复杂的情况是,变量通常在函数中被调用,但当函数被调用时,其他东西会被调用……另一种情况是,一个变量可以与一个类交织在一起,它与另一个类的另一个变量相关,另一个类影响完全不同的类……你明白了。
我不是唯一一个有这种经历的人,Zen of Python 明确表示显式比隐式好,但在 Python 做隐式变量太容易了,尤其是在大型项目中,shit*t 很快就受欢迎了。
可变类型无处不在–即使在函数中也是如此
在 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 仍然不是傻瓜式的,请谨慎地使用它。
这篇文章最初发表在 Medium 上,你可以在这里阅读。
原文标题:Python may be easy but it’s a goddamn mess
链接:https://thenextweb.com/news/python-may-be-easy-but-its-a-mess