写点什么

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

作者:梦想橡皮擦
  • 2022 年 8 月 12 日
    河北
  • 本文字数:3212 字

    阅读完需:约 11 分钟

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) # 返回 8add_five(3,4) # 返回  12
复制代码


它能运行,是因为表达式 b = 0 将 b 定义为一个整数,而整数是不可变的:


def add_element(list=[]):  list.append("foo")  return listadd_element() # 返回 ["foo"],符合预期
复制代码


到目前为止,一切正常,但是如果再次执行它会发生什么?


add_element() # returns ["foo", "foo"]! wtf!
复制代码


因为参数是一个列表,即列表 ["foo"] 已经存在,Python 只是把它的东西附加到那个列表中,这样做是因为列表与整数不同,列表是可变的类型。


常言道: “疯狂就是一再重复相同的事情,却期望得到不同的结果”(这句话常常被误认为是阿尔伯特 · 爱因斯坦说的)。也可以说,Python 加上可选参数,加上可变对象简直是疯了。

类变量也不安全

如果你认为这些问题仅限于可变对象作为可选参数的情况,那就错了。


如果你进行面向对象编程(几乎所有人都是这样),那么类在 Python 代码中无处不在,有史以来,类最有用的特性之一是……(噔噔蹬蹬)继承。


这只是一个花哨的说法,如果你有一个具有某些属性的父类,你可以创建一个子类继承其属性,像这样:


class parent(object):  x = 1class firstchild(parent):  passclass secondchild(parent):  passprint(parent.x, firstchild.x, secondchild.x) # 返回 1 1 1
复制代码


这不是一个特别好的例子,所以不要将其复制到你的代码项目中。关键是,子类继承了 x=1,因此我们可以调用它,并得到与父类相同的结果。


而且,如果我们改变了一个子类的 x 属性,它应该只改变那个子类。就像你在青少年时期染了头发,它不会改变你父母或你兄弟姐妹的头发,这样就可以了。


firstchild.x = 2print(parent.x, firstchild.x, secondchild.x) # 返回 1 2 1
复制代码


你小时候妈妈染头发的时候发生了什么? 你的头发没变,对吧?


parent.x = 3print(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 = 2def 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 = 2def add_5():  global x  x = x + 5print(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 上,你可以在这里阅读。


发布于: 刚刚阅读数: 3
用户头像

爬虫 100 例作者,蓝桥签约作者,博客专家 2021.02.06 加入

6 年产品经理+教学经验,3 年互联网项目管理经验; 互联网资深爱好者; 沉迷各种技术无法自拔,导致年龄被困在 25 岁; CSDN 爬虫 100 例作者。 个人公众号“梦想橡皮擦”。

评论

发布
暂无评论
Python也许很友好,但它也容易弄得一团槽_Python_梦想橡皮擦_InfoQ写作社区