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

160 投票
11 回答
77192 浏览
提问于 2025-04-15 17:30

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

在这些教程中,我发现经常使用一个关键词self。因为我主要是用Java的背景,所以我总是忘记要输入self。比如,我应该写self.rect.centerx,但我总是写成rect.centerx,因为在我看来,rect已经是这个类的一个成员变量了。

我能想到的Java中的类似情况就是,每次引用成员变量时都需要加上this

我是不是只能在所有成员变量前面加上self,还是说有其他方法可以声明它们,让我不需要这样做呢?

即使我说的不是很符合Python的风格,我还是想知道这样做是否可能。

我看过一些相关的StackOverflow问题,但它们并没有完全回答我的疑问:

11 个回答

37

其实,self并不是一个关键字,它只是一个约定俗成的名字,用来表示Python中实例方法的第一个参数。而这个第一个参数是不能省略的,因为它是方法知道自己是在哪个类的实例上被调用的唯一方式。

74

之前的回答基本上都是“你不能这样做”或者“你不应该这样做”。虽然我同意后者的观点,但问题在技术上还是没有得到解答。

此外,有些人确实有理由想要做一些和实际问题相似的事情。有时候我会遇到很长的数学公式,使用长名字会让公式变得难以识别。下面是几个在简单示例中可以做到的方法:

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 )

不过,这在一般情况下仍然是个糟糕的做法。

116

在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 是如何被明确要求的。

撰写回答