如何避免在 Python 中显式地使用“自我”?
- 2025-03-05 09:18:00
- admin 原创
- 66
问题描述:
我一直在通过关注一些pygame 教程学习 Python 。
我发现关键字self被广泛使用,由于我主要使用 Java,我发现自己总是忘记输入self。例如,self.rect.centerx
我会输入而不是rect.centerx
,因为对我来说,rect已经是该类的成员变量。
对于这种情况我能想到的 Java 并行方法是必须在所有对成员变量的引用前加上this。
我是否必须将所有成员变量都加上self前缀,或者有没有办法声明它们,使我避免这样做?
即使我所建议的不是Pythonic,我仍然想知道是否可行。
我已经看过这些相关的问题,但它们并没有完全回答我的问题:
Python - 为什么在类中使用“self”?
为什么需要在 Python 方法中明确添加“self”参数?
解决方案 1:
用 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.>
请记住,这两个例子都很极端,您不会每天都看到它们,我也不建议您经常编写这样的代码,但它们确实清楚地显示了明确需要自我的方面。
解决方案 2:
前面的答案基本上都是“你不能”或“你不应该”的变体。虽然我同意后一种观点,但这个问题从技术上来说仍然没有答案。
此外,有人可能想按照实际问题的要求做些事情,这是有正当理由的。我有时会遇到的一件事是冗长的数学方程式,使用长名称会使方程式无法识别。以下是您可以在一个固定示例中执行此操作的几种方法:
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 )
总的来说,这是一种糟糕的做法。
解决方案 3:
实际上self
并不是关键字,它只是 Python 中实例方法的第一个参数的常规名称。并且不能跳过第一个参数,因为它是方法知道正在调用类的哪个实例的唯一机制。
解决方案 4:
你可以使用任何你想要的名字,例如
class test(object):
def function(this, variable):
this.variable = variable
甚至
class test(object):
def function(s, variable):
s.variable = variable
但您只能使用名称作为范围。
我不建议你使用与 self 不同的东西,除非你有令人信服的理由,因为它会让经验丰富的 Python 专家感到陌生。
解决方案 5:
是的,您必须始终指定self
,因为根据 Python 哲学,显式比隐式更好。
您还会发现,使用 Python 编程的方式与使用 Java 编程的方式非常不同,因此使用self
会减少,因为您不会将所有内容都投影到对象内部。相反,您会更多地使用模块级函数,这样可以更好地进行测试。
顺便说一句。一开始我很讨厌它,现在我讨厌相反的东西。缩进驱动的流程控制也一样。
解决方案 6:
来自:自我地狱 - 更多状态功能。
...混合方法效果最好。所有实际进行计算的类方法都应移入闭包,而用于简化语法的扩展应保留在类中。将闭包塞入类中,将类视为命名空间。闭包本质上是静态函数,因此即使在类中也不需要 selfs*...
解决方案 7:
“self” 是类的当前对象实例的常规占位符。当您想要引用类中的对象的属性、字段或方法时,就像引用“itself”一样,就会使用它。但为了让它更简短,Python 编程领域的某个人开始使用“self”,其他领域使用“this”,但他们将其作为无法替换的关键字。我宁愿使用“its”来提高代码的可读性。这是 Python 中的优点之一 - 您可以自由选择除“self”之外的对象实例的占位符。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)
现在我们用‘its’替换‘self’:
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)
现在哪个更具可读性?
解决方案 8:
self 是 python 语法的一部分,用于访问对象的成员,所以恐怕你只能用它了
解决方案 9:
实际上,您可以使用 Armin Ronacher 的演讲“5 年的糟糕想法”中的秘诀“内隐自我”(谷歌搜索)。
这是一个非常聪明的配方,就像 Armin Ronacher 的几乎所有东西一样,但我认为这个想法不太有吸引力。我想我更喜欢在 C#/Java 中明确这一点。
更新。“坏主意食谱”链接:https://speakerdeck.com/mitsuhiko/5-years-of-bad-ideas ?slide=58
解决方案 10:
使用 ASCII 范围之外的可区分字符:
class Stuff:
def __init__(ж, x, y):
ж.x = x
ж.y = y
理由:
这并没有回答如何才能完全避免“自我”的问题,但正如其他人已经解释的那样,可能不应该这样做。
但如果你问,如何才能最大限度地减少要阅读的字符数量,同时又能保持良好的可区分性,这可能是一个可能的答案。你可能想要不同的字符选择,但我认为最好不要使用“s”,而是使用一些显而易见的东西:“这是有特殊含义的东西”
当我开始使用 python(来自 c++ 和 c#)时,我尝试了“this”,但很快发现,这是一种非常自私的处理方式,我会很快采用“self”,因为我正在阅读 SO 等网站上的示例代码。我最终发现“this”与“self”相比没有任何优势,并很快停止了它。
我还意识到,在所有语言中,拥有一个绑定并以某种方式强制访问对象成员的概念,但同时不占用超过一个字符的空间(我会将点包括在该计数中),这将是一个很好的做法。
所以,这是我在个人项目中使用的解决方案。怀着小小的希望,希望有一天我们都能意识到我们应该就一个共同的单字符达成一致。理想情况下,python 本身会启用一个单字符快捷方式,其中也包括点。
另一个解决方案比我的更复杂,但不涉及 Python,即使用字体,在特殊字符和点之间绘制连字符,这样会非常容易识别。我想象一个小圆形,右侧有一个边缘 - 指向属性名称。它仍然会占用固定宽度字体上的两个宽度,但可读性就像只有一个符号。
解决方案 11:
是的,self 很繁琐。但是,这样做更好吗?
class Test:
def __init__(_):
_.test = 'test'
def run(_):
print _.test
解决方案 12:
在这里,我遵循@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)
这里一样定义一个方法,并且它不保留文档字符串,它就会失败。但我认为它已经足够很好地证明了这个想法。
扫码咨询,免费领取项目管理大礼包!