You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python类封装疑问:实例创建后如何禁止外部修改属性?

问题描述

我正以业余学习的方式参加Python类相关课程,当前学习主题为封装与类属性,正在理解getter和setter机制。我阅读了数十种不同的解释,自认为理解其工作原理,但无法让自己的简单代码按预期运行。

我的需求是:创建Book类实例后,禁止外部修改name和pages属性,例如执行stuff.pages = 50这类操作应失效,但在实例创建时可以设置这些属性。

我尝试通过添加双下划线的私有属性、配置@property和setter装饰器来实现,但遇到诸多问题:

  • 移除pages的setter后,实例创建时也无法设置pages;
  • 设置pages的@property返回None时,外部仍能访问item.pages;
  • 后续还遇到@property装饰的方法与自定义同名方法的冲突问题,例如name()可正常调用但pages()的情况存疑,对此完全困惑,寻求解决方法。

相关尝试代码:

class Book:
    def __init__(self, name: str, pages: int):
        self.__name = name
        self.__pages = pages
    

    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self, name):
        self.__name = name


    @property
    def pages(self):
        return None
        #return self.__pages NOTICE THIS

    @pages.setter
    def pages(self, pages):
        self.__pages = pages

    def __str__(self):
        return f'{self.name} ({self.__pages} kg)'


item = Book("BookXYZ", 420)
print(item.pages)

后续测试代码:

class Book:
    def __init__(self, name: str, pages: int):
        self.__name = name
        self.__pages = pages


    @property
    def name(self):
        return self.__name

    @property
    def pages(self):
        return self.__pages

    def name(self):
        return str(self.__name)

    def pages(self):
        return str(self.__pages)

    def __str__(self):
        return f'{self.name()} ({self.__pages} kg)' #name()!!!!

测试代码:

stuff = Book("AAA", 20)
stuff2 = Book("BBB", 199)

print("book name:", stuff.name())
print("book pages:", stuff.pages())

print("book name:", stuff)
print("book name2:", stuff2)
解决方法

核心思路

要实现初始化时可设置、实例创建后只读的属性,只需:

  1. 在__init__中直接赋值双下划线开头的私有属性(__name、__pages),这一步不受@property的限制;
  2. 为每个属性定义@property装饰的getter方法,但不定义对应的setter装饰器,这样外部只能读取属性,无法赋值修改。

正确实现代码

class Book:
    def __init__(self, name: str, pages: int):
        # 初始化时直接设置私有属性,不受@property限制
        self.__name = name
        self.__pages = pages

    # 仅定义name的getter,无setter → 只读
    @property
    def name(self):
        return self.__name

    # 仅定义pages的getter,无setter → 只读
    @property
    def pages(self):
        return self.__pages

    def __str__(self):
        return f'{self.name} ({self.pages} 页)'

验证测试

# 创建实例,初始化正常赋值
stuff = Book("Python入门", 300)
print(stuff.name)  # 输出: Python入门
print(stuff.pages) # 输出: 300
print(stuff)       # 输出: Python入门 (300 页)

# 尝试修改属性,会抛出AttributeError
try:
    stuff.name = "进阶Python"
except AttributeError as e:
    print(e)  # 输出: can't set attribute

try:
    stuff.pages = 500
except AttributeError as e:
    print(e)  # 输出: can't set attribute

你的代码问题解析

  1. 移除setter后初始化失败的误解:
    你以为移除setter会导致初始化无法设置属性,其实__init__中直接操作的是私有属性__name/__pages,和@property的setter完全无关,只要在__init__里赋值私有属性,初始化就正常。

  2. @property返回None仍能访问的问题:
    @property装饰的方法是属性的读取入口,你返回None,外部拿到的就是None,但私有属性__pages还是存在的(比如你__str__里用self.__pages能拿到正确值)。这只是返回值的问题,不是访问权限的问题。

  3. 同名方法冲突的问题:
    当你用@property装饰name方法后,这个方法会被转换为类的属性描述符,之后再定义同名的普通def name(self)方法,会直接覆盖之前的@property描述符。所以最后name变成了普通方法,而不是属性,这就是为什么你需要用stuff.name()调用,而不是stuff.name。同理pages也是一样的情况,这是类属性赋值覆盖导致的。

内容的提问来源于stack exchange,提问作者vinter5

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 19:54:54