I Don’t Like Magic • Exploring The Class Attributes That Aren’t Really Class Attributes • [Club]
This syntax, used for data classes and typing.NamedTuple, confused me when first learning about these topics. Here’s why, and why it’s no longer confusing.
I don’t like magic. I don’t mean the magic of the Harry Potter kind—that one I’d like if only I could have it. It’s the “magic” that happens behind the scenes when a programming language like Python does things out of sight. You’ll often find things you have to “just learn” along the Python learning journey. “That’s the way things are,” you’re told.
That’s the kind of magic I don’t like. I want to know how things work. So let me take you back to when I first learnt about named tuples—the NamedTuple in the typing module, not the other one—and data classes. They share a similar syntax, and it’s this shared syntax that confused me at first. I found these topics harder to understand because of this.
Their syntax is different from other stuff I had learnt up to that point. And I could not reconcile it with the stuff I knew. That bothered me. It also made me doubt the stuff I already knew. Here’s what I mean. Let’s look at a standard class first:
class Person:
classification = “Human”
def __init__(self, name, age, profession):
self.name = name
self.age = age
self.profession = professionYou define a class attribute, .classification, inside the class block, but outside any of the special methods. All instances will share this class attribute. Then you define the .__init__() special method and create three instance attributes: .name, .age, and .profession. Each instance will have its own versions of these instance attributes. If you’re not familiar with class attributes and instance attributes, you can read my seven-part series on object-oriented programming: A Magical Tour Through Object-Oriented Programming in Python • Hogwarts School of Codecraft and Algorithmancy
Now, let’s assume you don’t actually need the class attribute and that this class will only store data. It won’t have any additional methods. You decide to use a data class instead:
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
profession: strOr you prefer to use a named tuple, and you reach out for typing.NamedTuple:
from typing import NamedTuple
class Person(NamedTuple):
name: str
age: int
profession: strThe syntax is similar. I’ll tell you why I used to find this confusing soon.
Whichever option you choose, you can create an instance using Person(”Matthew”, 30, “Python Programmer”). And each instance you create will have its own instance attributes .name, .age, and .profession.
But wait a minute! The data class and the named tuple use syntax that’s similar to creating class attributes. You define these just inside the class block and not in an .__init__() method. How come they create instance attributes? “That’s just how they work” is not good enough for me.
These aren’t class attributes. Not yet. There’s no value associated with these identifiers. Therefore, they can’t be class attributes, even though you write them where you’d normally add class attributes in a standard class. However, they can be class attributes if you include a default value:
@dataclass
class Person:
name: str
age: int
profession: str = “Python Programmer”The .profession attribute now has a string assigned to it. In a data class, this represents the default value. But if this weren’t a data class, you’d look at .profession and recognise it as a class attribute. But in a data class, it’s not a class attribute, it’s an instance attribute, as are .name and .age, which look like…what do they look like, really? They’re just type hints. Yes, type hints without any object assigned. Python type hints allow you to do this:
>>> first_name: strThis line is valid in Python. It does not create the variable name. You can confirm this:
>>> first_name
Traceback (most recent call last):
File “<input>”, line 1, in <module>
NameError: name ‘first_name’ is not definedAlthough you cannot just write first_name if the identifier doesn’t exist, you can use first_name: str. This creates an annotation which serves as the type hint. Third-party tools now know that when you create the variable first_name and assign it a value, it ought to be a string.
So, let’s go back to the latest version of the Person data class with the default value for one of the attributes:
@dataclass
class Person:
name: str
age: int
profession: str = “Python Programmer”But let’s ignore the @dataclass decorator for now. Indeed, let’s remove this decorator:
class Person:
name: str
age: int
profession: str = “Python Programmer”You define a class with one class attribute, .profession and three type hints:
name: strage: intprofession: str
How can we convert this information into instance attributes when creating an instance of the class? I won’t try to reverse engineer NamedTuple or data classes here. Instead, I’ll explore my own path to get a sense of what might be happening in those tools.
Let’s start hacking away…


