为啥从类中访问类变量需要“自我”。在 Python 中? [复制]
Posted
技术标签:
【中文标题】为啥从类中访问类变量需要“自我”。在 Python 中? [复制]【英文标题】:Why accessing to class variable from within the class needs "self." in Python? [duplicate]为什么从类中访问类变量需要“自我”。在 Python 中? [复制] 【发布时间】:2012-11-19 01:47:18 【问题描述】:可能重复:Python ‘self’ explained
我正在学习 Python,我有一个关于从这个类的方法访问类变量的问题,理论多于实践。
例如我们有:
class ExampleClass:
x = 123
def example_method(self):
print(self.x)
为什么一定要准确写self.x
,而不仅仅是x
? x
属于类的命名空间,使用它的方法也属于它。我错过了什么?这种风格背后的理由是什么?
在 C++ 中你可以这样写:
class ExampleClass
public:
int x;
void example_method()
x = 123;
cout << x;
;
;
它会起作用的!
【问题讨论】:
我认为这与范围有关。在模块内部,您可以声明一个“全局”变量并在类声明中调用它,但如果您在类声明中声明某些内容,(显然)它已经被类范围“捕获”,然后需要 self. @senderle 这个问题似乎主要是关于明确传递self
(或者至少这是公认的答案所关注的)。这个问题是关于为什么属性没有隐含的self.
。
附言。将其命名为“self”只是一种惯例和良好做法,它可以是任何东西,比如独角兽。
@delnan,实际上,我认为这两个问题都相当于“为什么有必要准确地写'self.x',而不仅仅是'x'?”但是,如果没有人同意我的观点,那么这不会被关闭:)——我只是想我会提出这是重复的可能性。 (重读另一个问题,我同意这是模棱两可的。)
如果你不接受 Bakuriu 的回答,我会追捕你。对设计决策问题有实际答案是多么令人耳目一新。
【参考方案1】:
来自The History of Python: Adding Support for User-defined Classes:
相反,我决定放弃隐式引用 实例变量。像 C++ 这样的语言可以让你把 this->foo 写成 显式引用实例变量 foo(如果有 单独的局部变量 foo)。因此,我决定做出这样的明确 引用引用实例变量的唯一方法。此外, 我决定不要让当前对象(“this”)成为 特殊关键字,我会简单地使“this”(或其等价物) 方法的第一个命名参数。实例变量总是 被引用为该参数的属性。
使用显式引用,不需要特殊的语法 对于方法定义也不必担心复杂 关于变量查找的语义。相反,一个简单的定义 第一个参数对应于实例的函数,通过 约定被命名为“自我”。例如:
def spam(self,y): print self.x, y
这种方法类似于我在 Modula-3 中看到的方法,它具有 已经为我提供了导入和异常处理的语法。 Modula-3 没有类,但它允许您创建记录类型 包含已初始化的全类型函数指针成员 默认为附近定义的函数,并添加语法糖,所以 如果 x 是这样一个记录变量,而 m 是一个函数指针 该记录的成员,初始化为函数 f,然后调用 x.m(args) 等价于调用 f(x, args)。这符合 对象和方法的典型实现,并使其成为可能 将实例变量等同于第一个参数的属性。
因此,BDFL 本人表示,他决定使用显式自我而不是隐式自我的唯一真正原因是:
这是明确的 它更容易实现,因为查找必须在运行时完成(而不是像其他语言那样在编译时完成),并且具有隐式 self 可能会增加查找的复杂性(从而增加成本)。编辑:Python中也有答案FAQ。
【讨论】:
【参考方案2】:这似乎与 Python 中的模块与类范围处理有关:
COLOR = 'blue'
class TellColor(object):
COLOR = 'red'
def tell(self):
print self.COLOR # references class variable
print COLOR # references module variable
a = TellColor()
a.tell()
> red
> blue
【讨论】:
有趣的清晰代码。但它没有给出原因。请看我的回答。【参考方案3】:这是我在ancient answer 中所做的关于此功能的内容:
你遇到的问题是因为这个:
块是作为一个单元执行的一段 Python 程序文本。 以下是块:模块、函数体和类 定义。
(...)
范围定义了名称在一个范围内的可见性 块。
(...)
类块中定义的名称范围仅限于 类块;它没有扩展到方法的代码块—— 这包括生成器表达式,因为它们是使用 功能范围。这意味着以下将失败:
A 类:
a = 42 b = list(a + i for i in range(10))
http://docs.python.org/reference/executionmodel.html#naming-and-binding
上面的意思是: 函数体是代码块,方法是函数,则在类定义中存在的函数体之外定义的名称不会扩展到函数体。
当我读到这篇文章时,我觉得很奇怪,但这就是 Python 的制作方式:
类块中定义的名称范围仅限于类块;它没有扩展到方法的代码块
官方文档是这么说的。
.
编辑
heltonbiker 写了一段有趣的代码:
COLOR = 'blue'
class TellColor(object):
COLOR = 'red'
def tell(self):
print self.COLOR # references class variable
print COLOR # references module variable
a = TellColor()
a.tell()
> red
> blue
这让我想知道写在方法 tell()
中的指令 print COLOR
是如何引发打印在类外部定义的全局对象 COLOR 的值的。
我在官方文档的这一部分找到了答案:
方法可以像普通方法一样引用全局名称 职能。与方法关联的全局范围是模块 包含它的定义。 (类从不用作全局范围。) 虽然人们很少遇到在某个领域使用全局数据的充分理由 方法,全局范围有许多合法用途:对于一个 导入到全局范围内的事物、函数和模块可以是 由方法以及其中定义的函数和类使用。 通常,包含该方法的类本身是在此定义的 全局范围 (...)
http://docs.python.org/2/tutorial/classes.html#method-objects
当解释器必须执行print self.COLOR
时,因为COLOR不是实例属性(也就是说标识符'COLOR'不属于实例的命名空间),解释器进入实例类的命名空间中搜索标识符“COLOR”并找到它,因此它打印出 TellColor.COLOR
当解释器必须执行print COLOR
时,由于该指令中没有写入属性访问,它将在全局命名空间中搜索标识符'COLOR',官方文档说它是模块的命名空间。
【讨论】:
我见过很多特定领域的模块(例如地理空间),在模块内部全局声明了命名常量(例如,EARTH_RADIUS
。如果有人真的想使用它,只需导入模块,例如 from Geostuff import EARTH_RADIUS
或 import Geostuff; a = Geostuff.EARTH_RADIUS
。【参考方案4】:
附加到对象(及其类,以及该类的祖先)的属性名称在编译时是不可确定的。所以你要么明确地查找属性,要么:
根除局部变量(在方法中)并始终使用实例变量。这没有什么好处,因为它本质上消除了具有所有优点的局部变量(至少在方法中)。 在运行时决定基础x
是指属性还是本地属性(如果没有self.x
,则使用一些额外的规则来决定x = ...
何时添加新属性)。这会降低代码的可读性,因为您永远不知道名称应该是哪一个,并且本质上将 所有方法 中的每个局部变量都变成了公共接口的一部分(因为附加该名称的属性会发生变化方法的行为)。
两者都有一个额外的缺点,即它们需要特殊的方法外壳。现在,“方法”只是一个可以通过类属性访问的常规函数。这对于各种用例非常有用。
【讨论】:
可能会提到为什么这仍然适用于具有基本相同情况的全局变量(实际上,有一个关键字可以区分:global
)
@JonasWielicki 哪些变量属于哪个作用域在编译时是可确定的(如果它被分配,它是本地的;如果不是,它在它被分配到的最里面的作用域中,默认为全局;global
和nonlocal
覆盖)。
啊,是的,你是对的。我总是忘记从全局范围内可用的变量读取时得到的 UnboundLocalError,但稍后在函数中为其赋值。
@delnan 我不明白 在编译时可确定 是什么意思。由什么决定?
@eyquem 由编译器决定。或者真的通过任何程序。或者人类,就此而言。考虑一个执行name = random_identifier(); setattr(something, name, value)
的程序。以上是关于为啥从类中访问类变量需要“自我”。在 Python 中? [复制]的主要内容,如果未能解决你的问题,请参考以下文章