12

可能重复:
Python 'self' 解释

我正在学习 Python,我有一个关于从这个类的方法访问类变量的问题,比实际更理论化。

例如我们有:

class ExampleClass:
    x = 123
    def example_method(self):
        print(self.x)

为什么一定要准确地写self.x,不只是xx属于类的命名空间,使用它的方法也属于它。我错过了什么?这种风格背后的理由是什么?

在 C++ 中,您可以编写:

class ExampleClass {
public:
    int x;
    void example_method()
    {
        x = 123;
        cout << x;
    };
};

它会起作用的!

4

4 回答 4

28

来自Python 的历史:添加对用户定义类的支持

相反,我决定放弃隐式引用实例变量的想法。像 C++ 这样的语言允许您编写 this->foo 来显式引用实例变量 foo(以防有单独的局部变量 foo)。因此,我决定让这种显式引用成为引用实例变量的唯一方法。此外,我决定与其将当前对象(“t​​his”)设为特殊关键字,不如简单地将“this”(或其等价物)设为方法的第一个命名参数。实例变量总是作为该参数的属性被引用。

使用显式引用,不需要为方法定义使用特殊的语法,也不必担心与变量查找有关的复杂语义。相反,人们只是简单地定义一个函数,它的第一个参数对应于实例,按照惯例,它被命名为“self”。例如:

def spam(self,y):
    print self.x, y

这种方法类似于我在 Modula-3 中看到的,它已经为我提供了导入和异常处理的语法。Modula-3 没有类,但它允许您创建包含完全类型化函数指针成员的记录类型,这些函数指针成员默认初始化为附近定义的函数,并添加语法糖,以便如果 x 是这样的记录变量,并且 m 是该记录的函数指针成员,初始化为函数 f,然后调用 xm(args) 等效于调用 f(x, args)。这与对象和方法的典型实现相匹配,并且可以将实例变量等同于第一个参数的属性。

因此,BDFL 本人表示,他决定使用显式自我而不是隐式自我的唯一真正原因是:

  • 这是明确的
  • 它更容易实现,因为查找必须在运行时完成(而不是像其他语言那样在编译时完成),并且具有隐式 self 可能会增加查找的复杂性(从而增加成本)。

编辑:Python FAQ中也有一个答案。

于 2012-11-30T19:58:50.370 回答
7

它似乎与 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
于 2012-11-30T19:51:33.893 回答
6

这是我在有关此功能的古老答案中所做的内容:


您遇到的问题是由于以下原因:

块是作为一个单元执行的一段 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

这让我想知道print COLOR在方法内部编写的指令如何tell()引发打印在类外部定义的全局对象 COLOR 的值。
我在官方文档的这一部分找到了答案:

方法可以像普通函数一样引用全局名称。与方法关联的全局范围是包含其定义的模块。(类从不用作全局范围。)虽然很少遇到在方法中使用全局数据的充分理由,但全局范围有许多合法用途:一方面,导入全局范围的函数和模块可以被方法以及其中定义的函数和类使用。通常,包含该方法的类本身是在这个全局范围内定义的(...)

http://docs.python.org/2/tutorial/classes.html#method-objects

当解释器必须执行print self.COLOR时,由于COLOR不是实例属性(也就是说标识符 'COLOR' 不属于实例的命名空间),解释器进入实例类的命名空间搜索标识符“COLOR”并找到它,因此它会打印出TellColor.COLOR的值

当解释器必须执行print COLOR时,由于该指令中没有写入属性访问,它将在全局命名空间中搜索标识符“COLOR”,官方文档说它是模块的命名空间。

于 2012-11-30T19:59:02.680 回答
3

附加到对象(及其类,以及该类的祖先)的属性名称在编译时是不可确定的。因此,您要么使属性查找显式,要么:

  • 根除局部变量(在方法中)并始终使用实例变量。这没有好处,因为它本质上消除了具有所有优点的局部变量(至少在方法中)。
  • 决定一个 basex在运行时是引用一个属性还是 local(x = ...如果没有,则使用一些额外的规则来决定何时添加新属性self.x)。这降低了代码的可读性,因为您永远不知道名称应该是哪个,并且本质上将所有方法中的每个局部变量都变成了公共接口的一部分(因为附加该名称的属性会改变方法的行为)。

两者都有一个额外的缺点,即它们需要特殊的方法外壳。现在,“方法”只是一个可以通过类属性访问的常规函数​​。这对于各种用例非常有用。

于 2012-11-30T19:49:55.827 回答