Home > Software design >  Ways to access class variable in method - python
Ways to access class variable in method - python

Time:05-20

Can you please tell me what is a difference between calling Class_name.class_variable and self.class_variable inside method. Here is the example:

class Employee:

   raise_amount = 1.04

   def __init__(self, first, last, pay):
       self.first = first
       self.last = last
       self.pay = pay

   def apply_raise(self):
       self.pay = int(self.pay * Employee.raise_amount)

So I used an Employee.raise_amount, but i can also write this method like that:

def apply_raise(self):
    self.pay = int(self.pay * self.raise_amount)

I tested that with:

emp_1 = Employee('James', 'Amb', 10000)
emp_2 = Employee('Test', 'User', 20000)

print("Original value")
print("emp_1.raise_amount", emp_1.raise_amount)
print("emp_2.raise_amount", emp_2.raise_amount)

emp_1.raise_amount = 1.1
print("emp_1.raise_amount = 1.1")
print("emp_1.raise_amount", emp_1.raise_amount)
print("emp_2.raise_amount", emp_2.raise_amount)

Employee.raise_amount = 1.2
print("Employee.raise_amount = 1.2")
print("emp_1.raise_amount", emp_1.raise_amount)
print("emp_2.raise_amount", emp_2.raise_amount)

I run the program using Employee.raise_amount and then self.raise_amount. In both situation OUTPUT is the same:

Original value
emp_1.raise_amount 1.04
emp_2.raise_amount 1.04
emp_1.raise_amount = 1.1
emp_1.raise_amount 1.1
emp_2.raise_amount 1.04
Employee.raise_amount = 1.2
emp_1.raise_amount 1.1
emp_2.raise_amount 1.2

So what is a difference and when should I use Class_name.class_variable and self.class_variable

CodePudding user response:

Python attribute reading, with self.attribute works like this:

  1. Python checks if the attribute is a data descriptor in the class or ancestor class, if so, its __get__ method is called (not the case, I will get back here later)
  2. Then checks the instance (self) __dict__ attribute, and sees if it contains attribute, if so, its fetched
  3. checks if there is a "non data descriptor" (a descriptor without the __set__ method, such as a function or method)
  4. Then checks if it is a plain (non descriptor) attribute in the class __dict__ <- you are here!
  5. Checks for a plain attribute in any of the ancestor classes
  6. Calls __gettattr__ method on the class if it exists
  7. Raises attribute error.

This set of rules is actually rather natural when one is making use of the language. But you have to take care if at any point you are writting back to the attribute - if you do something like self.attribute = value, then the attribute is set in the instance, not on the class, and if in other method you are retrieving it by doing ClassName.attribute it will see the orignal value, set on the class, not the value set on self, even if it is the same instance.

All things considered, the natural design of always using self.attribute will work better for read-only class attributes. If you need a special value for a single instance, you can set a new value for that instance only, and everything keeps working. For an example using your case, let's say that for most employees, the raise_ammount is 1.04, but one of them is special cased to be 1.06, any method or even external code can set the new attribute on the instance, and methods reading the value from self. will peek the updated number.

As for "descriptors" - they are special objects bound to the class that feature one of __get__, __set__ or __delete__ methods, and override attribute access on the instance and class. They are the mechanism used by the @property decorator, and by methods and classmethods themselves (so that the language can insert the self parameter in method calls).

Oh, and all these steps are encoded in the language in object.__getattribute__ method (not the same as __getattr__). Any class that overrides __getattribute__ can rewrite these rules at will (usually one will only tweak access to certain attributes, in order to create a proxy object, or something similar, not come out with a full hierarchical set of rules nearly as complex)

CodePudding user response:

One plays nice with inheritance, the other doesn't.

class Base:
    a = 1

    def f(self):
        print(Base.a)


class Foo(Base):
    a = 2

Base().f()
Foo().f()

outputs

1
1

Changing print(Base.a) to print(self.a) changes the output to

1
2

because now self is an instance of Foo (so it refers to Foo.a) where Base.a unsurprisingly refers to Base.a, regardless of the current instance we use to call f.

  • Related