Reputation: 101
I am very new to programming. I just started for couple weeks. I spent hours reading about class but I am still confused. I have a specific question.
I am confused on when to use class attributes, and when to use initializer (__init__
).
I understand that when using __init__
, I don't assign any value immediately, but only need to assign value when I create an object using that class. And class attributes are automatically inherent to the object created under that class.
But in term of practical use, do they accomplished the same thing? Are they just two different ways to do the same thing? Or is __init__
does something that class attributes can't do?
I went some testing with these codes, the results are the same. I am confused when to use which. To me class attribute looks more convenient to use.
#use class attributes for class Numbers_1
class Numbers_1:
one = 1
two = 2
three = 3
six = two * three
def multiply(self):
return self.six * self.two * self.three
#use initializer for class Numbers_2
class Numbers_2:
def __init__(self, num10, num20, num30, num600):
self.num10 = num10
self.num20 = num20
self.num30 = num30
self.num600 = num600
def multiply(self):
return self.num600 * self.num20 * self.num30
#Now I run some test to compare the two classes...
x = Numbers_1()
y = Numbers_2(10, 20, 30, 20*30)
print(x.one) #print 1
print(y.num10) #print 10
print(x.six) #print 6
print(y.num600) #print 600
#assign attributes to each objects
x.eighteen = x.six * x.three
y.num18000 = y.num600 * y.num30
print(x.eighteen) #print 18
print(y.num18000) #print 18000
#try printing methods in each object
print(x.multiply()) #print 36
print(y.multiply()) #print 360000
#try reassign values to attributes in each object
x.one = 100
y.num10 = 1000
print(x.one) #prints 100
print(y.num10) #prints 1000
Upvotes: 10
Views: 12755
Reputation: 67
Regarding class attributes, there are some subtle issues which we should be aware of.
According to https://docs.python.org/3/tutorial/classes.html:
9.4. Random Remarks
If the same attribute name occurs in both an instance and in a class, then attribute lookup prioritizes the instance.
Also, I have observed that, changing the value of a class attribute, this change of value propagates back to instances whom attribute value has not been set (overriden). This is in conformance with documentation above.
Let's explore this some examples. First, consider this class:
class Engine:
started = False;
def start(self):
self.started = True;
Let's instantiate it and see how started
attribute behaves:
engine1 = Engine()
print( f"1. engine1.started: {engine1.started}" );
print( f"1. Engine.started: {Engine.started}" );
Both are False
as expected:
1. engine1.started: False
1. Engine.started: False
Continue on:
engine1.start()
print( f"2. engine1.started: {engine1.started}" );
print( f"2. Engine.started: {Engine.started}" );
The output is:
2. engine1.started: True
2. Engine.started: False
Indeed, I see that:
then attribute lookup prioritizes the instance.
Consider this instantiation:
Engine.started = True
engine2 = Engine()
print( f"3. engine2.started: {engine2.started}" );
print( f"3. Engine.started: {Engine.started}" );
Both are True
, it should make sense for us:
3. engine2.started: True
3. Engine.started: True
Personally, this leads to this question: What happens to the value of the instance attribute when the value of the class attribute was changed? That is:
# Note: Engine.started is True from (3, engine2) above.
engine3 = Engine()
# Expected True.
print( f"4. engine3.started: {engine3.started}" );
Engine.started = False
# Expected False.
print( f"5. engine3.started: {engine3.started}" );
Output:
4. engine3.started: True
5. engine3.started: False
This is the observation that I have stated above:
changing the value of a class attribute, this change of value propagates back to instances whom attribute value has not been set (overriden).
Secondly, this is an example from the document page quoted above, I have tweaked it a tiny bit.
class Warehouse:
purpose = 'Storage'
region = 'west'
w1 = Warehouse()
print( "1: ", w1.purpose, w1.region )
Output:
1: Storage west
Then:
w2 = Warehouse()
w2.region = 'east'
print( "2: ", w2.purpose, w2.region )
2: Storage east
Warehouse.region = 'north'
w3 = Warehouse()
print( "3: ", w3.purpose, w3.region )
3: Storage north
What aboute w1 and w2?
print( f"4: w1.region: {w1.region}, w2.region: {w2.region}")
Output, we should be able to work out why:
4: w1.region: north, w2.region: east
Finally, changing a class attribute value by a parent class propagates the change of value down to child classes, but not vice versa.
Consider this example:
class Engine:
started = False;
def start(self):
self.started = True;
class TwoStrokeEngine(Engine):
pass
class FourStrokeEngine(Engine):
pass
# Expected: all False.
print( f"1. Engine.started: {Engine.started}" );
print( f"1. TwoStrokeEngine.started: {TwoStrokeEngine.started}" );
print( f"1. FourStrokeEngine.started: {FourStrokeEngine.started}\n" );
Engine.started = True
# Expected: all True.
print( f"2. Engine.started: {Engine.started}" );
print( f"2. TwoStrokeEngine.started: {TwoStrokeEngine.started}" );
print( f"2. FourStrokeEngine.started: {FourStrokeEngine.started}\n" );
Engine.started = False
# Expected: all False.
print( f"3. Engine.started: {Engine.started}" );
print( f"3. TwoStrokeEngine.started: {TwoStrokeEngine.started}" );
print( f"3. FourStrokeEngine.started: {FourStrokeEngine.started}\n" );
FourStrokeEngine.started = True
# Expected: False, False, True
print( f"4. Engine.started: {Engine.started}" );
print( f"4. TwoStrokeEngine.started: {TwoStrokeEngine.started}" );
print( f"4. FourStrokeEngine.started: {FourStrokeEngine.started}" );
Output:
1. Engine.started: False
1. TwoStrokeEngine.started: False
1. FourStrokeEngine.started: False
2. Engine.started: True
2. TwoStrokeEngine.started: True
2. FourStrokeEngine.started: True
3. Engine.started: False
3. TwoStrokeEngine.started: False
3. FourStrokeEngine.started: False
4. Engine.started: False
4. TwoStrokeEngine.started: False
4. FourStrokeEngine.started: True
Upvotes: 2
Reputation: 2520
To understand the difference, you need to consider the difference between classes and instances of those classes.
Class attributes apply to every object of that class. Modifying them modifies all instances of that class (excepting the instances that had this attribute explicitly modified before changing it in the Class itself). Modifying instance attributes modifies only the specific object being manipulated.
For example:
class Foo:
class_var = 'bar'
def __init__(self):
self.instance_var = 'baz'
foo1 = Foo()
foo2 = Foo()
print(foo1.class_var, foo2.class_var)
print(foo1.instance_var, foo2.instance_var)
Foo.class_var = 'quux'
Foo.instance_var = "this doesn't work"
foo1.instance_var = 'this does'
print(foo1.class_var, foo2.class_var)
print(foo1.instance_var, foo2.instance_var)
prints
bar bar
baz baz
quux quux
this does baz
And if we do:
foo1.class_var = 'spam'
Foo.class_var = 'eggs'
print(foo1.class_var, foo2.class_var)
it prints
spam eggs
foo1 stands, since it was modified before the Class.
So, modifying Foo.class_var
replaces class_var
for all existing instances of Foo
(excepting previously modified ones), while modifying Foo.instance_var
does nothing. Modifying instance_var
on an object of type Foo
, however, does work, but only for that specific instance -- other instances are unchanged.
Upvotes: 7
Reputation: 480
If you create multiple objects, you can see the difference
class Numbers_1:
one = 1
two = 2
six = one * two
def __init__(self, o, t):
self.o = o
self.t = t
def mul(self):
return self.o * self.t
o1 = Numbers_1(1, 2)
o2 = Numbers_1(10, 20)
o3 = Numbers_1(20, 30)
print(o1.six) # 2
print(o2.six) # 2
print(o3.six) # 2
print(o1.mul()) # 2
print(o2.mul()) # 200
print(o3.mul()) # 600
variable such as one, to, six are called class variables.
Class variables are shared by objects created with the same class.
Upvotes: 3
Reputation: 2891
class attributes are not object specific. For example:
x = Numbers_1()
y = Numbers_1()
In the above, x and y will have same class attributes.
On the contrary, init function defines object attributes. For example:
s = Numbers_2(10, 20, 30, 20*30)
t = Numbers_2(11, 21, 31, 21*31)
s and t now has different object attributes.
Upvotes: 1
Reputation:
You got everything right - except that class attributes also function like static variables in python.
Note however that everything in the class scope is run immediately upon parsing by the python interpreter.
# file1.py
def foo():
print("hello world")
class Person:
first_name = foo()
last_name = None
def __init__(self):
last_name = "augustus"
print("good night")
# file2.py
import file1
>>> "hello world"
x = Person()
>>> "good night"
Upvotes: 8