为什么需要在Python方法中显式包含“ self”参数?


196

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

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

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

这是Python中的一种故意的语言设计决策,还是有一些实现细节需要传递“ self”作为参数?


15
我敢打赌,你也会有兴趣知道为什么你需要明确写入self访问成员- stackoverflow.com/questions/910020/...
彼得·Dobrogost

1
但它看起来有点像锅炉板
Raghuveer 2015年

有点令人困惑,但值得理解stackoverflow.com/a/31367197/1815624
CrandellWS 2015年

Answers:


91

我喜欢引用Peters的Python Zen。“显式比隐式好。”

在Java和C ++中,this.可以推断出' ',除非您拥有无法推断的变量名。因此,您有时需要它,有时则不需要。

Python选择使这种事情变得明确,而不是基于规则。

另外,由于没有暗示或假设,因此公开了部分实现。 self.__class__self.__dict__以及其他“内部”结构也很明显。


53
尽管忘记该消息时会出现一些不太明显的错误消息会很不错。
马丁·贝克特

9
但是,当您调用方法时,不必传递对象变量,这是否违反了显式规则?如果要保持此禅宗,它必须类似于:object.method(object,param1,param2)。看起来有些矛盾……
Vedmant 2015年

10
“显式优于隐式”-难道围绕隐式事物构建的Python“样式”吗?例如,隐式数据类型,隐式函数边界(无{}),隐式变量范围...如果模块中的全局变量在函数中可用...为什么不将相同的逻辑/理由应用于类?简化的规则是否仅仅是缩进确定的“在更高级别声明的任何内容都可以在更低级别使用”?
西蒙(Simon)

13
“发现比隐藏更好”废话被发现
Vahid Amiri

10
让我们面对现实吧,那就太糟糕了。没有任何借口。这只是一个丑陋的遗物,但是还可以。
Toskan

62

这是为了最小化方法和函数之间的差异。它使您可以轻松地在元类中生成方法,或在运行时将方法添加到预先存在的类中。

例如

>>> 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运行时的实现更加容易。


10
+1是为了最小化方法和函数之间的差异。这应该被接受的答案
用户

这也是guido相互联系的解释的根源。
Marcin

1
这也表明在Python中,当您执行c.bar()时,它首先检查实例的属性,然后再检查类的属性。因此,您可以随时将数据或函数(对象)“附加”到Class上,并期望在其实例中进行访问(即dir(instance)将如何)。不只是在您“创建” c实例时。它非常动态。
Nishant 2014年

10
我真的不买。即使在需要父类的情况下,您仍然可以在执行时进行推断。实例方法和通过实例传递的类函数之间的等效性是愚蠢的。没有它们,Ruby就可以了。
zachaysan'3

2
JavaScript允许您在运行时向对象添加方法,并且不需要self在函数声明中使用(请注意,也许这是从玻璃屋扔石头,因为JavaScript具有一些非常棘手的this绑定语义)
Jonathan Benn

55

我建议人们应该阅读Guido van Rossum关于此主题的博客 - 为什么必须保留显性自我

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

我拒绝诸如特殊外壳“ @classmethod”和“ @staticmethod”之类的hack。


16

Python不会强迫您使用“自我”。您可以根据需要命名。您只需要记住,方法定义标头中的第一个参数是对该对象的引用。


按照惯例,对于涉及类型(mmmm元类)的实例或“ cls”,它应该是“自我”
pobk

1
它迫使将自我作为每种方法的第一参数,只是多余的文本对我而言没有多大意义。其他语言也可以正常工作。
Vedmant

我对吗 ?始终第一个参数是对对象的引用。
Mohammad Mahdi KouchakYazdi's

@MMKY不,例如,@staticmethod它不是。
2016年

1
“您只需要记住方法定义中的第一个参数...”,我尝试将单词“ self”更改为“ kwyjibo”,但它仍然有效。因此,正如通常所解释的,重要的不是“自我”这个词,而是任何占据该空间的位置(?)
RBV

7

还允许您执行此操作:(简而言之,调用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_selfMagicMethod实例的绑定。奇怪吗?就像有2个关键字this1以及this2Java和C#这样的语言一样。像这样的魔术使框架能够执行原本会更加冗长的工作。

同样,我不想评论这种东西的道德。我只是想展示在没有明确的自我参考的情况下很难做的事情。


4
在考虑您的第一个示例时,我可以在Java中执行相同的操作:内部类需要调用OuterClass.this以从外部类获取“自我”,但是您仍然可以将其this用作对自身的引用。与您在Python中所做的非常相似。对我来说,想象起来并不难。也许这取决于一个人对所使用语言的熟练程度?
klaar

但是,当您在匿名类的方法内部定义时,仍然可以引用任何作用域,该方法在匿名类内部定义,该匿名类在接口的匿名实现中定义,接口Something又在的另一个匿名实现中定义Something?在python中,您当然可以引用任何范围。
vlad-ardelean

没错,在Java中,您只能通过调用外部类的显式类名来引用外部类,并使用该外部类名作为prefix this。隐式引用是Java中的impssibru。
klaar

我不知道这是否行得通:在每个范围(每个方法)中都有一个引用this结果的局部变量。例如Object self1 = this;(使用Object或不太通用的东西)。然后,如果你有机会在更高范围的变量,你可以有机会获得self1self2...... selfn。我认为应该将这些声明为final或某些内容,但它可能会起作用。
vlad-ardelean

3

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

本质上使它们成为对象。现在的根本问题是,如果您的函数也是对象,那么在面向对象的范例中,当消息本身是对象时,如何将消息发送给对象?

看起来像一个鸡蛋问题,为了减少这种矛盾,唯一可能的方法是将执行上下文传递给方法或对其进行检测。但是由于python可以具有嵌套函数,因此将不可能做到这一点,因为内部函数的执行上下文将发生变化。

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

因此,我认为Zen来得晚了,这是一个实现问题。


嗨,我是python的新手(从java背景开始),我没有完全理解您所说的“当消息本身是对象时,如何将消息发送给Objects”。为什么会有这个问题,您能详细说明一下吗?
秋朗

1
@Qiulang Aah,在面向对象的编程中,对对象的调用方法等效于向有或没有有效载荷(函数参数)的对象发送消息。内部方法将表示为与类/对象相关联的代码块,并使用通过其针对其调用的对象可用的隐式环境。但是,如果您的方法是对象,则它们可以独立于与类/对象的关联而存在,这引出了一个问题,如果您调用此方法,它将在哪个环境下运行?
pankajdoharey

因此,必须有一种提供环境的机制,自我将在执行时表示当前环境,但您也可以提供另一个环境。
pankajdoharey

2

我认为这与PEP 227有关:

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


1

Python中的self所述,Demystified

像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是局部变量。它们不相同,并且位于不同的命名空间中。

自我在这里停留

许多人建议将self用作Python的关键字,例如C ++和Java。这将消除方法中形式参数列表中显式自我的多余使用。尽管这个想法看起来很有希望,但它不会发生。至少在不久的将来不会。主要原因是向后兼容。这是Python的创建者本人写的博客,解释了为何必须保留显式自我。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.