为什么需要在 Python 方法中显式使用“self”参数?[复制]

Why do you need explicitly have the "self" argument in a Python method? [duplicate]

提问人:readonly 提问时间:9/16/2008 最后编辑:smcireadonly 更新时间:11/7/2021 访问量:107311

问:

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

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

但在其他一些语言(如 C#)中,您使用“this”关键字引用了方法绑定到的对象,而无需在方法原型中将其声明为参数。

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

python oop 方法 self

评论

16赞 Piotr Dobrogost 1/2/2011
我敢打赌,你也有兴趣知道为什么你需要显式地写信来访问成员 - stackoverflow.com/questions/910020/......self
1赞 Raghuveer 5/20/2015
但它看起来有点像样板
0赞 CrandellWS 10/15/2015
有点令人困惑,但值得理解 stackoverflow.com/a/31367197/1815624
0赞 user3526905 12/29/2015
python-history.blogspot.in/2009/02/......

答:

73赞 Ryan 9/16/2008 #1

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

例如:

>>> class C:
...     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 运行时的实现更容易。

评论

13赞 user 5/27/2012
+1 表示,这是为了最小化方法和函数之间的差异。这应该是公认的答案
0赞 Marcin 7/19/2013
这也是guido的很多联系的解释的根源。
2赞 Nishant 1/5/2014
这也表明,在Python中,当你做 c.bar()时,它首先检查实例的属性,然后检查属性。因此,您可以随时将数据或函数(对象)“附加”到类中,并期望在其实例中访问(即 dir(instance) 将如何访问)。不仅仅是当你“创建”c实例时。它非常有活力。
12赞 zachaysan 3/2/2015
我真的不买。即使在需要父类的情况下,您仍然可以在执行时推断它。实例方法和传递实例的类函数之间的等价是愚蠢的;没有它们,Ruby 也能正常工作。
4赞 Jonathan Benn 10/24/2017
JavaScript 允许你在运行时向对象添加方法,并且不需要在函数声明中(请注意,也许这是从玻璃屋里扔石头,因为 JavaScript 有一些非常棘手的绑定语义)selfthis
96赞 S.Lott 9/16/2008 #2

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

在 Java 和 C++ 中,可以推导 '',除非您的变量名称无法推导。所以你有时需要它,有时不需要它。this.

Python 选择明确地表达这样的事情,而不是基于规则。

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

评论

58赞 Martin Beckett 2/4/2009
尽管当您忘记它时,如果有一个不那么隐晦的错误消息会很好。
11赞 Vedmant 6/6/2015
但是,当你调用一个方法时,你不必传递对象变量,这难道不是打破了显式规则吗?如果要保持这个禅,它必须是这样的:object.method(object, param1, param2)。看起来有些不一致......
16赞 Simon 7/28/2016
“explicit is better than implicit” - Python 的“风格”不是围绕着隐式的东西构建的吗?例如,隐式数据类型、隐式函数边界(无 { }'s)、隐式变量作用域......如果模块中的全局变量在函数中可用...为什么同样的逻辑/推理不应该应用于类?简化的规则难道不是“在较高级别声明的任何内容在较低级别可用”吗?
24赞 Vahid Amiri 3/18/2017
“显式比隐式好”检测到废话
18赞 Toskan 8/5/2017
让我们面对现实吧,这很糟糕。这是没有任何借口的。这只是一个丑陋的遗物,但没关系。
-5赞 Flávio Amieiro 9/16/2008 #3

还有另一个非常简单的答案:根据 Python 的禅宗,“显式胜于隐式”。

16赞 Victor Noagbodji 9/16/2008 #4

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

评论

0赞 pobk 9/16/2008
然而,按照惯例,对于涉及类型的实例,它应该是“self”,或者是“cls”(mmmm元类)
5赞 Vedmant 7/28/2015
它迫使在每种方法中将 self 作为第一个参数,只是额外的文本,对我来说没有多大意义。其他语言也可以很好地工作。
0赞 Mohammad Mahdi KouchakYazdi 9/18/2016
我说得对吗?第一个参数始终是对对象的引用。
0赞 Mark 9/23/2016
@MMKY 不,例如它不是。@staticmethod
1赞 RBV 1/10/2017
“你只需要记住,方法定义中的第一个参数......”我尝试将“自我”一词更改为“kwyjibo”,但它仍然有效。因此,正如人们经常解释的那样,重要的不是“自我”这个词,而是占据该空间的任何东西的位置(?
59赞 bhadra 11/21/2008 #5

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

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

我拒绝像特殊大小写的“@classmethod”和“@staticmethod”这样的黑客。

5赞 daole 3/31/2015 #6

我认为这与 PEP 227 有关:

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

8赞 vlad-ardelean 7/12/2015 #7

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

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()

而现在:将按预期行事。该方法将与参数绑定到 ,并使用 绑定到 MagicMethod 实例。很奇怪吧?这就像有 2 个关键字和 Java 和 C# 等语言。像这样的魔术允许框架做一些本来会更冗长的事情。InnocentClass().magic_method()innocent_selfInnocentClassmagic_selfthis1this2

再说一次,我不想评论这些东西的道德规范。我只是想展示一些没有明确的自我参照就很难做到的事情。

评论

4赞 klaar 4/22/2016
当我考虑你的第一个例子时,我可以在 Java 中做同样的事情:内部类需要调用才能从外部类中获取“自我”,但你仍然可以用作对自身的引用;与你在 Python 中所做的非常相似。对我来说,这并不难想象。也许这取决于一个人对相关语言的熟练程度?OuterClass.thisthis
0赞 vlad-ardelean 4/23/2016
但是,当您在匿名类的方法中时,您仍然可以引用任何范围,该方法在匿名类中定义,该类在接口的匿名实现中定义,而接口又在另一个匿名实现中定义?在 python 中,您当然可以引用任何范围。SomethingSomething
0赞 klaar 4/25/2016
你是对的,在 Java 中,您只能通过调用其显式类名来引用外部类,并使用它作为前缀。隐式引用在 Java 中是不可实现的。this
0赞 vlad-ardelean 4/25/2016
我想知道这是否有效:在每个范围(每个方法)中都有一个局部变量,该变量引用结果。例如(要么使用 Object,要么使用不太通用的东西)。然后,如果您有权访问更高作用域中的变量,则可以访问 , , ...。我认为这些应该被宣布为最终的或其他什么,但它可能会起作用。thisObject self1 = this;self1self2selfn
3赞 pankajdoharey 5/2/2016 #8

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

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

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

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

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

评论

0赞 Qiulang 4/19/2019
嗨,我是 python 的新手(来自 java 背景),我没有完全理解你所说的“当消息本身是对象时,您将如何向对象发送消息”。为什么这是一个问题,你能详细说明一下吗?
2赞 pankajdoharey 4/22/2019
@Qiulang Aah,在面向对象编程中,对对象调用方法等同于将消息分派给具有或不具有有效负载(函数的参数)的对象。方法在内部将表示为与类/对象关联的代码块,并使用通过调用它的对象可用的隐式环境。但是,如果你的方法是对象,它们可以独立于与类/对象相关联而存在,这就引出了一个问题,如果你调用这个方法,它会在哪个环境中运行?
0赞 pankajdoharey 4/22/2019
因此,必须有一种机制来提供一个环境,self 是指执行点的当前环境,但你也可以提供另一个环境。
2赞 mon 2/4/2019 #9

正如 Python 中的 self 中所解释的那样,揭开神秘面纱

像 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中将self作为关键字,就像C++和Java中的一样。这将消除方法中形式参数列表中显式自我的冗余使用。虽然这个想法看起来很有希望,但它不会发生。至少在不久的将来不会。主要原因是向后兼容性。这是 Python 创建者本人的博客,解释了为什么显式自我必须保留。

评论

0赞 chainstair 12/22/2020
很好的解释。最后是完全有道理的。谢谢伙计
0赞 Sanmitha Sadhishkumar 8/19/2020 #10

“self”参数保留当前调用对象。

class class_name:
    class_variable
    def method_name(self,arg):
        self.var=arg 
obj=class_name()
obj.method_name()

在这里,self 参数包含对象 obj。因此,语句 self.var 表示 obj.var