Detailed explanation of Python class attributes and instance attributes

When they first learned Python's object-oriented approach, many students encountered a pitfall: a shared variable was clearly defined in a class, but after assigning a value to an instance, other instances remained motionless. After looking through the documents for a long time, I figured out that it turned out to be the fault of "same name coverage".

Behind this is actually the access priority of instance attributes and class attributes at work. Today’s article uses the shortest possible time to break down these two concepts and explain them clearly, and by the way, I will give you a pitfall avoidance guide.


1. Who are the attributes tied to?

In the object-oriented world of Python, properties are data hanging on an object. But there are two types of "objects" here:

  • Class Object: Executionclass Student:automatically created at that moment
  • Instance Object: PassedStudent()The specific students transferred out

Properties can be attached to these two objects, so there are the following pair of brothers:

ConceptBelonging
Instance propertiesBind to the instance, each plays his own role
Class attributesBind to the class itself, shared by the whole family

2. Instance attributes: everyone’s “private territory”

The biggest feature of instance attributes is that they do not interfere with each other. Changing the properties of one instance will never affect another instance.

2.1 Standard writing:self.xxxexist__init__inside

The most standardized approach is to__init__passed in the constructorselfBinding:

class Student:
    def __init__(self, name, age):
        self.name = name   # 名字归自己
        self.age = age     # 年龄归自己

Create two instances, completely independent of each other:

alice = Student("Alice", 18)
bob = Student("Bob", 19)

print(f"Alice: {alice.name} - {alice.age}")  # Alice: Alice - 18
print(f"Bob: {bob.name} - {bob.age}")        # Bob: Bob - 19

2.2 Dynamic addition is also possible, but be restrained

Python allows you to "stick" new attributes to instances anytime and anywhere:

alice.score = 95
print(alice.score)  # 95

# Bob 没有 score,会报错
# print(bob.score)  # AttributeError

Although this kind of dynamic attribute is flexible, it can easily mess up the structure when used in production code. It is recommended to only use it in production code.__init__All instance properties are clearly defined here.


3. Class attribute: "Public repository" for all instances

Class attributes are written directly at the top level of the class definition, no needself, any instance (including those that have not yet been created) can access it and points to the same memory by default.

3.1 Definition and Access

class Student:
    school = "XYZ国际学校"    # 类属性

    def __init__(self, name):
        self.name = name

All three access methods can get the same value:

alice = Student("Alice")
bob = Student("Bob")

print(Student.school)   # XYZ国际学校
print(alice.school)     # XYZ国际学校
print(bob.school)       # XYZ国际学校

3.2 When to use class attributes?

  • Storage class-level constants (such as school motto, default configuration)
  • Statistics (such as how many instances are currently created)
  • State shared by all instances

4. The key point of pitfall: the priority of coverage with the same name

The order in which Python looks for attributes is: Find the instance's own attributes first, and if you can't find them, go to the class. This one rule leads to countless confusing behaviors.

4.1 Demonstration of attributes with the same name

class Student:
    name = "匿名学生"

s = Student()
print(s.name)       # 匿名学生  (来自类属性)
print(Student.name) # 匿名学生

Add an instance with the same namename

s.name = "小明"
print(s.name)       # 小明      (来自实例属性,优先级更高)
print(Student.name) # 匿名学生  (类属性纹丝不动)

After deleting the instance attribute, access will fall back to the class attribute:

del s.name
print(s.name)       # 匿名学生

⚠️ The biggest trick here: use实例.属性名 = 值In the form of "assigning a value" to a class attribute, you are actually quietly creating an instance attribute with the same name, without changing the class layer at all.

4.2 To correctly modify class attributes, you must use the class name

If you really want to modify the values ​​shared by all instances, you must operate through the class name:

Student.school = "ABC双语学校"
print(alice.school)   # ABC双语学校
print(bob.school)     # ABC双语学校

5. Classic case: counting the number of instances using class attributes

class Student:
    total_students = 0   # 类级别的计数器

    def __init__(self, name):
        self.name = name
        Student.total_students += 1   # 必须用类名修改!

Test it out:

print(Student.total_students)  # 0

s1 = Student("张三")
print(Student.total_students)  # 1

s2 = Student("李四")
print(Student.total_students)  # 2

# 尽管实例也能访问到,但尽量一律用类名,更清晰
print(s1.total_students)       # 2

Note: If written asself.total_students += 1, it will become adding a private counter to the instance, which is completely useless.


6. Modern writing:@dataclassHow to deal with attributes?

Starting with Python 3.7,@dataclassDecorators can help us automatically generate__init____repr__and other methods. However, please note that fields with default values ​​here are instance attributes by default, and class attributes need to be defined separately.

from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int = 18            # 实例属性,带默认值

    # 类属性放在这里
    total_students: int = 0
    school: str = "XYZ国际学校"

    def __post_init__(self):
        Student.total_students += 1

test:

s1 = Student("王五")
print(s1)  # Student(name='王五', age=18)
print(Student.total_students)  # 1

7. See the difference clearly with one picture

Comparison itemsInstance attributesClass attributes
Define location__init__Or add instances dynamicallyClass definition top level
Storage locationRespective memory space of each instanceMemory space of class object
Standard access methods实例.属性类名.属性
Coverage priority with the same nameHighest (find yourself first)Lowest (find the instance only if it cannot be found)
Modify the scope of influenceOnly affects the current instanceAffects all instances (as long as they are not overwritten by the same name)
Typical usesInstance-specific data (name, age)Shared data (school, counters)

8. Three suggestions for avoiding pitfalls

  1. Try not to let instance attributes and class attributes have the same name - Unless you are really doing advanced operations such as "overwriting", it will only make the code more readable.
  2. When modifying class attributes, be sure to use the class name——实例.属性 = 新值Always add a new property with the same name to the instance.
  3. Be careful when dynamically adding instance attributes - it is best to__init__or@dataclassAll instance properties are explicitly declared here.

After reading this article, if you encounter attribute access problems in the future, first think about this table and the "same name coverage priority", and you can basically locate the cause in seconds 😄