V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
chinesehuazhou
V2EX  ›  Python

Python 为什么要保留显式的 self ?

  •  
  •   chinesehuazhou · 2019-09-28 11:04:23 +08:00 · 3902 次点击
    这是一个创建于 1643 天前的主题,其中的信息可能已经有所发展或是发生改变。

    花下猫语:前两天,我偶然在一个知识星球(刘欣老师的“码农翻身”)里看到一篇主题,刘老师表示 Python 的类方法非要带个 self,而不像其它语言那样隐藏起来,这让人很不爽。我对此也有同感。在经过群聊讨论后,我获知 Guido 曾经专门撰文解释过这个问题。这篇文章并不好懂,我抽空先翻译出来了,看看能收到什么回应。如果可能的话,后续再另写文章分析。

    --------------以下为译文---------------

    布鲁斯·埃克尔( Bruce Eckel )发了篇博文 ,提议从类方法的形参列表中删除“self”。我将解释为什么这个提议不能通过。(译注:Bruce 是《 Thinking in Java 》、《 Thinking in C++》等多本书籍的作者,也是个 Python 开发者。他的文章总结了当年在巴西 Pycon 上的一次讨论,主要观点是在定义类方法时,形参中的“self”是多余的,而且由它引发的报错信息具有一定的误导性。)

    Bruce 的提议

    Bruce 知道,我们需要一种方法来区分对实例变量的引用和对其它变量的引用,因此他建议将“self”设为关键字。

    考虑一种典型的类,它有一个方法,例如:

    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 提出这个建议是为了减少打字。

    我认为他真正关心的是程序员(可能来自其它语言)所浪费的时间,有时候似乎不需要指定“self”参数,而且他们偶尔忘记了要加(即使他们十分清楚——习惯是一种强大的力量)。确实,与忘记在实例变量或方法引用之前键入“self.”相比,从参数列表中省略“self”,往往会导致很模糊的错误消息。

    也许更糟糕的是(如 Bruce 所述),当正确地声明了方法,但是在调用时的参数数量不对,这时收到的错误消息。如 Bruce 给出的以下示例:

    Traceback (most recent call last):
    File "classes.py", line 9, in
       obj.m2(1)
    TypeError: m2() takes exactly 3 arguments (2 given)
    

    我赞同它是令人困惑的,但是我宁愿去解决此错误消息,而不是修改语言。

    为什么 Bruce 的提议不可行

    首先,让我提出一些与 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

    英文: https://neopythonic.blogspot.com/2008/10/why-explicit-self-has-to-stay.html

    作者简介: Guido van Rossum,Python 的创造者,一直是“终身仁慈独裁者”,直到 2018 年 7 月 12 日退位。目前,他是新的最高决策层的五位成员之一,依然活跃在社区中。

    译者简介: 豌豆花下猫,生于广东毕业于武大,现为苏漂程序员,有一些极客思维,也有一些人文情怀,有一些温度,还有一些态度。公众号:「 Python 猫」( python_cat )。

    7 条回复    2019-10-02 00:37:59 +08:00
    msg7086
        1
    msg7086  
       2019-09-28 12:08:16 +08:00
    TL;DR
    因为改动会造成向后不兼容,所以打死也不会改的。
    mactaew
        2
    mactaew  
       2019-09-28 13:55:27 +08:00
    显式 self 挺好,好习惯,好规范都是好,特别是某天需要协作的时候。
    pastgift
        3
    pastgift  
       2019-09-28 18:33:40 +08:00 via iPhone
    Python 理念之一,显式优于隐式?
    mahone3297
        4
    mahone3297  
       2019-09-28 23:57:35 +08:00
    @pastgift 》 Python 理念之一,显式优于隐式?
    这么一说,跟 ruby 完全相反?
    lewis89
        5
    lewis89  
       2019-09-29 06:34:21 +08:00
    搞那么多隐式规则干嘛?现在写程序谁不是人手 IDE + 智能提示 多打不了几个字,过去是要多打几个字 现在基本上都是自动提示选择,多花不了几个时间。
    各种隐式规则,写的人爽了,阅读的人可就糟心了
    BingoXuan
        6
    BingoXuan  
       2019-09-29 10:23:22 +08:00
    其实你可以把 self 重名为 this,如 def test(this,*args,**kwargs)。但问题是阅读代码的人如何在只看函数签名而不看源代码情况下区分这个函数是对象方法还是类方法呢?如果省去了,那就更难确定了。

    这种显然是一种过度设计了
    freakxx
        7
    freakxx  
       2019-10-02 00:37:59 +08:00
    @BingoXuan #6
    习惯了 self 和 super 这一套后, 在 py 接受不来 this.

    而且从设计来说,好的统一习惯总归是好的。

    this 的指代是很不明确的,前几天还跟同事吐槽了以前一种很丑的 js 写法:
    var that = this.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   958 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 21:14 · PVG 05:14 · LAX 14:14 · JFK 17:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.