Python @property decorator tutorial

Why is it necessary to "install a door on the property"?

Exposing attributes directly in a class is like putting your house key outside the door - anyone can come in and change it at will, without any control at all.

# ❌ 危险的直接暴露属性
class Student:
    pass

s = Student()
s.score = 9999  # 满分750的高考,这分数太离谱了

This "streaking" design will allow illegal data (such as negative ages and oversized scores) to pollute your program at will, making it painful to troubleshoot.

The traditional solution is to use getter and setter methods to protect properties:

# ✅ 传统封装方式(但调用不优雅)
class Student:
    def __init__(self):
        self._score = 0   # 单下划线开头:这是一个“内部属性”

    def get_score(self):
        return self._score
    
    def set_score(self, value):
        if not isinstance(value, int):
            raise ValueError('分数必须是整数!')
        if not (0 <= value <= 100):
            raise ValueError('分数必须在0~100之间!')
        self._score = value

Safety is safety, but every time you read or write, you must writeobj.get_score()obj.set_score(85), I always feel awkward - we are obviously operating an "attribute", but we are forced to use method calls, which is not Pythonic at all.

Python provides@propertyDecorators make methods look like ordinary properties, retaining encapsulation checks and keeping the call simple and elegant.


The core magic of @property

@propertyThe core purpose of: turn the "read" method into a property access, and turn the "write" method into an assignment operation.

Standard writing structure

class 某个类:
    # 1. 读取属性——用 @property 装起来
    @property
    def 属性名(self):
        # 可以加任何访问逻辑(比如格式化、日志)
        return self._内部存储变量
    
    # 2. 修改属性——用 @属性名.setter 装起来
    @属性名.setter
    def 属性名(self, value):
        # 可以加验证、类型转换等
        self._内部存储变量 = value

Complete practice: student score classification

class Student:
    def __init__(self):
        self._score = 0

    @property
    def score(self):
        """读取分数"""
        return self._score

    @score.setter
    def score(self, value):
        """设置分数,并自动校验"""
        if not isinstance(value, int):
            raise ValueError('分数必须是整数!')
        if not (0 <= value <= 100):
            raise ValueError('分数必须在0~100之间!')
        self._score = value

It’s completely natural to call

s = Student()

s.score = 85       # ✅ 自动调用 @score.setter 里的验证逻辑
print(s.score)     # ✅ 自动调用 @property 里的读取逻辑
# 85

s.score = 9999     # ❌ 抛出 ValueError,非法值被完美拦截

Did you see that? On the surface we are operatingscoreBehind this "attribute", a whole set of security verification is quietly running.


Create a read-only "calculated property"

If you only define @property without defining the corresponding setter, then this property becomes a read-only property - it cannot be assigned a value. It is especially suitable for values ​​calculated in real time based on other attributes, such as age based on the year of birth and area based on width and height.

Example: The birth year can be changed, but the age is only readable

class Student:
    def __init__(self, birth_year):
        self._birth = birth_year

    # 可修改的出生年份
    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        if not isinstance(value, int) or value > 2024:
            raise ValueError('请输入合法的出生年份!')
        self._birth = value

    # 只读的计算属性——年龄
    @property
    def age(self):
        return 2024 - self._birth   # 当前年份减出生年份

Test it

s = Student(2000)
print(s.birth)  # 2000
print(s.age)    # 24

s.birth = 2005  # ✅ 修改出生年份
print(s.age)    # 19 ——自动跟着变!

s.age = 20      # ❌ 报错:AttributeError: can't set attribute

In this way, we can ensureagealways bybirthIt is accurately calculated and it is impossible for outsiders to tamper with the age value at will.


Some of the most common pitfalls for novices

1. The attribute name is the same as the internal variable name → infinite recursion

if you put@propertyIf the decorated method name is set to the same variable name as the internal variable name that actually stores data, a terrifying infinite loop will occur.

# ❌ 致命的无限递归
class Student:
    @property
    def birth(self):
        return self.birth   # 又去读 self.birth,又触发这个 property……

Correct practice: Use single underscore prefix for internal storage variables (such as_birth), this is a convention of the Python community, which means "this variable is for internal use and should not be touched by outsiders."

2. Forgot to initialize internal variables → report an error

use@propertyBefore, it must be__init__In the etc. method, put the corresponding internal variable (_xxx) is created, otherwise the attribute will not be found when accessing.

# ❌ 忘记创建 _score
class Student:
    @property
    def score(self):
        return self._score

s = Student()
print(s.score) # AttributeError: 'Student' object has no attribute '_score'

Practical example: screen resolution class

Combine readable and writable width and height with read-only resolution to write a practicalScreenkind.

class Screen:
    def __init__(self, width=1920, height=1080):
        self._width = width
        self._height = height

    # 宽度属性(合法值必须是正数)
    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        if not isinstance(value, (int, float)) or value <= 0:
            raise ValueError('宽度必须是大于0的数字!')
        self._width = value

    # 高度属性(同理)
    @property
    def height(self):
        return self._height

    @height.setter
    def height(self, value):
        if not isinstance(value, (int, float)) or value <= 0:
            raise ValueError('高度必须是大于0的数字!')
        self._height = value

    # 只读的分辨率(总像素数)
    @property
    def resolution(self):
        return self._width * self._height

Test effect

screen = Screen()                           # 默认1080p
print(f"默认分辨率: {screen.resolution}")    # 2073600

screen.width = 3840
screen.height = 2160
print(f"4K分辨率: {screen.resolution}")     # 8294400

screen.width = -1024   # ❌ 抛出 ValueError,完美拦截

When to use it and when to boldly run naked

  • Scenarios where @property is recommended
  1. Verification or conversion is required when assigning values ​​(such as age ≥ 0, the string cannot be empty)
  2. Need to dynamically calculate read-only values (area, total price, age)
  3. Later in the project, I want to add control to the properties that were originally directly exposed, but I don’t want to change the external calling code.
  • Scenarios where normal attributes can be used directly No additional logic is required, just storing and retrieving data. Python encourages "we are all trustworthy" and can be exposed directly, making the code simpler.

Summary

@propertyDecorators find the best balance between "naked" and "complicated": they allow you to operate ordinary properties and enjoy complete encapsulation protection. Learn it, and your Python class interface will become safe and elegant, and you will look like an experienced driver at first glance~