【问题标题】:Should this method be a classmethod and why can't it access vars?这个方法应该是一个类方法,为什么它不能访问变量?
【发布时间】:2016-04-20 16:42:20
【问题描述】:

我来自java背景,所以在这里我有点困惑。

考虑下面的代码 sn-p:

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'

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

我的理解确实不正确):

当我执行a = A() 时,在__init__ 创建的所有变量(obj_var) 可以通过此a 访问,当传递给同一类的任何其他classmethod 时。显然情况并非如此.

问题

  • 当方法中提到装饰器@classmethod 时,为什么我无法访问class_method 中的__init__ vars,但在移除装饰器时,它可以正常工作?

  • python 在编译时如何在内部处理这个特定的类?

  • 我有什么办法可以在同一方法中使用 @classmethod__init__ 变量?

【问题讨论】:

  • 如果你想要实例方法,为什么要使用classmethod
  • @NoobEditor:这些或多或少是一样的,但同样,你没有以任何方式解释你为什么使用classmethod。您似乎认为类方法只是您在类中定义的任何方法,但事实并非如此。您可能想查看this questionthis question。如果你想访问实例变量,不要使用classmethod。差不多就是这样。
  • Python 类方法类似于 Java 静态方法
  • @DavidHeffernan:不,他们不是。 Java 静态方法不参与多态性。它们不能覆盖或被其他方法(静态或其他方法)覆盖,并且不能在运行时在没有大量反射的情况下以多态方式调用。 Python 没有这些限制。
  • @Kevin 我说“类似于”,而不是“相同”。请记住,asker 是一个认为 classmethod 是实例方法的新手。就一个简单而有用的类比而言,我的评论是合理的。新手根本无法理解你刚才所说的话。虽然我知道你所说的一切,但你认为提问者能理解吗?想想学习过程。对于提问者来说,你的答案是完全错误的。

标签: python decorator python-decorators instance-variables class-method


【解决方案1】:

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

您可能已经注意到,Python 方法的声明方式与独立函数一样,但在类中。那是因为 Python 方法真的是独立的函数,恰好在一个类中。 self/cls 参数并不特殊。这只是函数的第一个参数。

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

当您使用foo.bar 访问变量、函数、方法或任何其他类型的对象时,会调用某些称为“描述符协议”的挂钩。你不必知道这个细节来理解函数是如何工作的。你只需要知道:

  1. 首先,如果有一个实例变量bar直接附加到foo(并且foo不是一个类),我们直接返回它。(*)
  2. 如果foo 是一个类并且bar 是一个@classmethod 函数(**) 在foo 或其超类中声明,那么在我们返回之前第一个参数设置为foo它。否则,原样返回。(***) 如果我们返回了一些东西,我们停在这里
  3. 搜索foo的“方法解析顺序”。这包括foo 的类(称为type(foo))、该类的超类等等,直到我们到达object。在多重继承的情况下,这会变得有点复杂,但同样,您不需要知道这一点。
  4. 从第一个类中获取bar 变量(称为Baz)。
  5. 如果Baz.bar 是一个常规的、未修饰的函数,请将其第一个参数设置为foo 并返回它。
  6. 否则,如果Baz.bar@classmethod 函数,则将其第一个参数设置为type(foo) 并返回。
  7. 否则,如果Baz.bar@staticmethod 函数,或者根本不是函数(**),则原样返回。

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

以下是我撒谎的所有内容:

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

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

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

【讨论】:

  • 迟到了……我知道了!!
  • 我会在前面加上一行“因为你不能只将实例变量作为类变量来访问;否则如果你有 2 个以上的实例,那么 clas 怎么知道你要访问哪个实例参考?!”。无论如何,这个答案是矫枉过正的,警告“在 Python 2.x 中,类可能继承自 object,您应该将其移至脚注。关于 MRO 的长篇大论是矫枉过正。OP 只是对他们无法访问感到困惑实例变量就像它们是类变量一样。
【解决方案2】:

如果你想从一个类方法访问一个实例变量,你需要创建一个实例变量所在的类的实例。 希望下面的代码应该可以工作。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-17
    • 2018-11-01
    • 1970-01-01
    • 2014-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-10
    相关资源
    最近更新 更多