1

I'm learning how classes works and I got stuck on a thing which I can't explain, I also didn't find a solution on the Internet so here am I with my first question on StackOverflow.

class Swords:
    damage = 5

Here is a class with only one line, with attribute damage. Let's make an instance of the class:

sharp_sword = Swords()

And here is the moment of truth. My class doesn't have an init function, so right now my sharp_sword.__dict__ is empty - my object of the class has an empty namespace, it doesn't have any attributes or methods. Well, we can create an attribute since we already defined it in the class itself:

sharp_sword.damage = 10

Now if we print(sharp_sword.damage) the return will be 10, if we print(Swords.damage), the return will be 5. That means that our instance of the class has its own attribute - and it is without any init inside my class. I can create another object, set its damage to 15, and I will have two objects with different damage. Since we didn't change damage in class, if we print(Swords.damage), the return will be 5.

The question coming by itself - why then should I use __init__ inside my class to set properties for objects if I can do it without it with even fewer lines of code in my class?

class SwordsWithoutInit:
    damage = 5

class SwordsWithInit:
    def __init__(self):
        self.damage = 5

big_sword = SwordsWithoutInit()
big_sword.damage = 10

sharp_sword = SwordsWithInit()
sharp_sword.damage = 20

print(f'big sword (class {type(big_sword)}) damage equals {big_sword.damage}')
print(f'sharp sword (class {type(sharp_sword)}) damage equals {sharp_sword.damage}')

# it both works the same way
# note that even if you create another object of class without init, it still will work the same as with init, there won't be any errors

another_sword = SwordsWithoutInit()
another_sword.damage = 33

print(f'big sword (class {type(big_sword)}) damage equals {big_sword.damage}')
print(f'another sword (class {type(another_sword)}) damage equals {another_sword.damage}')

print(f'Class without init still will have damage attribute equals to {SwordsWithoutInit.damage} in itself - we didnt change it')

'''
output:
big sword (class <class '__main__.SwordsWithoutInit'>) damage equals 10
#sharp sword (class <class '__main__.SwordsWithInit'>) damage equals 20
big sword (class <class '__main__.SwordsWithoutInit'>) damage equals 10
another sword (class <class '__main__.SwordsWithoutInit'>) damage equals 33
Class without init still will have damage attribute equals to 5 in itself - we didnt change it
'''
Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
Acid Fronn
  • 13
  • 3

3 Answers3

2

The way you've set this up, via some odd sequence you'll sort of get the result you expect either way, but it's mostly due to int being immutable.

So, let's look at a mutable type instead:

class Sword:
  pass

class Spear:
  pass

# All players get a sword by default
class Items:
  weapons = [Sword()]
  
player1_items = Items()
player2_items = Items()

# give player2 a spear
player2_items.weapons.append(Spear())

# player1 should still just have a sword right?
# whoops! they have a spear too!
print(player1_items.weapons)

live example

You might now ask "well then I'll just use __init__ if I have mutable types." and I suppose that would work for the cases you're describing. It's just a strange way of structuring the code and will make it harder to read over time. To answer the question why you should favor assigning to self even "if I can do it without it with even less lines of code"..

  1. Minimizing the number of lines isn't a good motivation to do something.
  2. Keeping your code readable, maintainable, and fairly consistent is a good motivation.
Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
0

Listen, init func is very important. With its help you can set the attributes while you create the object. like

sword = Sword(5)

This function is very useful, when you have multiple attributes to set coz you won't want to set each attribute separetly, it is also very useful when you have to make many object of the same the class, you won't want to write another extra lines for each just setting the attributes.

Hope that helps!!

Ultimate48
  • 18
  • 3
0

Why do we use __init__ in Python?

Using __init__ is simpler

At first glance, your approach does seem shorter. By not implementing an __init__ method, your class definition becomes one line shorter and looks much cleaner:

class NoInitSword:
    damage = 5

However, it actually requires a lot more code. You now have to write two lines each time you want a sword with a different value:

butter_knife = NoInitSword()
butter_knife.damage = 1

This may not seem like a big deal, but it can quickly get messy. For example, if you wanted to create a list of swords:

big_sword = NoInitSword()
big_sword.damage = 10

sharp_sword = NoInitSword()
sharp_sword.damage = 20

normal_sword = NoInitSword()

big_sharp_sword = NoInitSword()
big_sharp_sword.damage = 30

sword_collection = [big_sword, sharp_sword, normal_sword, big_sharp_sword]

Or:

sword_collection = [NoInitSword(), NoInitSword(), NoInitSword(), NoInitSword()]
sword_collection[0].damage = 10
sword_collection[1].damage = 20
sword_collection[3].damage = 30

A better approach

Fortunately, Python provides us a way of creating an object and setting its attributes to whatever values we want—all in just one line. The only thing we have to do is define an __init__ method.

This method takes arguments that we can use when initializing the object. For instance:

class Sword:
    def __init__(self, damage=5):
        self.damage = damage

This allows you to easily create new objects in one line:

big_sword = Sword(10)    # big_sword.damage == 10
sharp_sword = Sword(20)  # sharp_sword.damage == 20
normal_sword = Sword()   # normal_sword.damage == 5

And that list example from earlier? Here's what it looks like with an __init__ method:

sword_collection = [Sword(10), Sword(20), Sword(), Sword(30)]
pythonista
  • 123
  • 1
  • 7