花下猫语:前两天,我偶然在一个知识星球(刘欣老师的“码农翻身”)里看到一篇主题,刘老师表示 Python 的类方法非要带个 self,而不像其它语言那样隐藏起来,这让人很不爽。我对此也有同感。在通过群聊讨论后,我获知 Guido 曾经专门撰文解释过这个问题。这篇文章并很差懂,我抽空先翻译出来了,看看能收到什么回应。若是可能的话,后续再另写文章分析。html
--------------如下为译文---------------python
布鲁斯·埃克尔(Bruce Eckel)发了篇博文 ,提议从类方法的形参列表中删除“self”。我将解释为何这个提议不能经过。(译注:Bruce 是《Thinking in Java》、《Thinking in C++》等多本书籍的做者,也是个 Python 开发者。他的文章总结了当年在巴西 Pycon 上的一次讨论,主要观点是在定义类方法时,形参中的“self”是多余的,并且由它引起的报错信息具备必定的误导性。)程序员
Bruce 知道,咱们须要一种方法来区分对实例变量的引用和对其它变量的引用,所以他建议将“self”设为关键字。web
考虑一种典型的类,它有一个方法,例如:jsp
class C:
def meth(self, arg):
self.val = arg
return self.val
复制代码
跟据 Bruce 的提议,这将变为:函数
class C:
def meth(arg): # Look ma, no self!
self.val = arg
return self.val
复制代码
这样每一个方法会节省 6 个字符。但我不以为 Bruce 提出这个建议是为了减小打字。post
我认为他真正关心的是程序员(可能来自其它语言)所浪费的时间,有时候彷佛不须要指定“self”参数,并且他们偶尔忘记了要加(即便他们十分清楚——习惯是一种强大的力量)。确实,与忘记在实例变量或方法引用以前键入“self.”相比,从参数列表中省略“self”,每每会致使很模糊的错误消息。ui
也许更糟糕的是(如 Bruce 所述),当正确地声明了方法,可是在调用时的参数数量不对,这时收到的错误消息。如 Bruce 给出的如下示例:spa
Traceback (most recent call last):
File "classes.py", line 9, in
obj.m2(1)
TypeError: m2() takes exactly 3 arguments (2 given)
复制代码
我赞同它是使人困惑的,可是我宁愿去解决此错误消息,而不是修改语言。翻译
首先,让我提出一些与 Bruce 的提议相反的典型论点。
这有一个很好的论据能够证实,在参数列表中使用显式的“self”,能够加强如下两种调用方法在理论上的等效性。假设“ foo”是“C”的一个实例:
foo.meth(arg) == C.meth(foo, arg)
复制代码
(译注:说实话,我没有理解这个例子的意思。如下仅是我的见解。在类的内部定义方法时,可能会产生几种不一样的方法:实例方法 、类方法 和 静态方法 。它们的做用和行为是不一样的,那么在定义和调用时怎么作区分呢?Python 约定了一种方式,即在定义时用第一个参数做区分:self 表示实例方法、cls或其它符号 表示类方法……三种方法均可以被类的实例调用,并且看起来如出一辙,如上例的等号左侧那样。这时候就要靠定义时赋予的参数来区分了,像上例等号右侧,第一个参数是实例对象,代表此处是个实例方法。)
另外一个论据是,在参数列表中使用显式的“self”,将一个函数插入一个类,得到动态地修改一个类的能力,建立出相应的一个类方法。
例如,咱们能够建立一个与上面的“C”彻底等效的类,以下所示:
# Define an empty class:
class C:
pass
# Define a global function:
def meth(myself, arg):
myself.val = arg
return myself.val
# Poke the method into the class:
C.meth = meth
复制代码
请注意,我将“self”参数重命名为“myself”,以强调(在语法上)咱们不是在此处定义一个方法(译注:类外部的是函数 ,即 function,类内部的是方法 ,即 method)。
这样以后,C 的实例就具备了一个“meth”方法,该方法有一个参数,且功能跟以前的彻底同样。对于在把方法插入类以前就建立的那些 C 的实例,它甚至也适用。
我想 Bruce 并不特别在乎前述的等效性。我赞成这只是理论上的重要。我能想到的惟一例外是旧式的调用超级方法的习语(idiom)。可是,这个习语很容易出错(正是因为须要显式地传递"self"的缘由),这就是为何在 Python 3000 中,我建议在全部状况下都使用"super()"的缘由。
Bruce 可能会想到一种使第二个等效例子起做用的方法——在某些状况下,这种等效性真的很重要。我不知道 Bruce 花了多少时间思考如何实现他的提议,可是我想他正在考虑将一个名为“self”的额外形参自动地添加到直接地在类内部定义的全部方法的思路(我必须说是“直接地”,以便那些嵌套在方法内部的函数,能免于这种自动操做)。这样,可使第一个等效例子保持等效。
可是,有一种状况我认为 Bruce 不能在不向编译器中添加某种 ESP 的状况下解决:装饰器。 我相信这是 Bruce 的提议的最终败笔。
当装饰一个方法时,咱们不知道是否要自动地给它加一个“self”参数:装饰器能够将函数变成一个静态方法(没有“self”)或一个类方法(有一个有趣的 self,它指向一个类而不是一个实例),或者能够作一些彻底不一样的事情(用纯 Python 实现“ @classmethod”或“ @staticmethod”的装饰器是繁琐的)。除非知道装饰器的用途,不然没有其它办法来肯定是否要赋予正在定义的方法一个隐式的“self”参数。
我拒绝诸如特殊包装的“ @classmethod”和“ @staticmethod”之类的黑科技。我也认为除了自检外,自动地肯定某个方法是类方法(class method)、实例方法(instance method)仍是静态方法(static method),这不是一个好主意(就像在 Bruce 的文章的评论中,有人建议的那样):这使得很难仅仅根据方法前的“def”,来决定应该怎样调用该方法。
(译注:对于一个方法,在当前的添加了相应参数的状况下,能够简单地加装饰器,区分它是哪一种方法,调用时也容易区分调用;可是,若是没有加参数,即便能够用神奇的自动机制来区分出它是哪一种方法,但在调用时,你很差肯定该怎么调用)。
在评论中,我看到了一些很是极端的对 Bruce 的提议的附和,但一般的代价是使得规则难以遵循,或者要求对语言进行更深层的修改,这令咱们极其难以接受它,特别是合入 Python 3.1。顺便说一句,对于 3.1,再次声明咱们的规则,新特性只有在保持向后兼容的状况下才是可接受的。
有一个彷佛可行的建议(可使它向后兼容)是把类中的
def foo(self, arg): ...
复制代码
改为这样的语法糖:
def self.foo(arg): ...
复制代码
但我不认同它把“self”变为保留字(reserved word),或者要求前缀必须是“self”。若是这样作了,那对于类方法,很容易也出现这种状况:
@classmethod
def cls.foo(arg): ...
复制代码
好了,相比于现状,我并无更喜欢这个。可是相比于 Bruce 的提议或在他的博客评论区中提出的更极端的说法,我认为这个要好得多,并且它具备向后兼容的巨大优点,而且不须要很费力,就能够写成带有参考实现的 PEP。(我想 Bruce 应该会发现本身提案中的缺陷,若是他真的付出努力尝试编写可靠的 PEP 或者尝试实现它。)
我能够继续聊不少,但这是一个阳光明媚的周日早晨,而我还有其它的计划... :-)
做者:Guido van Rossum,写于:2008.10.26
英文: neopythonic.blogspot.com/2008/10/why…
做者简介: Guido van Rossum,Python 的创造者,一直是“终身仁慈独裁者”,直到 2018 年 7 月 12 日退位。目前,他是新的最高决策层的五位成员之一,依然活跃在社区中。
译者简介: 豌豆花下猫,生于广东毕业于武大,现为苏漂程序员,有一些极客思惟,也有一些人文情怀,有一些温度,还有一些态度。公众号:「Python猫」(python_cat)。
公众号【Python猫】, 本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写做、优质英文推荐与翻译等等,欢迎关注哦。