为什么在Python方法中需要显式地有"self"参数?

221

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

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

但在其他一些编程语言中,例如C#,在方法原型中没有声明“this”关键字作为参数,你就可以引用该方法绑定到的对象。

Python中这种需要传递"self"作为参数的行为是有意为之的语言设计决策还是由于某些实现细节需要?


16
我打赌您也想知道为什么需要显式编写“self”才能访问成员——https://dev59.com/1HNA5IYBdhLWcg3wkuzO - Piotr Dobrogost
1
但它看起来有点像样板文件。 - Raghuveer
有点混淆但值得理解 https://dev59.com/GnVD5IYBdhLWcg3wKYP-#31367197 - CrandellWS
http://python-history.blogspot.in/2009/02/adding-support-for-user-defined-classes.html - user3526905
10个回答

96

我想引用彼得斯的Python禅宗:“显式优于隐式。”

在Java和C++中,'this.'可以被推断出来,除非你有一些无法推断的变量名称。 因此,有时需要使用它,有时不需要。

Python选择使这些东西明确而不是基于规则。

此外,由于没有暗示或假设,一些实现细节是公开的。 self.__class__self.__dict__和其他“内部”结构以明显的方式可用。


58
当你忘记密码时,能够有一条更加清晰易懂的错误提示信息会更好。 - Martin Beckett
11
但是当您调用一个方法时,您并不需要传递对象变量,这是否违反了明确性的规则?如果要保持这种"禅",就必须像这样:object.method(object, param1, param2)。看起来有点不一致... - Vedmant
16
“显式优于隐式” - Python 的“风格”不是建立在事情变得隐式的基础上吗?例如,隐式数据类型,隐式函数边界(没有{ }),隐式变量作用域等等...如果模块中的全局变量可以在函数中使用...为什么不应该将同样的逻辑/推理应用于类?简化后的规则不是“在更高级别声明的任何内容都可以在较低级别按缩进确定的位置使用” 吗? - Simon
24
"明确比隐晦更好" 检测到无意义言论。 - Vahid Amiri
18
面对现实,它就是糟糕的。没有任何借口可以为此辩解。它只是一个丑陋的遗物,但这也没关系。 - Toskan
显示剩余5条评论

73

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

例如:

>>> 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
+1 是为了将方法和函数之间的差异最小化。这应该被接受作为答案。 - user
这也是Guido广为引用的解释的核心。 - Marcin
2
这也表明在Python中,当您首先执行c.bar()时,它会首先检查实例的属性,然后检查__class__属性。因此,您可以随时“附加”数据或函数(对象)到类,并期望在其实例中访问(即dir(instance)将显示它)。不仅仅是在您“创建”c实例时。它非常动态。 - Nishant
12
我不太相信。即使在需要父类的情况下,您仍然可以在执行时推断它。将实例方法与传递实例的类函数等效是愚蠢的;Ruby没有这些也能很好地工作。 - zachaysan
4
JavaScript允许您在运行时向对象添加方法,函数声明中不需要使用self关键词(但请注意,这可能会引起困惑,因为JavaScript有一些非常棘手的this绑定语义)。 - Jonathan Benn

59

我建议大家阅读Guido van Rossum的博客关于这个主题的文章-为什么必须保留明确的 self?

当一个方法定义被装饰时,我们不知道是否要自动给它加上 'self' 参数:装饰器可以把函数变成静态方法(没有 'self'),或者类方法(有一种有趣的 self 引用类而不是实例),或者完全做其他事情(在纯 Python 中编写实现 '@classmethod' 或 '@staticmethod' 的装饰器非常简单)。如果不知道装饰器所做的工作,就无法确定正在定义的方法是否具有隐式的 'self' 参数。

我拒绝像特殊处理 '@classmethod' 和 '@staticmethod' 这样的 hack。


16

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


按照惯例,对于实例应该使用“self”,而对于涉及类型的情况(元类),则应该使用“cls”。 - pobk
5
每个方法都强制把self作为第一个参数,这对我来说只是一些没有多大意义的额外文本。其他语言使用这种方式也没有问题。 - Vedmant
我对吗?始终如一,第一个参数是指对象的引用。 - Mohammad Mahdi KouchakYazdi
@MMKY 不是的,例如使用 @staticmethod 就不是这样。 - Mark
1
你只需要记住方法定义中的第一个参数...我尝试将单词“self”更改为“kwyjibo”,它仍然有效。因此,通常解释的是,重要的不是单词“self”,而是占据该位置的任何内容的位置 - RBV
问题在于必须引用实例,而不是使用的名称。我所知道的任何现代语言都能够解除开发人员的这种烦恼。另一方面,Python经常在根本没有定义任何自定义类的情况下使用(随机示例),方法只是静态和全局(非OOP),不需要“self”或等效名称,甚至没有主方法。对我来说,“self”的强制性“优越性”似乎尚未得到证明。 - mins

8
同时也允许您这样做:(简而言之,调用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#这样的语言中,这种想象更难实现。通过显式地进行自我引用,你可以自由地通过该自我引用引用任何对象。此外,在更为静态的语言中进行运行时类操作更加困难 - 并不是说好或坏。只是显式的self允许所有这些疯狂的事情存在。

另外,想象一下:我们想要定制方法的行为(用于分析或一些疯狂的黑魔法)。这会让我们思考:如果我们有一个可以重写或控制其行为的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()将像预期的那样起作用。该方法将绑定到InnocentClassinnocent_self参数,并绑定到MagicMethod实例的magic_self。很奇怪,对吧?这就像在Java和C#等语言中有2个关键字this1this2。这种神奇的功能使得框架能够执行本应更加冗长的任务。

再次说明,我不想评论这些内容的伦理道德。我只是想展示一些没有显式self引用更难实现的事情。


4
考虑你的第一个例子,我可以在Java中做同样的事情:内部类需要调用OuterClass.this来获取外部类的实例,但仍然可以使用this作为对自身的引用;这与你在Python中所做的非常相似。对于我来说,想象这个过程并不困难,也许这取决于一个人对所涉及语言的熟练程度? - klaar
但是,当你在一个匿名类的方法中时,你仍然可以引用任何作用域吗?这个匿名类又被定义在另一个匿名类中,而这个匿名类又被定义在接口“Something”的匿名实现中,而这个接口又被定义在另一个匿名实现中。在Python中,你当然可以引用任何作用域。 - vlad-ardelean
你是对的,在Java中,你只能通过调用外部类的显式类名来引用它,并使用其作为前缀来引用'this'。Java中不存在隐式引用。 - klaar
我在想这个方法是否可行:在每个作用域(每个方法)中都有一个本地变量,引用了“this”结果。例如Object self1 = this;(可以使用Object或其他不太通用的类型)。然后,如果您可以在更高的作用域中访问该变量,则可以访问self1self2,... selfn。我认为这些应该声明为final或类似的东西,但这可能有效。 - vlad-ardelean

5
我认为这与PEP 227有关:
类作用域中的变量名不可访问。变量名解析在最靠近的封闭函数作用域中进行。如果类定义出现在一系列嵌套作用域中,则解析过程将跳过类定义。此规则可以防止类属性和局部变量访问之间发生奇怪的交互。如果类定义中发生名称绑定操作,则会在生成的类对象上创建一个属性。要在方法中或方法内嵌套的函数中访问此变量,必须使用属性引用,可以通过self或类名来实现。

3
我认为除了“Python之禅”之外的真正原因是,函数在Python中是一等公民,这使它们成为对象。现实问题是,在面向对象的程序设计范式中,如果您的函数也是对象,那么当消息本身是对象时,如何向对象发送消息?看起来像一个鸡蛋问题,为了减少这种悖论,唯一可能的方法是将执行上下文传递给方法或检测它。但由于Python可以有嵌套函数,因此对于内部函数,执行上下文会发生变化,所以唯一的解决方案是显式地传递“self”(执行上下文)。因此,我认为这是一个实施问题,“Python之禅”是远后才诞生的。

嗨,我是一个Java背景的Python新手,我不太明白你说的“当消息本身是对象时,你如何向对象发送消息”的意思。为什么会有问题?能否详细解释一下? - Qiulang
2
@Qiulang 啊,在面向对象编程中,调用对象的方法相当于向对象分派带有或不带有有效负载(函数参数)的消息。方法在内部将表示为与类/对象相关联的代码块,并使用通过其调用的对象可用的隐式环境。但是,如果您的方法是对象,则它们可以独立于与类/对象相关联而存在,这引出了一个问题:如果您调用此方法,它将针对哪个环境运行? - pankajdoharey
因此,必须有一种机制来提供一个环境,self 意味着执行时的当前环境,但也可以提供另一个环境。 - pankajdoharey

2

正如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中没有特殊含义。我们可以使用其他名称(如this),但我强烈建议你不要这样做。大多数开发人员都不赞成使用self以外的名称,并且会降低代码的可读性(“可读性很重要”)。
...
在第一个示例中,self.x是一个实例属性,而x是一个局部变量。它们不相同,位于不同的命名空间中。

自引用是必须的

许多人提议在Python中将self作为关键字,就像C++和Java一样。这将消除方法形式参数列表中显式self的冗余使用。虽然这个想法似乎很有前途,但它不会发生。至少在短期内不会发生。主要原因是向后兼容性。这是Python创始人的博客,解释为什么需要保留显式self。


非常好的解释。终于有一个能彻底让我理解的了。谢谢伙计。 - chainstair

0

'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。


-5

还有另一个非常简单的答案:根据 Python之禅,"明确比含蓄更好"。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接