当前位置: 首页 > news >正文

Python 面向对象实战:私有属性与公有属性的最佳实践——用线段类举例

在这里插入图片描述

描述

在绘图软件、GIS、CAD 或简单的图形编辑器中,线段(Segment)是非常基础的对象。每个线段有两个端点(x1,y1)和(x2,y2)。在实现时我们通常希望:

  • 封装端点数据(防止外部随意改写造成不一致),比如修改端点后需要自动更新某些内部缓存或做验证(不能产生零长度线段等)。
  • 统计创建了多少线段(类层面的统计),但又不想让外部随意改这个计数(增加/减小计数会破坏统计)。
  • 允许外部读取一些信息(比如用于 UI 显示的公开计数,或线段的标签),同时对写操作做控制(通过方法或属性 setter 做验证)。

Python 中通过“以两个下划线开头但不以两个下划线结尾”的名字,会触发名称改写(name mangling),能够在一定程度上把属性“隐藏”到类作用域下(并非绝对私有,但能避免偶然覆盖/访问)。本文以 Segment 类为例实现上述需求,并演示私有/公有属性的典型用法与注意点。

题解答案(完整可运行代码)

# segment_example.py
import mathclass Segment:"""表示二维平面上的一条线段(端点私有,部分类属性私有/公有示例)"""# 私有类属性:名称以两个下划线开头(但不以两个下划线结尾)__secret_count = 0# 公有类属性:外部可以直接读写(但请谨慎写)public_count = 0def __init__(self, x1=0, y1=0, x2=0, y2=0, label=None):# 私有实例属性(用双下划线名字,会被 name-mangle 成 _Segment__x1 等)self.__x1 = float(x1)self.__y1 = float(y1)self.__x2 = float(x2)self.__y2 = float(y2)# 公有实例属性(习惯上可被外部直接访问)self.label = label# 每创建一个实例就更新统计(通过类名访问私有类属性)Segment.__secret_count += 1Segment.public_count += 1# 验证:不允许零长度的线段(举例业务规则)if self.length() == 0:raise ValueError("不允许零长度线段:两个端点不能相同")# ---------- 公有方法:访问/修改私有数据 -----------def set_points(self, x1, y1, x2, y2):"""设置端点(会做基本验证)"""x1, y1, x2, y2 = float(x1), float(y1), float(x2), float(y2)if x1 == x2 and y1 == y2:raise ValueError("不允许把两个端点设置为相同坐标(零长度)")self.__x1, self.__y1, self.__x2, self.__y2 = x1, y1, x2, y2def get_points(self):"""返回端点坐标的元组(只读视图)"""return (self.__x1, self.__y1, self.__x2, self.__y2)def length(self):"""返回线段长度(Euclidean distance)"""dx = self.__x2 - self.__x1dy = self.__y2 - self.__y1return math.hypot(dx, dy)def midpoint(self):"""返回线段中点坐标"""return ((self.__x1 + self.__x2) / 2.0, (self.__y1 + self.__y2) / 2.0)def translate(self, dx, dy):"""平移线段(原地修改)"""self.__x1 += dxself.__y1 += dyself.__x2 += dxself.__y2 += dy# ---------- 类方法 / 静态方法 -----------@classmethoddef get_public_count(cls):"""返回公有计数(等价于直接访问 cls.public_count)"""return cls.public_count@classmethoddef get_secret_count(cls):"""返回私有计数(提供受控访问)"""return cls.__secret_countdef __repr__(self):p = self.get_points()return f"Segment(({p[0]}, {p[1]}), ({p[2]}, {p[3]}), label={self.label!r})"

题解代码分析

下面逐个解释代码重点,帮助你真正理解私有与公有属性的选择和用法。

私有类属性 __secret_count

__secret_count = 0
  • 这是类级别的属性。因为以两个下划线开头,它会被 Python 改名(name-mangling)为 _Segment__secret_count(内部实现),从而在外部直接用 Segment.__secret_count 访问会报错。
  • 设计初衷是:统计创建的实例数,但不希望外部随意改写这个统计值(虽然可以通过 name-mangling 强行访问)。为了安全、规范,类里同时提供了公有的 public_count(可被 UI 展示)和受控的 get_secret_count() 来读取私有值。

公有类属性 public_count

public_count = 0
  • 这是公开的类属性,外部可直接访问 Segment.public_count。我们把它作为“展示用”的计数:即使外部能改它,设计上是允许展示、轻量读写,但关键逻辑仍然被私有计数保护。

私有实例属性 __x1, __y1, __x2, __y2

self.__x1 = float(x1)
...
  • 这些属性存储端点坐标。以双下划线开头,它们在类外不会以原名出现,会被改写成 _Segment__x1 等。
  • 这样可以降低外部代码不小心直接赋值造成状态不一致的可能性(例如直接把 s.__x1 改成字符串)。如果真的需要外部控制坐标,应该通过 set_points() 或者用 @property/setter 做合法性检查。

构造函数里的验证

if self.length() == 0:raise ValueError("不允许零长度线段:两个端点不能相同")
  • 这是业务规则示例:不允许零长度线段。封装私有数据的好处在此体现:我们能在构造/设置时统一做验证,保证类状态始终合法。

访问与修改接口

  • get_points():提供只读视图,返回端点元组;
  • set_points():提供受控修改,内部做验证;
  • length() / midpoint() / translate():这些都是典型的对象行为,直接在私有字段上操作,不暴露实现细节。

类方法 get_secret_countget_public_count

  • 即便有私有类属性,我们仍然提供受控的读取接口,既能保护数据,又能让调用者获得需要的信息。

名称改写(name mangling)说明

  • 私有属性并不是绝对隐藏。实际上,属性名 __x1 在类定义内部会被解释器改写成 _Segment__x1。你可以从外部通过 instance._Segment__x1 访问,但这不被推荐,仅用于调试或特殊场景。
  • 规则回顾:如果名字以两个下划线开头不以两个下划线结尾(即不是 dunder 方法),就会触发 name mangling。像 __init__ 不会被“私有化”,因为它以两个下划线开头但也以两个下划线结尾(这是魔法方法/特殊方法)。

示例测试及结果

下面给出一系列示例用法,并展示运行结果(假定把上面类存为 segment_example.py 或直接在 REPL 执行)。

# 示例 1:创建一个正常线段
s = Segment(0, 0, 3, 4, label="A-B")
print(s)                       # 调用 __repr__
print("端点:", s.get_points())
print("长度:", s.length())
print("中点:", s.midpoint())
print("类的公有计数:", Segment.public_count)
print("通过类方法看公有计数:", Segment.get_public_count())
print("通过类方法看私有计数:", Segment.get_secret_count())# 示例 2:平移后验证
s.translate(1, 1)
print("平移后端点:", s.get_points())
print("平移后长度(应保持不变):", s.length())# 示例 3:尝试创建零长度线段(应抛异常)
try:bad = Segment(0, 0, 0, 0)
except ValueError as e:print("创建零长度线段失败:", e)# 示例 4:演示不推荐但可行的私有属性访问(name-mangling)
print("私有属性(name-mangle) x1:", s._Segment__x1)
print("私有类计数(name-mangle):", Segment._Segment__secret_count)

预期输出(示例)

Segment((0.0, 0.0), (3.0, 4.0), label='A-B')
端点: (0.0, 0.0, 3.0, 4.0)
长度: 5.0
中点: (1.5, 2.0)
类的公有计数: 1
通过类方法看公有计数: 1
通过类方法看私有计数: 1
平移后端点: (1.0, 1.0, 4.0, 5.0)
平移后长度(应保持不变): 5.0
创建零长度线段失败: 不允许零长度线段:两个端点不能相同
私有属性(name-mangle) x1: 1.0
私有类计数(name-mangle): 1

注意:最后两个打印演示的是“可以通过 name-mangle 访问私有数据,但这属于越过封装的做法,不建议在正常业务逻辑中使用”。

时间复杂度

Segment 中常用操作的时间复杂度分析(按单次调用计):

  • __init__:O(1) —— 创建实例、赋值、做一次长度计算(常数时间)。
  • get_points():O(1) —— 返回 4 元素元组。
  • set_points():O(1) —— 验证并赋值(常数时间)。
  • length():O(1) —— 常数次算术运算和 math.hypot
  • midpoint():O(1) —— 常数时间。
  • translate(dx, dy):O(1) —— 常数次赋值。

总体上,这个类的基本操作都是 O(1)。如果你的应用需要对大量线段做批量操作(比如 N 条线段做碰撞检测),那整体复杂度会依据具体算法提升(例如 O(N^2) 的暴力检测等),但这超出当前类设计范畴。

空间复杂度

单个 Segment 实例占用常数空间:保存 4 个浮点数、少量额外元数据与一个 label 引用(如果提供)。因此单个对象的空间复杂度是 O(1)。

若有 NSegment 对象,总空间大致为 O(N)。

总结

  • 使用双下划线前缀(例如 __x1)可以触发 Python 的 name-mangling,从而把属性名“隐藏”在类的内部,减少外部无意的覆盖和误用,但并非绝对不可访问。私有属性用于实现数据封装、保证内部一致性与提供受控访问。
  • 公有属性(例如 public_countlabel)适合用于需要频繁读取、用于 UI 展示或允许用户直接定制的内容,但一旦允许写入,就需要在设计上容忍或校验它的变更。
  • 在实际场景(绘图工具、几何计算、地图标注等)中,把核心数据设为私有、对外提供受控方法是一个良好的工程实践。这样能把内部实现与外部接口解耦,便于以后修改实现(例如改用向量缓存、懒计算等)而不会影响外部代码。
  • 如果需要严格不可变的属性,可以在外层加封装(例如只读 property、或使用 @dataclass(frozen=True) 的变体),但那又是另一种设计取舍。

文章转载自:

http://kyJ0nZQz.ndtkt.cn
http://5MrN5RBP.ndtkt.cn
http://WKYgNRqu.ndtkt.cn
http://BL34Uq3S.ndtkt.cn
http://NoQ5wd3n.ndtkt.cn
http://44XTyqZp.ndtkt.cn
http://yYJ0HGSh.ndtkt.cn
http://KfvgdBrh.ndtkt.cn
http://mv79e21y.ndtkt.cn
http://LlIRNyzx.ndtkt.cn
http://Z3v1K5nR.ndtkt.cn
http://hefIciRy.ndtkt.cn
http://CVIYQL9Q.ndtkt.cn
http://WZ86GpO1.ndtkt.cn
http://k2knA17V.ndtkt.cn
http://Utf1WZ4X.ndtkt.cn
http://zUpBlLhe.ndtkt.cn
http://CMr3QJ6L.ndtkt.cn
http://m7vjW2HI.ndtkt.cn
http://TNQDQt6q.ndtkt.cn
http://h3Ph0XNK.ndtkt.cn
http://UCa1XBQ2.ndtkt.cn
http://jlLExVn6.ndtkt.cn
http://RHElWjaf.ndtkt.cn
http://DRqHCIH9.ndtkt.cn
http://ICjso2CA.ndtkt.cn
http://vnpv9zHJ.ndtkt.cn
http://hKnIvmrK.ndtkt.cn
http://sMgZ2EVT.ndtkt.cn
http://NW4pOm2I.ndtkt.cn
http://www.dtcms.com/a/379434.html

相关文章:

  • 使用deboor法计算三次B样条曲线在参数为u处的位置的方法介绍
  • 认识HertzBeat的第一天
  • AUTOSAR进阶图解==>AUTOSAR_EXP_ApplicationLevelErrorHandling
  • 线程同步:条件变量实战指南
  • OpenLayers数据源集成 -- 章节七:高德地图集成详解
  • AI助推下半年旺季,阿里国际站9月采购节超预期爆发
  • 电商平台拍立淘API接口调用全解析(基于淘宝/唯品会技术实践)
  • 9.11 Qt
  • 字节一面 面经(补充版)
  • 第二章 ELK安装部署与环境配置
  • I2C 总线
  • 设计模式——七大常见设计原则
  • 请创建一个视觉精美、交互流畅的进阶版贪吃蛇游戏
  • 利用美团龙猫添加xlsx的sheet.xml读取sharedStrings.xml中共享字符串输出到csv功能
  • 时序数据库:定义与基本特点
  • 【WorkManager】Android 后台任务调度的核心组件指南
  • python项目批量安装包和生成requirements.txt文件
  • 零部件力学测试系统参数
  • 3D Web轻量引擎HOOPS赋能BIM/工程施工:实现超大模型的轻量化加载与高效浏览!
  • Java Web应用的安全性与防护措施!
  • 填写简历信息
  • 优先算法——专题十一:字符串
  • [Spring Cloud][3]从零开始简单工程搭建实践详解,远程调用
  • 为什么要显示调用析构函数
  • MySQL 数据完整性与约束:从基础到实战,守护数据准确性
  • Python中的“占位符”艺术:深入理解pass关键字的妙用
  • 构建企业级Python离线包仓库:从下载到服务部署全流程指南
  • C++面向对象之多态
  • 个人自留笔记——git操作
  • 命令模式,餐厅订单管理系统C++