Python|关于 Python,一些不得不吐槽的“迷惑行为”

Python|关于 Python,一些不得不吐槽的“迷惑行为”

长期以来 , Python一直自诩是最适合新手程序员的语言之一 。 话虽没错 , 但这并不意味着编程新手不会对Python的一些行为感到困惑 。
举个例子 , 动态类型 。 你无需单独编写一行代码来定义变量的类型 , Python能够自行分辨 , 乍一看之下 , 这似乎很神奇 。 感觉这样编程速度更快 。
然而 , 就因为少了一行变量定义 , 整个项目在运行结束之前就有可能崩溃 。
说句公道话 , 许多其他编程语言也使用动态类型 。 但对于Python而言 , 这只是一系列噩梦的开始 。


隐式的变量声明会影响阅读代码几年前 , 我想在同事编写的一个软件的基础之上 , 进行二次开发 。 我知道该软件的基本思想 , 我的同事甚至写了一篇论文作为该软件的文档 。
但是 , 我仍然需要阅读数千行 Python 代码 , 才能搞清楚各个部分在干什么 , 以及我可以将新功能放到哪里 。 然而 , 就在这个过程中 , 我遇到了很大的问题……
纵观整个代码库 , 变量声明到处都是 。 为了搞清楚每个变量的用途 , 我不得不搜索整个文件 , 甚至是整个项目 。
此外 , 还有各种各样的复杂情况 , 比如函数的某个参数的名字和调用该函数时使用的变量完全不同 , 或者一个变量与某个类紧密结合 , 而该类又和另一个类中的某个变量交织在一起……诸如此类的事情层出不穷 。
很多人都有类似的感觉 , 有人就曾表示显式变量声明优于隐式(参考链接:https://peps.python.org/pep-0020/) 。 但是 , 在Python中隐式变量声明比比皆是 , 尤其是在大型项目中 。
无处不在的可变类型 , 甚至在函数中在 Python 中 , 定义函数的时候可以指定可选参数 , 即不需要明确指定的参数 。 如下所示:
def add_five(a b=0):return a + b + 5

通过这个简单的示例可以看出 , 在调用函数时 , 无论指定一个参数还是两个参数都可以:
add_five(3) # returns 8add_five(34) # returns 12

之所以会出现这种现象 , 是因为表达式b=0定义了b是一个整数 , 而整数是不可变的 。 再看看下面这个例子
def add_element(list=[
):list.append(\"foo\")return listadd_element() # returns [\"foo\"
as expected

发现问题了吗?再执行一次会怎么样?
add_element() # returns [\"foo\" \"foo\"
! wtf!

因为这里的list已经存在 , 即[\"foo\"
, 而Python会继续向这个列表添加新东西 。 这是因为列表与整数不同 , 是可变类型 。
我不禁想起一句话:“疯子就是不断重复同一件事 , 却期待不同的结果 。 ”(经考证 , 这句话不是爱因斯坦说的) 。 我想说 , Python + 可选参数 + 可变对象 = 疯子 。
类变量并不安全如果你认为上述问题仅限于可变对象作为可选参数的时候 , 那你就大错特错了 。
相信你也使用Python编写面向对象的代码 , 在Python代码中类无处不在 。 而类最实用的特性之一便是:继承 。
简单来说 , 如果父类具有某些属性 , 子类就可以继承这些属性 。 如下所示:
class parent(object):x = 1class firstchild(parent):passclass secondchild(parent):pass print(parent.x firstchild.x secondchild.x) # returns 1 1 1

注意 , 这段代码写得并不好 , 不要复制到实际的项目中 。 关键在于 , 子类继承了 x = 1 , 因此我们可以获取子类的这个属性 , 得到的结果与父类相同 。
如果我们修改某个子类的x属性 , 那么理应说变化的只有这个子类 。 就好像孩子染发不可能改变父母亲或兄弟姐妹的发色 。 代码如下: