Как ссылаться на методы класса при определении переменных класса в Python? - PullRequest
0 голосов
/ 11 февраля 2019

У меня есть следующий класс и переменные класса:

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 голосов
/ 11 февраля 2019

Первое, что нужно отметить, это то, что в 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()
0 голосов
/ 11 февраля 2019

Другой способ сделать это - определить родительский класс, который контролирует создание своих подклассов (или метакласса).Ниже мы используем __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
0 голосов
/ 11 февраля 2019

Тело класса в python является исполняемым контекстом, не похожим на Java, который содержит только объявление.В конечном итоге это означает, что последовательность выполнения важна в определении класса.

Чтобы процитировать документацию:

определение класса является исполняемым оператором.

...

Комплект класса затем выполняется в новом кадре выполнения (см. Имена и привязка), используя вновь созданное локальное пространство имен и исходное глобальное пространство имен.(Обычно набор содержит в основном определения функций.) Когда набор класса завершает выполнение, его кадр выполнения отбрасывается, но его локальное пространство имен сохраняется.[4] Объект класса затем создается с использованием списка наследования для базовых классов и сохраненного локального пространства имен для словаря атрибутов.Имя класса связано с этим объектом класса в исходном локальном пространстве имен.

Еще несколько длинных пояснений .

Если вы хотите вызвать функцию для определенияпеременную класса, вы можете сделать это одним из следующих способов:

  1. использовать staticmethod:

    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()
    
0 голосов
/ 11 февраля 2019

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