Lesson 9 - Static class members in Python

In the previous lesson, Arena with a mage in Python (inheritance and polymorphism), we put our knowledge of inheritance and polymorphism to the test. In today's Python tutorial, we're going to go over static class members. Until now, we only used data, states, being carried by an instance. Attributes, which we've defined, belonged to an instance and were unique to each instance. OOP, however, allows us to declare attributes and methods on a class itself. We call these members static and they are independent of an instance.

Beware of static membersWARNING! Today's lesson will show you static members which actually violate the object-oriented principles. OOP includes them only for special cases and in general everything can be written without static members. We always have to think carefully before adding static members. Generally, I would recommend for you not to use static members ever, unless you're absolutely sure what you're doing. Like global variables, static members are something that enable you to write bad code and violate good practices. Today, we'll go over this topic just to make you understand. Use this knowledge wisely, there will be less evil in the world.

Static (class) attributes

We can declare various members as static, let's start with attributes. As I mentioned in the introduction, static members belong to the class, not to an instance. So the data stored there can be read even if an instance of the class has not been created. Basically, we could say that static attributes are shared among all class instances, but even that wouldn't be accurate since they aren't actually related to instances. Let's create a new project, name it something like static, and create a simple User class:

class User:

    def __init__(self, name, password):
        self.name = name
        self.password = password
        self.logged_in = False

    def log_in(self, entered_password):
        if self.password == entered_password:
            self.logged_in = True
            return True
        else:
            self.logged_in = False
            return False # wrong password

The class is simple, it represents a user in a system. Each user instance has its own name, password, and carries information about whether the user is logged in. In order for the user to log-in, we call the log_in() method which takes a password as a parameter. The method verifies whether it's the right password. If the person behind the keyboard is really that user, it logs him/her in. It returns True/False depending on whether the login was successful. In reality, the password would be also hashed, but we won't do any hashing this time around.

When a new user registers, the system tells him/her what the minimum length of their password must be. This number should be stored somewhere. During user registration, we still wouldn't have the user instance. The object has not been created and would only be created after the form has been completely filled and submitted. Of course, it'd be very useful to have the minimum password length stored in the User class somewhere since it, naturally, belongs there. We'll make this value a static attribute by declaring a minimal_password_length attribute directly in the class body:

class User:

    minimal_password_length = 6

    ...

Now, let's print the attribute to the console. We access this attribute directly on the class:

print(User.minimal_password_length)

We can see the attribute really belongs to the class. We can access it from different places in the code without having to create a user. However, we'd find it on the user instance as well:

u = User("Thomas White", "passwordissword")
print(u.minimal_password_length)

Another practical use of static attributes is to assign unique identification numbers to each user. If we didn't know what static members were, we'd have to check every user creation and increment a counter. Instead, we'll create a private static attribute next_id right on the User class. The next user who registers will have their id stored there. The first user's id will be 1, the second's 2, and so on. The User will get a new attribute - id, that will be set in the constructor depending on what the next_id value is:

class User:

    minimal_password_length = 6
    next_id = 1

    def __init__(self, name, password):
        self.name = name
        self.password = password
        self.logged_in = False
        self.id = User.next_id
        User.next_id += 1

    ...

The class stores the next instance's id by itself. We assign this id to a new instance in the constructor and increase it by 1, which prepares it for the next instance. Not only attributes can be static, this approach could be applied to a variety of class members.

Static methods

Static methods are called on the class. All they usually are is utilities that we need to use often and creating an instance every time would be counterproductive.

Let's make another example, just to clarify. During user registration, we need to know the minimum password length before we create the user instance. Also, it would be great if we could validate the password before the program creates the user. A full validation, in this case, would include checking whether the length is correct, making sure it doesn't contain accent characters, verifying that there is at least one number in it, and so on. To do this, we'll create a static validate_password() method:

@staticmethod
def validate_password(password):
    if len(password) >= User.minimal_password_length:
        return True
    else:
        return False

We'll call this method on the User class:

print(User.validate_password("passwordissword"))

Viewer beware! The validate_password() method belongs to the class. Meaning that we can't access any instance attributes in it. Those attributes don't exist in the class context, rather in the context of an instance. It wouldn't make any sense to use the user's name in our method! You can try it out if you'd like to get a visual confirmation of its impossibility.

In addition to static methods, Python also supports class methods. These methods receive the class as the first parameter. Class methods are useful when we inherit this class and want to have a different value of a class variable in the child. Otherwise, it's better to use a static method.

@classmethod
def validate_password(cls, password):
    if len(password) >= cls.__minimal_password_length:
        return True
    else:
        return False

The first parameter containing the class reference is conventionally named cls. Using this parameter, we access class variables, which is similar to self.

Password validation can be achieved without knowledge of static members. We could create a UserValidator class and write methods in it accordingly. Then, we'd have to create its instance to be able to call those methods. It'd be a bit confusing because the "concept of the user" would be unnecessarily split up into two classes. Now, thanks to static members, all of the information is neatly organized in one place.

Thinking back, we really don't want the minimal_password_length static attribute to be accessible from outside the class. What we'll do is make it private and create a static get method for reading it. We know this approach well from previous lessons. Let's add a get method for both the minimum password length and the instance's id. We'll also try these methods:

u = User("Thomas White", "passwordissword")
print("First user's ID: ", u.get_id())
v = User("Oli Pickle", "csfd1fg")
print("Second user's ID: ", v.get_id())
print("Minimum password length: ", User.get_minimal_password_length())
print("Password validation \"password\": ", User.validate_password("password"))
input()

The output will be:

Console application
First user's ID: 1
Second user's ID: 2
Minimum password length: 6
Password validation "password": True

In Python 3, we can also put "ordinary functions" into classes. For example, we can do:

class MyClass:

    def my_function():
        print("This function is in a class.")

    def another_function(text):
        print("This function is also in a class!")
        print("Text is:", text)

MyClass.my_function()
MyClass.another_function("parameter")

It looks similar to functions contained in a module.

Info to the attached code

I added the following lines to today's source code to keep things simple:

get_minimal_password_length = get_minimal_password_length_s
validate_password = validate_password_s

This binds new objects (functions) with the original functions. This will allow us to use the functions without additional use of letters to distinguish between static and class functions. You can try that both functions refer to the same object using the is operator.

Static members appear very often in design patterns, we've already mentioned them in our lessons. We'll get in further detail in the approaches that bring object-oriented programming to perfection later on. For now, this will do. I don't want to overwhelm you :) In the next lesson, Properties in Python, we'll look at what properties are in Python.


 

Download

Downloaded 0x (2.71 kB)
Application includes source codes in language Python

 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Previous article
Arena with a mage in Python (inheritance and polymorphism)
All articles in this section
Object-Oriented Programming in Python
Thumbnail
Next article
Properties in Python
Activities (2)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!