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:
- 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) - Then checks the instance (self)
__dict__
attribute, and sees if it containsattribute
, if so, its fetched - checks if there is a "non data descriptor" (a descriptor without the
__set__
method, such as a function or method) - Then checks if it is a plain (non descriptor) attribute in the class
__dict__
<- you are here! - Checks for a plain attribute in any of the ancestor classes
- Calls
__gettattr__
method on the class if it exists - 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
.