Class and instance attributes... what's that?
I love Porsche! Credit: ? Nick Morozov: https://flic.kr/p/nBshnQ

Class and instance attributes... what's that?

Hello everyone to another blog post. This time I'll be explaining about what's a class and what's an instance attribute in the Python programming language. But as always, let's begin, well, with the beginning. What is a class and what is an instance.

Object-Oriented Programming - a primer

First things first. Object-Oriented Programming is one of multiple programming paradigms in existence, this one is based on the concept of "objects" which can contain data and code. In OOP, the data stored is named attributes and the code in the form of functions is named methods.

So, we have this object that has its own functionality and its own data. And how do you create this thing, you may ask? Well, to create an object you need a blueprint. That blueprint in OOP is called class.

Ok, too much jargon introduced. Let's build upon a simple example to illustrate this further and then explain the concept of the title of this article.

Let's take a car for example, or rather, the concept of a car. Think. What do all cars have in common (or the vast majority of them)?

Well, almost all of them have wheels (4 of them in fact), a steering wheel, an engine (obviously), a chassis to support the whole structure, a gear shifter (it doesn't matter if the car is manual, or automatic), and at least, a seat for the driver.

And what does a car do? Easy, they transport you from one place to another, but before driving, you've got to turn them on. If you get to your destination, you need to park your car. And you need to refuel it (yes, electric ones need to be plugged in, this counts as "refuel", don't sue me).

Let's take these characteristics and place a nice image for you to remember everything said:

Car attributes and methods

Nice, so here we have a blueprint of a car (a class). You can create your Volvo of your dreams, or if you fancy a Mercedes, then be my guest. The action of creating (instantiating) an object (see why the term works?) is called an instance.

Beware! Not all objects are modeled after real world ones, you can make objects of abstract things (non-tangible ones). The sky is your imagination.

A car class

Ok. Let's create a representation in code of this class called "car" in Python:

class Car:
    engine = None
    gear_shifter = None
    seats = None


suv = Car()
suv.engine = 1000
suv.gear_shifter = "automatic"
suv.seats = 5
print("SUV engine {:d}CC, {} and with {:d} seats".
      format(suv.engine, suv.gear_shifter, suv.seats))

  
  
Output:

SUV engine 1000CC, automatic and with 5 seats

We have a basic car. Excellent. But that method of creating an object is a bit cumbersome don't you think? Entering each attribute as we need to assign it to our suv object is boring. Let's try to make it a little more efficient.

class Car:
    def __init__(self, engine, gear_shifter, seats):
        self.engine = engine
        self.gear_shifter = gear_shifter
        self.seats = seats


suv = Car(1000, "automatic", 5)
print("SUV engine {:d}CC, {} and with {:d} seats".
      format(suv.engine, suv.gear_shifter, suv.seats))



Output:

SUV engine 1000CC, automatic and with 5 seats

"Hey", you might ask, "what's that __init__ thing? Well. That's our constructor: that's the method that creates a new instance of our class Car. Below them we have all of our three attributes created earlier: the engine of our car, the type of the gear shift and the number of seats.

Instance attributes

Those attributes mentioned, called by self (which is a form to represent the object itself), are called instance attributes. These are attributes that can be used by each instance of the class. But they can change. Let's create another car:

class Car:
    def __init__(self, engine, gear_shifter, seats):
        self.engine = engine
        self.gear_shifter = gear_shifter
        self.seats = seats


sports_car = Car(3000, "automatic", 1)
print("Sports car engine {:d}CC, {} and with {:d} seats".
      format(sports_car.engine, sports_car.gear_shifter, sports_car.seats))



Output:

Sports car engine 3000CC, automatic and with 1 seats

See? Our sports car information may be totally different than our earlier SUV, and that's ok. Not all cars are the same. Remember that to access the info of the car we use name_of_instance.name_of_attribute, like the examples above that we accessed to print the info, if the information is accessible to the public, that is (the attribute is public).

Let's change the engine of our sports car because it is damaged and see if the change reflects back to our SUV example:

sports_car.engine = 500
print("Sports car engine: {:d} vs SUV engine: {:d}".
      format(sports_car.engine, suv.engine))



Output:

Sports car engine: 500 vs SUV engine 1000

Nope. The changes are independent, as it should be. That's a characteristic of instance attributes. They're exclusive of each instance created.

There's a more "pythonic" way to structure the class to then create an instance of that class. And that is to use the decorator @property for retrieving the attribute's info, @property_name.setter for storing (or changing) the attribute's value and @property_name.deleter for deleting the attribute's value. This way we have more control of the attribute itself (we can make it private so no one can modify it directly and cause an accident, if there was no setter implemented, that is), but the end result is the same.

class Car:
    def __init__(self, engine, gear_shifter, seats):
        self.engine = engine
        self.gear_shifter = gear_shifter
        self.seats = seats

    @property
    def engine(self):
        return self.__engine


    @engine.setter
    def engine(self, value):
        self.__engine = value


    @property
    def gear_shifter(self):
        return self.__gear_shifter


    @gear_shifter.setter
    def gear_shifter(self, value):
        self.__gear_shifter = value


    @property
    def seats(self):
        return self.__seats


    @seats.setter
    def seats(self, value):
        self.__seats = value


suv = Car(1000, "automatic", 5)
print("SUV engine {:d}CC, {} and with {:d} seats".
      format(suv.engine, suv.gear_shifter, suv.seats))

suv.engine = 100
print("SUV engine {:d}CC, {} and with {:d} seats".
      format(suv.engine, suv.gear_shifter, suv.seats))



Output:

SUV engine 1000CC, automatic and with 5 seats
SUV engine 100CC, automatic and with 5 seats

Class attributes

Now let's talk about class attributes. These are attributes that are shared commonly between all objects of the class. How does that work? Let's put then the wheels attribute but as a class attribute instead of an instance attribute and you'll see what I mean:

class Car:
    wheels = 4
    def __init__(self, engine, gear_shifter, seats):
        self.engine = engine
        self.gear_shifter = gear_shifter
        self.seats = seats
    [...]


suv = Car(1000, "automatic", 5)
print("SUV engine {:d}CC, {} and with {:d} seats and {:d} wheels".
      format(suv.engine, suv.gear_shifter, suv.seats, suv.wheels))

sports_car = Car(3000, "automatic", 1)
print("Sports car engine {:d}CC, {} and with {:d} seats and {:d} wheels".
            format(sports_car.engine, sports_car.gear_shifter,
                   sports_car.seats, sports_car.wheels))



Output:

SUV engine 1000CC, automatic and with 5 seats and 4 wheels
Sports car engine 3000CC, automatic and with 1 seats and 4 wheels

Ok, nothing new, we knew that. All cars have four wheels. But what happens now that I decide to put ALL cars as three-wheelers instead of the more common four-wheeled counterparts? Let's see.

class Car:
    wheels = 3
    [...]


suv = Car(1000, "automatic", 5)
print("SUV engine {:d}CC, {} and with {:d} seats and {:d} wheels".
      format(suv.engine, suv.gear_shifter, suv.seats, suv.wheels))

sports_car = Car(3000, "automatic", 1)
print("Sports car engine {:d}CC, {} and with {:d} seats and {:d} wheels".
      format(sports_car.engine, sports_car.gear_shifter,
             sports_car.seats, sports_car.wheels))



Output:

SUV engine 1000CC, automatic and with 5 seats and 3 wheels
Sports car engine 3000CC, automatic and with 1 seats and 3 wheels

See what I mean? The value changed for ALL the cars I've created. That could be helpful if I decided to create some attribute with treats that are common for all of my objects.

We can change the value of the "wheels" class attribute directly without going to the class and changing it there. Look:

class Car:
    wheels = 3
    [...]


suv = Car(1000, "automatic", 5)
print("SUV engine {:d}CC, {} and with {:d} seats and {:d} wheels".
      format(suv.engine, suv.gear_shifter, suv.seats, suv.wheels))

sports_car = Car(3000, "automatic", 1)
print("Sports car engine {:d}CC, {} and with {:d} seats and {:d} wheels".
            format(sports_car.engine, sports_car.gear_shifter,
                   sports_car.seats, sports_car.wheels))

Car.wheels = 4

print("SUV engine {:d}CC, {} and with {:d} seats and {:d} wheels".
      format(suv.engine, suv.gear_shifter, suv.seats, suv.wheels))
print("Sports car engine {:d}CC, {} and with {:d} seats and {:d} wheels".
            format(sports_car.engine, sports_car.gear_shifter,
                   sports_car.seats, sports_car.wheels))



Output:

SUV engine 1000CC, automatic and with 5 seats and 3 wheels
Sports car engine 3000CC, automatic and with 1 seats and 3 wheels
SUV engine 1000CC, automatic and with 5 seats and 4 wheels
Sports car engine 3000CC, automatic and with 1 seats and 4 wheels

That is pretty useful then when we have to manipulate an attribute common to all objects, like this example. The catch is, it is global. It is automatically assigned to all instances. If you want to change a particular car to a three wheeler, you can do that but then there's a new attribute assigned to our three wheeler. How do I know that? Well, let's introduce our next topic: __dict__.

__dict__

First, our example. Let's take our original SUV and our sports car and after that let's change our SUV's wheel count to 3 to see what happens:

Car.wheels = 4
suv = Car(1000, "automatic", 5)
sports_car = Car(3000, "automatic", 1)

print("SUV engine {:d}CC, {} and with {:d} seats and {:d} wheels".
      format(suv.engine, suv.gear_shifter, suv.seats, suv.wheels))
print("Sports car engine {:d}CC, {} and with {:d} seats and {:d} wheels".
      format(sports_car.engine, sports_car.gear_shifter,
             sports_car.seats, sports_car.wheels))



Output:

SUV engine 1000CC, automatic and with 5 seats and 4 wheels
Sports car engine 3000CC, automatic and with 1 seats and 4 wheels


suv.wheels = 3

print("SUV engine {:d}CC, {} and with {:d} seats and {:d} wheels".
      format(suv.engine, suv.gear_shifter, suv.seats, suv.wheels))
print("Sports car engine {:d}CC, {} and with {:d} seats and {:d} wheels".
      format(sports_car.engine, sports_car.gear_shifter,
             sports_car.seats, sports_car.wheels))

print(Car.__dict__)
print(suv.__dict__)
print(sports_car.__dict__)


Output:

SUV engine 1000CC, automatic and with 5 seats and 3 wheels
Sports car engine 3000CC, automatic and with 1 seats and 4 wheels

{'__module__': '__main__', '__doc__': None, '__dict__': <attribute
'__dict__' of 'Car' objects>, 'gear_shifter': <property object at
0x7f77950c0458>, 'seats': <property object at 0x7f77950c04a8>,
'__weakref__': <attribute '__weakref__' of 'Car' objects>, 'engine':
<property object at 0x7f77950c0048>, '__init__':
<function Car.__init__ at 0x7f7795161b70>, 'wheels': 4}

{'_Car__engine': 1000, '_Car__seats': 5, 'wheels': 3,
'_Car__gear_shifter': 'automatic'}

{'_Car__engine': 3000, '_Car__seats': 1,
'_Car__gear_shifter': 'automatic'}

What are those cryptic things between these {} characters? Well, that's a dictionary. Is a data structure and an object in Python (by the way, everything in Python is an object, but I'll write about that in the next blog post) and what it does is it stores a value given a key. The structure is 'key': value. The last two are a dictionary representation of the object's attributes and the first one is a dictionary representation of the class' attributes and methods.

If you pay close attention there's a key difference between the SUV's __dict__ and the sports car __dict__, and that is, because we assigned a "wheels" attribute with a value of 3 to the SUV instance, you can see that it is reflected as a dictionary entry with key 'wheels' and a value of 3 in it, instead of the sports car __dict__ with no 'wheels' entry. This means that it effectively created an instance attribute with that unique value to that instance. The evidence of that is that the class' __dict__ representation has also a 'wheels' attribute but with the value of 4 reflected when you print the sports_car.wheels attribute and it prints a 4, meaning that, in fact, it is a class attribute.

And oh, do you see that _Car_attribute_name there? That in Python is called name mangling, meaning that Python internally changes the name of the attribute to prevent accidental changes if there's no accessor, rendering that attribute private (only accessed within the class).

Wrapping up

To finish this blog post, let's talk about differences, and advantages and disadvantages of each attribute type (instance vs class ones).

Instance attributes:

  • Defined inside a constructor (__init__) using the self parameter.
  • Specific to object.
  • Accessed using object dot notation. In the case of an suv instance of the Car class, suv.engine.
  • Changing value of instance attribute will not be reflected to other objects.
  • Advantage: because instance attributes are localized, changes on them don't affect the class' attributes.
  • Disadvantage: instance attributes are discarded upon deletion of an instance. This way, if you want to keep track of some values of the instance after destroyed, this can be a major drawback. It all depends on design.

Class attributes:

  • Defined directly inside a class.
  • Shared across all objects.
  • Accessed using class name as well as using object with dot notation. In the case of our Car class: Car.wheels, or an suv instance of the class suv.wheels.
  • Changing value by using classname.class_attribute = value (like Car.wheels = 4) will be reflected to all the objects.
  • Advantage: because class attributes affect all instances, a declaration or change of an attribute are automatically reflected in all instances. For example, this could work to count the active instances of a class.
  • Disadvantage: if you need to change a class attribute and personalize each instance affected by the class attribute this approach doesn't work. Too cumbersome. Also, don't forget that you can't set a class atribute's value using the object.class_attribute notation. It will only create an instance attribute.

That's it. Thank you for reading this blog post. I hope it is useful in some way to you. If you liked it, consider sharing it and commenting. That would help me a lot! See you aroung.


要查看或添加评论,请登录

Alfredo Delgado Moreno的更多文章

  • My first postmortem!

    My first postmortem!

    Ok, so this is my first postmortem. And I'm writing this after a very interesting case of a server outage after…

  • google.com <ENTER>... And then?

    google.com <ENTER>... And then?

    Ever wondered what happens when you type in your web browser any address and then press enter? Well, there's the fun 23…

  • IoT - more acronyms??

    IoT - more acronyms??

    I know the pain. Almost every concept or technology in the Information Technology world (IT for short) has an acronym.

  • Recursion... the pain of every CS student (but it doesn't need to be)

    Recursion... the pain of every CS student (but it doesn't need to be)

    Hello everyone, and welcome to yet another blog post. This time, we'll take matters seriously and delve into recursion.

  • Python: Everything is an object

    Python: Everything is an object

    Hello everyone. And welcome to today's blog post where I'll be sharing my findings of Python with you.

  • Dynamic libraries!!!

    Dynamic libraries!!!

    Hello everyone and welcome to yet another blog post. Today I'll be talking about dynamic libraries.

  • Journey to the Center of the Shell - or what happens when you type ls -l *.c on your shell

    Journey to the Center of the Shell - or what happens when you type ls -l *.c on your shell

    “It is only when you suffer that you truly understand.” Jules Verne - Journey to the Center of the Earth (1867) As the…

  • What about negative numbers in binary?

    What about negative numbers in binary?

    Hello everyone, and welcome to another blog entry. This time I'll talk about the representation of negative numbers in…

  • Morse code as a macro?!?

    Morse code as a macro?!?

    Hey everyone! How are you doing? Today I ran into an interesting piece of code written in C looking like this: #define…

  • Static libraries!!!

    Static libraries!!!

    Full Metal Jacket jokes aside. This topic is pretty important for us developers, so bear with me while I'll explain it…

社区洞察

其他会员也浏览了