Stupid Pig
Stupid Pig

Reputation: 101

python __init__ vs class attributes

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

Answers (5)

Be Hai Nguyen
Be Hai Nguyen

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

Mark Whitfield
Mark Whitfield

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

멍개-mung
멍개-mung

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

Heapify
Heapify

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

user8743642
user8743642

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

Related Questions