it-swarm.cn

为什么需要在Python方法中明确地使用“self”参数?

在Python中定义类的方法时,它看起来像这样:

class MyClass(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

但是在其他一些语言中,例如C#,您可以使用“this”关键字引用该方法绑定的对象,而不将其声明为方法原型中的参数。

这是一个有意的语言设计决策在Python或是否有一些实现细节需要传递“自我”作为参数?

182
Readonly

我想引用Peters的Python禅。 “明确比隐含更好。”

在Java和C++中,可以推导出'this.',除非你有可变名称使其无法推断。所以你有时需要它,有时却不需要它。

Python选择将这样的内容显式化而不是基于规则。

此外,由于没有任何暗示或假设,因此公开了部分实现。 self.__class__self.__dict__和其他“内部”结构以明显的方式可用。

88
S.Lott

这是为了最小化方法和功能之间的差异。它允许您轻松地在元类中生成方法,或者在运行时将方法添加到预先存在的类中。

例如.

>>> class C(object):
...     def foo(self):
...         print "Hi!"
...
>>>
>>> def bar(self):
...     print "Bork bork bork!"
...
>>>
>>> c = C()
>>> C.bar = bar
>>> c.bar()
Bork bork bork!
>>> c.foo()
Hi!
>>>

它(据我所知)也使python运行时的实现更容易。

56
Ryan

我建议一个人应该阅读 Guido van Rossum的博客 关于这个话题 - 为什么明确的自我必须留下来

当一个方法定义被装饰时,我们不知道是否自动给它一个'self'参数:装饰器可以将函数转换为静态方法(没有'self'),或者类方法(有一种有趣的自我,指的是一个类而不是一个实例),或者它可以做一些完全不同的事情(编写一个在纯Python中实现'@classmethod'或'@staticmethod'的装饰器是微不足道的)。如果不知道装饰器是否赋予使用隐式“自”参数定义的方法的作用,就没有办法。

我拒绝像特殊套管'@classmethod'和'@staticmethod'这样的黑客攻击。

51
bhadra

Python不会强迫你使用“自我”。你可以给它任何你想要的名字。您只需记住方法定义标头中的第一个参数是对该对象的引用。

16
Victor Noagbodji

也允许你这样做:(简而言之,调用Outer(3).create_inner_class(4)().weird_sum_with_closure_scope(5)将返回12,但将以最疯狂的方式执行此操作。

class Outer(object):
    def __init__(self, outer_num):
        self.outer_num = outer_num

    def create_inner_class(outer_self, inner_arg):
        class Inner(object):
            inner_arg = inner_arg
            def weird_sum_with_closure_scope(inner_self, num)
                return num + outer_self.outer_num + inner_arg
        return Inner

当然,在Java和C#等语言中难以想象。通过使自引用显式化,您可以通过该自引用自由引用任何对象。此外,这种在运行时使用类的方法在更静态的语言中更难做到 - 不一定是好或坏。只是明确的自我允许所有这些疯狂存在。

而且,想象一下:我们想定制方法的行为(用于分析,或者一些疯狂的黑魔法)。这可以让我们思考:如果我们有一个类Method,我们可以覆盖或控制哪些行为怎么办?

那么这里是:

from functools import partial

class MagicMethod(object):
    """Does black magic when called"""
    def __get__(self, obj, obj_type):
        # This binds the <other> class instance to the <innocent_self> parameter
        # of the method MagicMethod.invoke
        return partial(self.invoke, obj)


    def invoke(magic_self, innocent_self, *args, **kwargs):
        # do black magic here
        ...
        print magic_self, innocent_self, args, kwargs

class InnocentClass(object):
    magic_method = MagicMethod()

现在:InnocentClass().magic_method()将表现得像预期的那样。该方法将innocent_self参数绑定到InnocentClass,并使用magic_self绑定到MagicMethod实例。怪啊?这就像在Java和C#这样的语言中有2个关键字this1this2。像这样的魔法允许框架做一些本来会更冗长的东西。

同样,我不想评论这些东西的伦理。我只是想展示一些没有明确的自我引用会更难做的事情。

6
vlad-ardelean

我认为除了“Python的禅”之外的真正原因是函数是Python中的一等公民。

这基本上使它们成为一个对象。现在基本的问题是,如果你的函数也是对象,那么在面向对象的范例中,当消息本身是对象时,你如何向对象发送消息?

看起来像鸡蛋问题,为了减少这个悖论,唯一可行的方法是将执行上下文传递给方法或检测它。但是因为python可以有嵌套函数,所以不可能这样做,因为执行的上下文会因内部函数而改变。

这意味着唯一可行的解​​决方案是显式传递'self'(执行的上下文)。

所以我相信这是一个实施问题,禅宗来得更晚。

2
pankajdoharey

我认为这与PEP 227有关:

无法访问类范围中的名称。名称在最里面的封闭函数范围内解析。如果类定义出现在嵌套作用域链中,则解析过程将跳过类定义。此规则可防止类属性与本地变量访问之间的奇怪交互。如果在类定义中发生名称绑定操作,则会在生成的类对象上创建属性。要在方法中或在嵌套在方法中的函数中访问此变量,必须通过self或类名使用属性引用。

2
daole

Python中的自我,揭秘 中所述

像obj.meth(args)这样的东西变成了Class.meth(obj,args)。调用过程是自动的,而接收过程不是(明确的)。这就是类中函数的第一个参数必须是对象本身的原因。

class Point(object):
    def __init__(self,x = 0,y = 0):
        self.x = x
        self.y = y

    def distance(self):
        """Find distance from Origin"""
        return (self.x**2 + self.y**2) ** 0.5

调用次数:

>>> p1 = Point(6,8)
>>> p1.distance()
10.0

init()定义了三个参数,但我们只传递了两个(6和8)。类似地,distance()需要一个但是传递零参数。

为什么Python不抱怨这个参数号不匹配

通常,当我们使用某些参数调用方法时,通过将方法的对象放在第一个参数之前来调用相应的类函数。所以,像obj.meth(args)之类的东西变成了Class.meth(obj,args)。 调用进程是自动的,而接收进程不是(显式)。

这就是类中函数的第一个参数必须是对象本身的原因。将此参数写为self只是一个约定。它不是关键字,在Python中没有特殊含义。我们可以使用其他名称(像这样),但我强烈建议你不要这样做。大多数开发人员都不赞同使用self以外的名称,这会降低代码的可读性(“可读性计数”)。
...
在第一个示例中,self.x是实例属性,而x是局部变量。它们不同,位于不同的名称空间中。

自我在这里留下来

许多人已经建议使自己成为Python中的关键字,就像在C++和Java中一样。这将消除方法中形式参数列表中显式self的多余使用。虽然这个想法似乎很有希望,但它不会发生。至少不会在不久的将来。 主要原因是向后兼容。这是来自Python创建者的博客,他解释了为什么显性自我必须留下来。

0
mon