如何在 Python 中避免显式的“自我”?

我通过学习一些 Pygame 教程来学习 Python。

在那里我发现了关键字 自我的广泛使用,并且来自一个主要的 Java 背景,我发现我总是忘记键入 自我。例如,我将输入 rect.centerx而不是 self.rect.centerx,因为对我来说,正确已经是类的一个成员变量。

在这种情况下,我能想到的 Java 并行方式是必须在所有对成员变量的引用前加上 这个的前缀。

我是否在所有成员变量前面都加上了 自我的前缀,还是有一种声明它们的方法可以让我避免这样做?

即使我建议的不是 蟒蛇,我仍然想知道它是否可行。

我看了一下这些相关的 SO 问题,但它们并没有完全回答我所追求的:

68384 次浏览

Self 是 Python 语法的一部分,用于访问对象的成员,所以恐怕您无法摆脱它

是的,您必须始终指定 self,因为根据 python 的哲学,显式的比隐式的好。

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

顺便说一下。我一开始讨厌它,现在我讨厌它的反面。对于缩进驱动的流控制也是一样。

实际上,self不是一个关键字,它只是 Python 中实例方法的第一个参数的常规名称。第一个参数是不能跳过的,因为它是方法知道调用它的类的哪个实例的唯一机制。

用 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.>

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

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

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

甚至

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

但是你必须用一个名字来命名这个范围。

我不建议你使用与自我不同的东西,除非你有一个令人信服的理由,因为它会使外国的经验丰富的蟒蛇。

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

现在哪个更易读?

是的,自我是乏味的。但是,它是更好的吗?

class Test:


def __init__(_):
_.test = 'test'


def run(_):
print _.test

发信人: Self Hell-更多有状态函数。

... 混合方法最好,你所有的类方法实际上 Do 计算应该移动到闭包中,清理语法的扩展应该保留在类中。将闭包填充到类中,像对待名称空间一样对待类。闭包本质上是静态函数,因此不需要 self * ,甚至在类中也是如此..。

事实上,你可以使用食谱“隐式自我”从阿明罗纳赫演示“5年的坏主意”(谷歌它)。

这是一个非常聪明的配方,几乎所有的东西都来自阿明 · 罗纳赫,但我不认为这个想法是非常吸引人的。我想我更喜欢 C #/Java 中的显式 这个

更新。链接到“坏主意食谱”: https://speakerdeck.com/mitsuhiko/5-years-of-bad-ideas?slide=58

以前的答案基本上都是“你不能”或“你不应该”的变体。虽然我同意后一种观点,但从技术上讲,这个问题仍然没有答案。

此外,还有一些合理的理由可以解释为什么有些人想要按照实际问题的思路做一些事情。有时候我会碰到一些冗长的数学方程式,在这些方程式中,使用长名称会使方程式变得面目全非。这里有一些方法可以让你在一个固定的例子中做到这一点:

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)基本上就是问题实际要求的内容,但是让我澄清一下,我并不认为这通常是一个好主意。

有关更多信息以及遍历类变量甚至函数的方法,请参见 这个问题的答案和讨论。有关动态命名变量的其他方法的讨论,以及为什么这通常不是一个好主意,请参阅 这篇博文

更新: 在 Python 3中,似乎有 不可能动态更新或更改函数中的局部变量,因此 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 )

总的来说,这是一种很糟糕的做法。

在这里,我按照@argentum2f 的回答来复制属性。这可以由装饰器自动完成,并且可以与 Python3一起工作。当然,复制属性意味着不能更改它们,因此装饰器的名称为 @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,那么它至少会失败。但我认为它很好地证明了这个想法。