How to refer to class methods when defining class variables in Python?
Asked Answered
R

4

8

I have the following class and class variables:

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

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

However, the interpreter says that run_class_method isn't defined. Using MyClass.run_class_method() doesn't work either. Coming from a java background, I don't understand why this doesn't work. So, how can I fix it?

Additionally, I discovered that this works if I define class variables at the end of the class. Is this considered bad practice in python?

Retiary answered 10/2, 2019 at 21:39 Comment(3)
You are missing cls argument def run_class_method(cls)Knockwurst
Oh, thanks for that. I forgot to add it when typing. Thats not the reason though. Python interpreter still throws an error saying run_class_method isn't defined @KnockwurstRetiary
The critical thing to understand is the the class object does not exist when the class definition is being executed.Mufi
R
8

Class body in python is an executable context, not like Java that only contains declaration. What this ultimately means is that sequence of execution is important within a class definition.

To quote the documentation:

class definition is an executable statement.

...

The class’s suite is then executed in a new execution frame (see Naming and binding), using a newly created local namespace and the original global namespace. (Usually, the suite contains mostly function definitions.) When the class’s suite finishes execution, its execution frame is discarded but its local namespace is saved. [4] A class object is then created using the inheritance list for the base classes and the saved local namespace for the attribute dictionary. The class name is bound to this class object in the original local namespace.

Some more lengthier explanations.

If you want to call a function to define a class variable, you can do it with one of these ways:

  1. use 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. or define it as a standalone function:

    def run_method():
        return "ran method"
    
    class MyClass:
        class_var_1 = "a"
        class_var_2 = run_method()
    
        # optional
        run_method = staticmethod(run_method)
    
  3. or access the original function with __func__ and provide a dummy cls value:

    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. or set the class variables after class creation:

    class MyClass:
        @classmethod
        def run_class_method(cls):
            return "ran class method"
    
        class_var_1 = "a"
    
    MyClass.class_var_2 = MyClass.run_class_method()
    
Rosannrosanna answered 10/2, 2019 at 22:7 Comment(1)
You can use staticmethod the same way you did classmethod (as a decorator) { class MyClass: @staticmethod def run_class_method(): return "ran class method" class_var_1 = "a" class_var_2 = run_class_method.__func__(object()) }Sext
D
3

MyClass is not yet defined when its class attributes are still being defined, so at the time class_var_2 is being defined, MyClass is not yet available for reference. You can work around this by defining class_var_2 after the MyClass definition block:

class MyClass:
    class_var_1 = "a"

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

MyClass.class_var_2 = MyClass.run_class_method()
Disapprobation answered 10/2, 2019 at 21:53 Comment(3)
So, to clarify, there is no way to reference the class method from inside the class when the class variable is defined before the class method? Is putting the class variables at the end of the class bad practice?Retiary
@YangK Python is a dynamic language, and a class definition is just executable code. You can never reference a variable in a code block that hasn't been defined.Mufi
@Mufi got it thanks, but is defining class variables after the class methods bad practice? It works so Im wondering if i should use itRetiary
P
2

The first thing to note is that Java does not have class methods. It has static methods and regular methods. A regular method receives the instance it was called from as an argument. A class method receives the class is was called from (not the class it is defined on) as an argument. Static methods get nothing special and act like normal functions -- static methods are just a way of grouping logically related methods.

The second thing to note is that a Java class definition is parsed into a separate class definition and an implicit static constructor. When initialising class attributes this enables you to call methods before they are defined in the class body. This is because in the actual program these statements will be called only after the class has been created/loaded into memory. In Python there is no such distinction. Instead, to create a class you execute a series of statements inside a specialised namespace, and this is then used to create the class. Like in a body of a function or module block of code you cannot use a variable before it is exists. This includes using the class within the class body (as it doesn't exist yet!)

eg. This is valid 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();
    }
}

But this is not valid 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

In Python you must do the static construction of your class explicitly. Bear in mind this is exactly what would happen in Java, it's just hidden behind syntactic sugar.

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()
Poetics answered 10/2, 2019 at 22:25 Comment(0)
A
0

Another way to do this would be to define a parent class that has control over the creation of its subclasses (or a metaclass). Below, we use __init_subclass__ in a parent class to set the attribute during class creation.

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
Apprehensible answered 10/2, 2019 at 21:59 Comment(3)
Thanks for the answer and time. But i don't know, seems kinda hackyRetiary
@YangK It is kind of hacky. That should probably slow you down some and make you think about your design. Why do you need to execute a method of the class before the class exists? Could that method just be a standalone function instead? Would the class attribute make more sense as a property or other descriptor?Apprehensible
Yes, I have been considering my design, but I using the marshmallow library and I'm trying to adjust my design to use their library properly. This is creating some problems, however :)Retiary

© 2022 - 2024 — McMap. All rights reserved.