这个方法应该是一个类方法吗?为什么它不能访问变量?

Should this method be a classmethod and why can't it access vars?

我是java出身,所以这里有点迷糊。

考虑下面的代码片段:

class A():
    def __init__(self, **kwargs):
        self.obj_var = "I am obj var"

    @classmethod
    def class_method(cls):
        print cls.obj_var   # this line is in question here
        cls.cls_obj = "I m class object"
        return cls.cls_obj

这会引发错误:

In [30]: a = A()

In [31]: a.obj_var
Out[31]: 'I am obj var'

In [32]: a.class_method()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-32-3dcd9d512548> in <module>()
----> 1 a.class_method()

<ipython-input-29-9c0d341ad75f> in class_method(cls)
      8     @classmethod
      9     def class_method(cls):
---> 10         print cls.obj_var
     11         cls.cls_obj = "I m class object"
     12         return cls.cls_obj

AttributeError: class A has no attribute 'obj_var'

i如果我注释掉 @classmethod,这将正常工作。

我的理解肯定是不正确的):

当我执行 a = A() 时,然后在 __init__ 中创建的所有变量(obj_var)在传递给任何其他 [=17] 时都可以通过此 a 访问=] 相同 class.Apparently 情况并非如此。

问题

我们来谈谈 Python 的方法实际上是如何工作的。

您可能已经注意到 Python 方法的声明方式与 free-standing 函数类似,但在 class 中。那是因为 Python 方法 实际上是 free-standing 恰好在 class 中的函数。 self/cls 参数并不特殊。它只是函数的第一个参数。

在我们继续之前,我想指出您似乎没有明确继承自 object。如果您在 Python 2.x 中工作,则图表的根部没有 object 除非您明确继承它。 这是一件坏事,您应该尽可能在新代码中直接或间接继承自object。从 Python 3 中的 object 继承是合法且无害的,但没有必要。本讨论的其余部分假定您正在 3.x 中工作或已解决此问题。

当您使用 foo.bar 访问变量、函数、方法或任何其他类型的对象时,将调用某些称为 "descriptor protocol" 的挂钩。您无需了解其详细信息即可了解函数的工作原理。你只需要知道这个:

  1. 首先,如果有一个实例变量bar直接附加到foo(并且foo不是class),我们只是return它直接。(*)
  2. 如果 foo 是一个 class 并且 bar 是一个 @classmethod 函数(**) 在 foo 或其超类之一中声明classes,那么第一个参数在我们return之前设置为foo。否则,它 return 不变。(***) 如果我们 return 编辑了一些东西,我们 到此为止
  3. 我们搜索foo的"method resolution order"。这包括 foo 的 class(称为 type(foo)),class 的超级 class,等等,直到我们到达 object.在多重继承的情况下,这会变得有点复杂,但同样,您不需要知道这一点。
  4. 从第一个 class 中取出 bar 变量(称之为 Baz)。
  5. 如果 Baz.bar 是一个常规的、未修饰的函数,将其第一个参数设置为 foo 并 return 它。
  6. 否则,如果 Baz.bar 是一个 @classmethod 函数,将其第一个参数设置为 type(foo) 并 return 它。
  7. 否则,如果 Baz.bar 是一个 @staticmethod 函数,或者根本不是一个函数 (**),return 它不变。

如您所见,如果声明方法 @classmethod第一个参数始终是 class,而不是实例, 无论函数是如何被调用的。这意味着您无权访问 foo 的实例变量,因为您无权访问 foo 本身。 __init__() 中设置的任何变量都是实例变量,因此它们在这里不可见。

以下是我撒的所有谎言:

(*): Python 实际上先完成剩下的工作,然后再回到这一步。但这只对像 @property 这样的东西很重要,它实际上可以覆盖局部实例变量。常规方法不能。

(**):这是一个谎言。在这种情况下,Python 还对 @property 函数以及实现某些特殊方法的任何其他函数进行特殊处理。

(***):我也忽略了 Python 2.x 中的未绑定方法,因为除了某些非常奇怪的情况(当您尝试将错误类型的对象作为第一个参数),它们没有任何区别。它们不存在于 Python 3.

如果您想从 class 方法访问实例变量,您需要在存在实例变量的地方创建 class 的实例。 希望下面的代码能正常工作。

class A():
def __init__(self, **kwargs):
    self.obj_var = "I am obj var"

@classmethod
def class_method(cls):
    self = cls() # Here you are creating an instance of the class (in this case it is class A)
    print(self.obj_var) 
    cls.cls_obj = "I m class object"
    return cls.cls_obj

 print(A.class_method())