Python OOP Class Attributes & Methods Tutorial

In this tutorial we learn the difference between instance attributes and methods, and class attributes and methods.

We cover how to create class members, as well as magic methods, private members and name mangling.

Here's a table of contents of what you'll learn in this lesson:
(click on a link to skip to its section)

Let's jump right in.

What are class attributes & methods?

There is a difference between instance attributes and methods, and class attributes and methods. An instance attribute or method is unique to that particular instance of the class. A class attribute or method is unique to the class itself.

Class attributes and methods aren’t used that often, in most cases instance attributes and methods are used.

How to initialize a class attribute

A class attribute is initialized like a variable, we don’t use self. in front of it.

Example: How_to_do_the_thing
class Person:

	# class attribute
	_users_logged_in = 0

How to access a class attribute

To access a class attribute we use the class name and the class attribute with dot notation.

Syntax:
  ClassName.attribute_name
Example:
class Person:

	# class attribute
	_users_logged_in = 0

print(Person._users_logged_in)

We can use self.attribute when working with a class attribute inside a class, but it’s clearer to use the class name.

Example:
class Person:

	# class attribute
	_users_logged_in = 0

	def silly_method(self):
		if self._users_logged_in == Person._users_logged_in:
			print("Same, but accessed differently")

user_1 = Person()

print(user_1.silly_method())

A class attribute is the same for all instances of the class.

Example:
class Person:

	# class attribute
	_users_logged_in = 0

	def __init__(self, name):
		# instance attribute
		self.name = name
		Person._users_logged_in += 1

	def logout(self):
		Person._users_logged_in -= 1

user_1 = Person("John")
user_2 = Person("Jane")

print("Users online:", Person._users_logged_in)

user_2.logout()

print("Users online:", Person._users_logged_in)

In the example above our instance attribute, “self.name”, is different for each user object. Our class attribute, _users_logged_in, is the same for both instances.

If you have worked with other languages such as C#, the class attribute would be almost similar to a static.

Let’s look at another example. This time we have a class attribute called age.

Example:
class Person:

	# class attribute
	_age = 30

	def __init__(self, name):
		# instance attribute
		self.name = name

user_1 = Person("John")
user_2 = Person("Jane")

print("Name:", user_1.name, " | Age:", user_1._age)
print("Name:", user_2.name, " | Age:", user_2._age)

In the example above, both user objects have the same value for the class attribute, “_age”. The “_age” attribute in both user objects refer to the “_age” class attribute.

How to change the value of a class attribute

We can change the value of a class attribute per class instance. This means that if we change the value of a class attribute it will only be changed on that object.

Example:
class Person:

	# class attribute
	_age = 30

	def __init__(self, name):
		# instance attribute
		self.name = name

user_1 = Person("John")
user_2 = Person("Jane")

user_2._age = 28

print("Name:", user_1.name, " | Age:", user_1._age)
print("Name:", user_2.name, " | Age:", user_2._age)

In the example above we change the class attribute, “_age”, in the “user_2” object. What happens now is that “_age” is changed from a class attribute to an instance attribute, but only inside the “user_2” object.

In general, class attributes are used for situations where an attribute’s value must be available to all instances of a class, but isn’t changed outside of the class.

How to create a class method

We create a class method like a normal instance method. The exceptions are that we write the @classmethod decorator on the line before, and instead of self, we use the cls keyword as the first parameter.

Syntax:
@classmethod
def method_name(cls, parameters):
	# body
Example:
class Person:

	# class attribute
	_users_logged_in = 0

	# class method
	@classmethod
	def print_users(cls):
		print("Users online:", cls._users_logged_in)

	def __init__(self, name):
		# instance attribute
		self.name = name
		Person._users_logged_in += 1

	def logout(self):
		Person._users_logged_in -= 1

user_1 = Person("John")
user_2 = Person("Jane")

Person.print_users()

user_2.logout()

Person.print_users()

The example above is a not a representation of a real world use case for a class method. It is only to demonstrate the concept.

How to access a class method

To access a class method we use the class name and the method name with dot notation.

Example:
class Person:

	# class attribute
	_users_logged_in = 0

	# class method
	@classmethod
	def print_users(cls):
		print("Users online:", cls._users_logged_in)

# access class method
Person.print_users()
Class method use case

A perfect example of where a class method would be used instead of an instance method, is the dict.fromkeys() dictionary method.

Example:
x = dict.fromkeys(["first_name", "last_name", "age"], "unknown")

print(x)

The fromkeys() method generates a new class instance for us by calling from the dict class itself, instead of calling from the instance of the class.

So instead of x.fromkeys(), we call dict.fromkeys() directly.

Underscores in names. Dunder methods, private members, name mangling

In Python 3 we often see names with underscores or multiple underscores.

Example: multiple underscores
# _name
# __name
# __name__

A common question is about the significance of the underscores. When and where to use them, and why?

Let’s start with the dunder method name. The dunder method is a method like __init__() with two underscores on each side.

Dunder method names, two underscores on each side

We can write our own dunder methods. However, it is against convention.

Example: custom dunder method
class Person:
	def __hello__():
		# method body

The idea is that we only define dunder methods when we are referencing or overwriting something. The __init__() method is a perfect example of this.

Private member, single underscore prefix

In many other languages, like C#, an attribute or method may be set as private. This means that the attribute or method marked private cannot be used outside of the class.

The private modifier does not exist in Python, everything is public. Prefixing a name with a single underscore is a convention that indicates that the field, or method, is not supposed to be accessed outside of the class.

Example:
class Person:
	def __init__(self, name):
		self.name = name
		self._secret = 10

user_1 = Person("John")

print(user_1.name)
print(user_1._secret)

In the example above the “_secret” attribute prints to the terminal but conventionally should not be accessed outside of the class.

Following this convention, we would use it only inside the class.

Example:
class Person:
	def __init__(self, name):
		self.name = name
		self._secret = 10
		self._dom = 10

	def lucky_day(self):
		if self._secret == self._dom:
			print("It's your lucky day")

user_1 = Person("John")

print(user_1.name)
print(user_1.lucky_day())

Here we use “_secret” and “_dom” only inside the class.

Name mangling, the double underscore prefix

The double underscore as prefix is not conventional but technical. Python changes the name behind the scenes.

As an example, let’s try to access an attribute with a double underscore.

Example:
class Person:
	def __init__(self):
		self.__name = "John"

user_1 = Person()

print(user_1.name)

The example above will raise an AttributeError.

Output:
 AttributeError: 'Person' object has no attribute 'name'

That’s because Python changed the name of the attribute. To see what changed, let’s use dir on the object to print out the Person class’s members.

Example:
class Person:
	def __init__(self):
		self.__name = "John"

user_1 = Person()

print(dir(user_1))

The very first result is our name attribute. Python changed it to “_Person__name”.

Output:
 ['_Person__name', '__class__', '__delattr__', ...]

If we want to access the name attribute we have to write _Person__name.

Example:
class Person:
	def __init__(self):
		self.__name = "John"

user_1 = Person()

print(user_1._Person__name)

The purpose of name mangling is to make this method or attribute particular to this class. It’s useful with class inheritance to avoid conflicting names.

Name mangling will make more sense once we cover inheritance and overriding.

Summary: Points to remember

  • Class attributes and methods are unique to the class, whereas instance attributes and methods are unique to an object
  • A class attribute does not need the self. prefix
  • A class method must have the @classmethod decorator on the line before and uses cls as self
  • We can write our own dunder methods but it’s against convention
  • Python cannot have private members. Instead, we use an underscore as prefix to a member that should not be used outside of the class.
  • “Private” member naming is convention only
  • Name mangling let’s Python change the variable to be specific to its own class. Name mangling is useful in inheritance.