Python也许很友好,但它也容易弄得一团槽

译文 精选
开发
直到出现问题前,Python对初学者都是友好的

  作者 | 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

责任编辑:张洁
相关推荐

2009-12-16 09:52:15

Linux操作系统

2019-07-08 11:25:14

云计算工具Kubernetes

2021-02-24 15:09:51

编程技能开发

2011-07-04 10:08:59

LinuxARM

2019-03-11 15:26:26

HTTPSHTTP密钥

2019-10-25 09:35:58

HTTPSHTTP通信

2019-11-13 09:08:50

HTTPS安全加密算法

2024-06-17 10:30:38

运维IP地址网络

2024-12-03 15:42:13

命令模式代码

2011-12-29 16:37:21

笔记本评测

2022-08-26 09:02:57

代码库编程语言

2019-11-15 09:26:36

OAuthWeb系统

2011-12-21 17:04:53

云计算

2019-11-05 09:20:06

SQLiteLinux

2024-08-23 15:02:23

2012-02-29 08:54:54

甲骨文云计算

2022-01-04 10:19:23

架构运维技术

2011-12-29 09:03:30

云计算

2023-05-09 12:27:52

亚马逊微服务重构

2018-07-04 11:01:48

点赞
收藏

51CTO技术栈公众号