如何避免在Python中使用显式的'self'?

157

我一直在通过一些pygame教程学习Python。

其中我发现广泛使用关键字self,由于我的主要背景是Java,我发现我经常忘记输入self。例如,我会输入rect.centerx而不是self.rect.centerx,因为对我来说,rect已经是类的成员变量了。

我能想到这种情况的Java对应方式是在所有成员变量的引用前加上this

我是否必须在所有成员变量前面加上self,还是有一种声明方式可以让我避免这样做?

即使我提出的方法不符合“Pythonic”的规范,我仍然希望知道是否有可能实现。

我看过了这些相关的SO问题,但它们并不能完全回答我的问题:


6
我来自Java编程背景,觉得这很自然。但我会在每次调用时显式地添加"this"关键字,以便更清晰地指出我正在引用一个实例变量。 - Uri
4
您是否熟悉一些C++/Java程序员使用的在所有成员名称前加上“m_”前缀的约定?使用“self.”同样有助于可读性。另外,您应该阅读http://dirtsimple.org/2004/12/python-is-not-java.html。 - Beni Cherniavsky-Paskin
2
尽管通常情况下 m_ 仅用于非公共的非静态数据成员(至少在 C++ 中)。 - Roger Pate
14
那么,为什么每个人都告诉OP使用self是好的/必要的等等,但没有人说是否有办法可以以某种肮脏的技巧避免使用它? - einpoklum
还有,@Anurag:这怎么可能是自我接受的呢?:-\ - einpoklum
显示剩余4条评论
11个回答

114

在Java术语中:Python没有成员函数,所有类函数都是静态的,并且在作为成员函数调用时,第一个参数是对实际类实例的引用。

这意味着当您的代码有一个class MyClass并且您构建了一个实例m = MyClass()时,调用m.do_something()将被执行为MyClass.do_something(m)

还要注意,这个第一个参数技术上可以被称为任何你想要的东西,但是惯例是使用self,如果你希望其他人(包括你未来的自己)能够轻松地阅读你的代码,你应该坚持这个惯例。

结果是,即使没有完整的类定义可见,也永远不会有关于什么是成员和什么不是的混淆。这导致了有用的属性,例如:您不能添加意外遮盖非成员的成员,从而破坏代码。

一个极端的例子:您可以编写一个类,而不需要知道它可能具有哪些基类,并始终知道您是否正在访问成员:

class A(some_function()):
  def f(self):
    self.member = 42
    self.method()

这是完整的代码!(some_function返回用作基础的类型。)

另一个例子,其中类的方法是动态组成的:

class B(object):
  pass

print B()
# <__main__.B object at 0xb7e4082c>

def B_init(self):
  self.answer = 42
def B_str(self):
  return "<The answer is %s.>" % self.answer
# notice these functions require no knowledge of the actual class
# how hard are they to read and realize that "members" are used?

B.__init__ = B_init
B.__str__ = B_str

print B()
# <The answer is 42.>

请记住,这两个例子都是极端的,你不会每天都看到它们,我也不建议你经常编写这样的代码,但它们确实清楚地展示了需要显式使用 self 的方面。


6
谢谢。这个答案非常恰当,因为它还解释了使用 self 的好处。 - bguiz
4
@ Roger Pate:请停止编辑我的问题,以删除其中的Python内容。我认为它应该在那里。(还有感谢您的答案!) - bguiz
3
“SO” 的惯例是在标题中不重复标签。然而,两天前我编辑时,没有看到您还原了七个月前的标题。” - Roger Pate
11
这实际上是一个听起来相当愚蠢的理由。Python 可能无法接受遮蔽,需要您明确声明,即使用某种__shadow__ myFieldName来“祝福”您的遮蔽字段。那也会防止意外遮蔽,不是吗? - einpoklum
3
@dwjohnston 好的,我晚了几年才加入这个聚会,但我刚开始学Python(我的背景是C++),我讨厌输入“self”。然而,我喜欢明确区分本地变量和实例数据。是否可以使用语法扩展使“。”等于“self。”来允许编写类似以下代码的内容: - user1759557
显示剩余4条评论

71
以前的答案基本上都是“你不能”或“你不应该”的变体。虽然我同意后者的观点,但问题在技术上仍未得到回答。 此外,有合法的理由使某人想要做类似于实际问题所问的事情。我有时会遇到冗长的数学方程式,使用长名称会使方程式难以识别。以下是如何在一个简单的示例中做到这一点的几种方法:
import numpy as np
class MyFunkyGaussian() :
    def __init__(self, A, x0, w, s, y0) :
        self.A = float(A)
        self.x0 = x0
        self.w = w
        self.y0 = y0
        self.s = s

    # The correct way, but subjectively less readable to some (like me) 
    def calc1(self, x) :
        return (self.A/(self.w*np.sqrt(np.pi))/(1+self.s*self.w**2/2)
                * np.exp( -(x-self.x0)**2/self.w**2)
                * (1+self.s*(x-self.x0)**2) + self.y0 )

    # The correct way if you really don't want to use 'self' in the calculations
    def calc2(self, x) :
        # Explicity copy variables
        A, x0, w, y0, s = self.A, self.x0, self.w, self.y0, self.s
        sqrt, exp, pi = np.sqrt, np.exp, np.pi
        return ( A/( w*sqrt(pi) )/(1+s*w**2/2)
                * exp( -(x-x0)**2/w**2 )
                * (1+s*(x-x0)**2) + y0 )

    # Probably a bad idea...
    def calc3(self, x) :
        # Automatically copy every class vairable
        for k in self.__dict__ : exec(k+'= self.'+k)
        sqrt, exp, pi = np.sqrt, np.exp, np.pi
        return ( A/( w*sqrt(pi) )/(1+s*w**2/2)
                * exp( -(x-x0)**2/w**2 )
                * (1+s*(x-x0)**2) + y0 )

g = MyFunkyGaussian(2.0, 1.5, 3.0, 5.0, 0.0)
print(g.calc1(0.5))
print(g.calc2(0.5))
print(g.calc3(0.5))

第三个例子——即使用for k in self.__dict__ : exec(k+'= self.'+k)——基本上就是问题实际上所要求的,但请让我明确一点,我认为这通常不是一个好主意。
有关更多信息以及迭代类变量甚至函数的方法,请参见此问题的答案和讨论。有关其他动态命名变量的方法以及为什么通常不是一个好主意的讨论,请参见此博客文章
更新:似乎没有办法在Python3中动态更新或更改函数中的局部变量,因此calc3和类似变体不再可行。我现在能想到的唯一兼容Python3的解决方案是使用globals
def calc4(self, x) :
        # Automatically copy every class variable in globals
        globals().update(self.__dict__)
        sqrt, exp, pi = np.sqrt, np.exp, np.pi
        return ( A/( w*sqrt(pi) )/(1+s*w**2/2)
                * exp( -(x-x0)**2/w**2 )
                * (1+s*(x-x0)**2) + y0 )

再次强调,这通常是一个可怕的做法。


9
太棒了!这是最正确的答案(也许是唯一的?)。+1。你还独特地给出了一个实用的理由。+1i。 - David Lotts
2
创建一个类并将代码移入其中后:现在所有的方法和变量都不再被识别(没有_self._..)。我又想起了 Python 和我不合拍的另一个原因。感谢您提供这个想法,虽然它不能解决问题/头痛/整体难以阅读性,但提供了一个适度的解决方法。 - WestCoastProjects
为什么不直接更新“locals”而不使用“exec”? - Nathan
1
我在Python 2和3中尝试了locals().update(self.__dict__),但它没有起作用。在Python 3中,即使是“exec”技巧也不再是一个选项。另一方面,globals().update(self.__dict__)确实可以工作,但通常来说这是一种可怕的做法。 - argentum2f
4
@WestCoastProjects 我非常赞同。我相信《Clean Code》这本书中概述的原则。在我的代码中到处都加上"self."并不能让它更易读或简单。 - MickeyDickey
显示剩余2条评论

36

实际上,self 不是关键字,它只是 Python 实例方法的第一个参数通常被赋予的名称。这个第一个参数不能被省略,因为它是方法知道被调用时属于哪个类的唯一机制。


4
对我来说,这个答案,尤其是第二句话,比被采纳的答案更有用,因为我现在知道“explicit self”只是Python中的一个限制,不能避免。 - QuestionDriven

24

你可以使用任何想要的名称,例如

class test(object):
    def function(this, variable):
        this.variable = variable

甚至更好

class test(object):
    def function(s, variable):
        s.variable = variable

但是你必须使用一个名称作为作用域。

我不建议除非你有充分的理由,否则使用与self不同的东西,因为这会让有经验的Python程序员感到陌生。


28
你可以这样做,但最好不要!没有必要让你的代码变得比必要的更怪异。是的,你可以将它命名为任何名称,但约定俗成的做法是将其命名为“self”,你应该遵循这个约定。这将使你的代码对于任何有经验的Python程序员来说更容易理解。(这也包括了你六个月后回来想弄清楚你的旧程序的情况!) - steveha
4
更加陌生的代码:def 函数名(_, 变量名): _.变量名 = 变量 - Bob Stein
1
@BobStein-VisiBone 更加陌生:def funcion(*args): args[0].variable = args[1] - Aemyl
4
@steveha并没有推荐这种做法,但这些信息对于像我一样不知道可以使用不同的关键字代替self并想知道为什么要传递自己类的对象的人来说非常有用。 - Sven van den Boogaart
2
"weirder". Python确实很奇怪 - 特别是它的类结构和这种使用self的方式,这是支持反可读性的一个标志。我知道会有很多人为此辩护,但这并不改变事实的真实性。 - WestCoastProjects
1
我开始使用只有字母's'的代码,看起来更加简洁。 - IGRACH

9

是的,根据Python哲学,显式优于隐式,因此您必须始终指定self

您还会发现,在Python中编程的方式与在Java中编程的方式非常不同,因此使用self的倾向会减少,因为您不会将所有内容都投影到对象内部。相反,您更多地使用模块级函数,这样可以更好地进行测试。

顺便说一句。我一开始讨厌它,现在我讨厌相反的东西。对于基于缩进的流程控制也是如此。


2
“您更多地使用模块级函数,这可能会更好地进行测试”是可疑的,我非常不同意。确实,您不必强制将所有内容都成为某个类(静态或非静态)的方法,而不管它是否“逻辑上属于模块级别”,但这与self无关,对于我来说也没有任何重大影响,在测试方面也是如此。 - Roger Pate
它源于我的经验。我并不总是将其作为口头禅,但如果您将不需要访问成员变量的任何内容作为单独的独立方法进行测试,那么这会使事情更容易。是的,您将逻辑与数据分开,这确实违反了OOP的原则,但它们在模块级别上是在一起的。我不会给其中任何一个打上“最佳”标记,这只是个人口味问题。有时我发现自己在指定与类本身无关的类方法,因为它们没有以任何方式触及self。那么将它们放在类中有什么意义呢? - Stefano Borini
我不同意其中的两个部分(似乎我使用“非方法”并不比其他语言更频繁),但“可以更好地测试”的确意味着一种方式优于另一种方式(这只是我的理解吗?似乎不太可能),而我在实践中没有找到支持这一点的证据。请注意,我并不是说您应该始终使用其中之一,我只是说方法和非方法同样能够进行测试。 - Roger Pate
5
当然可以,但方法不同。一个对象有一个状态,而模块级别的方法没有。如果你在测试类级别的方法时发现测试失败了,可能有两种情况:1)在调用时对象的状态出现了问题;2)方法本身存在问题。如果你有一个无状态的模块级别的方法,只会出现第二种情况。你将设置从对象中移动到测试套件中(在测试中作为黑盒子处理,因为它受到对象内部最终复杂逻辑的控制),这样可以减少复杂性并更好地控制设置。 - Stefano Borini
1
如果你有一个无状态的模块级方法,那么有一个有状态的模块级方法呢?你告诉我的只是无状态函数比有状态函数更容易测试,我同意这一点,但这与方法和非方法无关。将self参数视为函数的另一个参数即可。 - Roger Pate

4

“self”是类的当前对象实例的传统占位符。当你想要在类内部引用对象的属性、字段或方法时,可以使用它来表示“本身”。但为了缩短代码,Python编程领域中的某些人开始使用“self”,而其他领域则使用“this”,但它们将其作为关键字,无法替换。我更喜欢使用“its”来增加代码的可读性。这是Python中的一件好事——你可以自由选择对象实例的占位符,而不仅限于“self”。 例如:

class UserAccount():    
    def __init__(self, user_type, username, password):
        self.user_type = user_type
        self.username = username            
        self.password = encrypt(password)        

    def get_password(self):
        return decrypt(self.password)

    def set_password(self, password):
        self.password = encrypt(password)

现在我们将“self”替换为“its”:
class UserAccount():    
    def __init__(its, user_type, username, password):
        its.user_type = user_type
        its.username = username            
        its.password = encrypt(password)        

    def get_password(its):
        return decrypt(its.password)

    def set_password(its, password):
        its.password = encrypt(password)

现在哪个更易读?

为什么不使用s(或其他单个字母)代替its - WestCoastProjects
1
'its' 有意义,而 's' 没有。 - LEMUEL ADANE
s具有相同的含义:类实例的别名。我必须查找上下文中its的含义。 - WestCoastProjects
1
两者对我来说都不可读。将“你自己”作为外部参数仍然是不合逻辑的,毫无意义。 - Cesar

3

来源:自我深渊-更多有状态的函数。

...混合方法效果最佳。您所有实际进行计算的类方法都应该移动到闭包中,而用于清理语法的扩展应该保留在类中。将闭包放入类中,将类视为命名空间。闭包本质上是静态函数,因此甚至在类中也不需要self*...


1
闭包对于小型用例来说很好,但是如果过度使用它们,将会大大增加程序的内存开销(因为您正在使用基于原型的面向对象而不是基于类的面向对象——因此每个对象都需要自己的一组函数,而不是在类中保留一组公共函数)。此外,它将阻止您能够使用魔术/dunder方法(例如__str__等),因为这些方法的调用方式与普通方法不同。 - Dunes
这个回答很好。 - Snowcrash

1

我在这里遵循@argentum2f的答案中复制属性的想法。这可以通过装饰器自动化,并且适用于Python 3. 当然,复制属性意味着它们不能被更改,因此装饰器的名称为@const_self

使用@const_self,您可以定义一个带有与要使用的属性相同名称的第一个参数的方法 - 没有self

from cmath import sqrt

def const_self(fun):
    fun_args = fun.__code__.co_varnames[:fun.__code__.co_argcount]

    def fun_with_self(*args, **kwargs):
        self = args[0]
        other_args = list(args[1:])

        used_attributes = [arg for arg in fun_args if hasattr(self, arg)]
        self_args = [getattr(self, attr) for attr in used_attributes]

        return fun(*(self_args + other_args), **kwargs)

    return fun_with_self

class QuadraticEquation:
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    @const_self
    def roots(a, b, c, dummy, lazy = False):
        print("Dummy is", dummy)
        if lazy: return # A lazy calculator does not calculate
        return (-b - sqrt(b**2 - 4*a*c)) /2/a, (-b + sqrt(b**2 - 4*a*c)) /2/a

当然,这段代码还有很多需要改进的地方:至少它会在你定义一个像def fun(a, dummy, b, c): print(a,b,c)这样的方法时失败,并且它不会保留docstring。但我认为它足以清晰地展示了这个想法。


1

self是Python语法中访问对象成员的一部分,所以我恐怕你必须使用它。


2
self 是一种在不真正使用修饰符的情况下告诉访问修饰符的方式。+1 - Perpetualcoder
1
这是给任何阅读此内容的人。self不是Python语法的一部分,它只是一个变量名。你可以使用任何名字。此外,当调用Python方法时,始终会隐式地传递至少一个参数在第一个位置。该参数是实例。这就是为什么如果你忘记定义一个参数,它会给你一个错误的原因。 - Hunkoys

1

实际上,您可以使用Armin Ronacher演讲“5年的坏主意”(google it)中的“隐式self”配方。

这是一个非常巧妙的配方,就像Armin Ronacher的几乎所有东西一样,但我认为这个想法并不是很吸引人。我认为我更喜欢C#/ Java中的显式this

更新。链接到“糟糕思想配方”:https://speakerdeck.com/mitsuhiko/5-years-of-bad-ideas?slide=58


这个链接(http://code.activestate.com/recipes/362305-making-self-implicit-in-objects/)是你所提到的吗?如果是,请在你的回答中包含它。 - reubenjohn
不,阿明“坏主意”对我来说更有趣。我已经包含了链接。 - Alex Yu
在您点击配方链接之前,请注意这使得def method(<del>self</del>)参数列表中隐含了它,但是使用这个巧妙的技巧仍然需要self.variable - David Lotts

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