在Python中定义类变量时如何引用类方法?

时间:2019-02-10 21:39:42

标签: python python-3.x class class-method

我有以下类和类变量:

class MyClass:
    class_var_1 = "a"
    class_var_2 = run_class_method()

    @classmethod
    def run_class_method(cls):
        return "ran class method"

但是,解释器说run_class_method没有定义。使用MyClass.run_class_method()也不起作用。来自Java背景,我不明白为什么这行不通。那么,我该如何解决?

此外,我发现,如果我在类末尾定义类变量,则此方法有效。这在python中被认为是不好的做法吗?

4 个答案:

答案 0 :(得分:2)

MyClass的类属性仍在定义时,尚未定义,因此在定义class_var_2时,MyClass尚不可用。您可以通过在class_var_2定义块之后定义MyClass来解决此问题:

class MyClass:
    class_var_1 = "a"

    @classmethod
    def run_class_method(cls):
        return "ran class method"

MyClass.class_var_2 = MyClass.run_class_method()

答案 1 :(得分:2)

python中的类主体是可执行上下文,与仅包含声明的Java不同。这最终意味着在类定义中执行顺序很重要。

引用文档:

  

类定义是一个可执行语句。

     

...

     

然后,使用新创建的本地名称空间和原始的全局名称空间,在新的执行框架中执行该类的套件(请参阅命名和绑定)。 (通常,该套件主要包含函数定义。)当该类的套件完成执行时,其执行框架将被丢弃,但其本地名称空间将被保存。 [4]然后,使用基类的继承列表和属性字典的已保存本地名称空间创建类对象。类名绑定到原始本地名称空间中的该类对象。

更多lengthier explanations

如果您要调用函数来定义类变量,则可以通过以下方式之一进行操作:

  1. 使用静态方法:

    class MyClass:
        def _run_instance_method():
            return "ran instance method"
        run_instance_method = staticmethod(_run_instance_method)
    
        class_var_1 = "a"
        class_var_2 = _run_instance_method() # or run_instance_method.__func__()
    
  2. 或将其定义为独立函数:

    def run_method():
        return "ran method"
    
    class MyClass:
        class_var_1 = "a"
        class_var_2 = run_method()
    
        # optional
        run_method = staticmethod(run_method)
    
  3. 或使用__func__访问原始函数并提供一个虚拟cls值:

    class MyClass:
        @classmethod
        def run_class_method(cls):
            return "ran class method"
    
        class_var_1 = "a"
        class_var_2 = run_class_method.__func__(object())
    
  4. 或在创建类后设置类变量:

    class MyClass:
        @classmethod
        def run_class_method(cls):
            return "ran class method"
    
        class_var_1 = "a"
    
    MyClass.class_var_2 = MyClass.run_class_method()
    

答案 2 :(得分:1)

首先要注意的是Java没有类方法。它具有静态方法和常规方法。常规方法将作为参数的实例从其接收。从该类(不是在其上定义的类)中调用该类的类方法作为参数。静态方法没有什么特别的,它的行为类似于普通函数-静态方法只是将逻辑相关方法进行分组的一种方式。

要注意的第二件事是将Java类定义解析为单独的类定义和隐式静态构造函数。初始化类属性时,这使您可以在类主体中定义方法之前调用它们。这是因为在实际程序中,只有在创建类/将类加载到内存之后才调用这些语句。在Python中没有这种区别。相反,要创建一个类,可以在一个专门的名称空间内执行一系列语句,然后将其用于创建该类。就像在函数或代码块的主体中​​一样,您不能在变量存在之前使用它。这包括在类主体中使用该类(因为它尚不存在!)

例如这是有效的Java:

class X {
    static int i = 1;
    static X obj = newInstance();
    // ^-- executed after the class has been created, but is still being initialised.
    static X newInstance() {
        return new X();
    }
}

但这不是有效的Python

class X:
    val = 1
    obj = new_instance()
    # ^-- We're still in the body of X, and neither new_instance nor X has been created yet
    @classmethod
    def new_instance(cls):
        return cls()
    # even if new_instance was defined before obj, Python still wouldn't be able to fill
    # in the cls argument as X still doesn't exist when new_instance is first invoked

在Python中,您必须明确地对类进行静态构造。请记住,这正是Java中会发生的事情,只是隐藏在语法糖之后。

class X:
    val = 1 # this can still be done in the class body as it doesn't need the class
    obj = None # not necessary, but can help type checkers know that X has an
    # attribute obj -- you can use type annotations to further help
    @classmethod
    def new_instance(cls):
       return cls()
# explicit class initialisation of attributes
X.obj = X.new_instance()

答案 3 :(得分:0)

执行此操作的另一种方法是定义一个父类,该父类可以控制其子类(或元类)的创建。下面,我们在父类中使用__init_subclass__在类创建期间设置属性。

class InitVar():
    def __init_subclass__(cls, varname, funcname, **kwargs):
        class_method = getattr(cls, funcname)
        setattr(cls, varname, class_method())

class MyClass(InitVar, varname="class_var_2", funcname="run_class_method"):
    class_var_1 = "a"
    @classmethod
    def run_class_method(cls):
        return "ran class method"

print(MyClass.class_var_2)
# ran class method