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

举报
梦想橡皮擦 发表于 2022/08/04 14:03:45 2022/08/04
【摘要】 Python 也许很友好,但它也容易弄得一团槽直到出现混乱前,它对初学者都是友好的无论是行业领袖还是学术研究人员,都吹捧 Python 是编程新手最好的语言之一。他们没有错,但这并不意味着 Python 不会让编程新手们感到困惑。以动态类型为例,看起来令人惊讶,Python 可以自己计算出变量可能获得的值类型,而且不需要浪费一行代码来声明类型,这样更快。一开始是这样的,然后你在某一行搞砸了...

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 上,你可以在这里阅读。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。