18

I want to have code that does something like this

class myClass:

    def __init__(self):
        self.__var1 = 'var1'
    
    var2 = 'var2'

    def myNormalMethod(self):
        print(self.__var1)

    @classmethod
    def myClassMethod(cls):
       print(cls.__var2)
    
    #How do I do this?
    def myMethod():
        print(self.__var1)
        print(cls.__var2)

Right now the final method does not work as I do not know how I can have access to both self and cls. How do I implement this?

Yury Kirienko
  • 1,810
  • 1
  • 22
  • 32
user2802557
  • 747
  • 1
  • 9
  • 19
  • 3
    `myMethod()` should be declared as `myMethod(self)`, so it has access to `self` as a parameter. (Like `myNormalMethod(self)`, above.) You can get `self`'s class using `type(self)`. – khelwood Jan 24 '17 at 15:47

3 Answers3

19

As a very brief review, self refers to a current instance of the class while cls variables are attached to the class itelf i.e., shared among every instance. Here are some references to help with this, and how I got to your solution:

I modified your sample code to illustrate the difference and included a solution:

class MyClass:
    __var2 = 'var2'
    var3 = 'var3'

    def __init__(self):
        self.__var1 = 'var1'

    def normal_method(self):
        print(self.__var1)

    @classmethod
    def class_method(cls):
       print(cls.__var2)

    def my_method(self):
        print(self.__var1)
        print(self.__var2)
        print(self.__class__.__var2)


if __name__ == '__main__':
    print(MyClass.__dict__['var3'])

    clzz = MyClass()
    clzz.my_method()

__var2 and var3 are variables saved to the class. You can access any class variable without an instance via __dict__ which represents the name space.

Since class variables become a part of every instance, you can just call self to access them. Alternatively, you can explicitly call self.__class__.__var2 to make it clear where the intended variable is actually stored.

Yury Kirienko
  • 1,810
  • 1
  • 22
  • 32
KarmaQueenn
  • 331
  • 2
  • 4
  • Maybe a dumb question, but when I want to access `__var1` inside `class_method`, how do I do that? – Shahidur Oct 18 '22 at 11:53
  • It's worth mentioning that you can access class variables with `self.var`, but not assign them (`self.var = ...` creates an instance variable rather than assigning to the class variable). For that, you *need* `self.__class__.var = ...`). – Dominick Pastore Feb 11 '23 at 18:29
  • @Shahidur You can't. If you find you need to do that, then it probably means your `class_method` should not actually be a `@classmethod`. – Dominick Pastore Feb 11 '23 at 18:33
12

You could just do self.__var2 - any attributes not found on the instance will be automatically looked up in the class.

To make it explicit that you're expecting the attribute to come from the class itself, use self.__class__.__var2.

jasonharper
  • 9,450
  • 2
  • 18
  • 42
0

TL;DR: You can use Descriptors to achieve this.

While the existing answers work for OP's case, they do not generally solve "How have access to both cls and self in a method".

E.g., here is the definition of two Python classes:

class MyClass:

    def __init__(self, name):
        self.name = name

    # TODO: def foo

class MySubclass(MyClass):
    pass

Where I would like to implement MyClass.foo such that it exhibits the following behavior for any subclass MySubclass of MyClass:

MySubclass.foo() == MySubclass.__name__
MySubclass(name="bar").foo() == "bar"

If one could pass both cls and self in the method, this would be easy:

# Does not work because `self` is never set
@classmethod
def foo(cls, self=None):
    if self:
        return self.name
    return cls.__name__

In these kind of cases, you can use Descriptors to build an object that mimics the behavior you want your method to have:

class MyDescriptor:

    def __get__(self, instance, cls):  # instance = self, cls = cls
        if instance is None:
            return lambda: cls.__name__
        else:
            return lambda: instance.name


class MyClass:

    def __init__(self, name):
        self.name = name

    foo = MyDescriptor()