持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第17天,点击查看活动详情
封装和信息隐藏
要我们和学生打交道,如果不通过上课和取得成绩来让他们受苦,那将是一种耻辱。
图 10-6 包含一个可用于跟踪一组学生的成绩的类。班级成绩的实例是使用列表和字典实现的。该列表跟踪班上的学生。字典将学生的标识号映射到成绩列表。
班级成绩(对象):
请注意,get_grades返回与学生关联的成绩列表的副本,get_students返回学生列表的副本。复制列表的计算成本可能具有通过简单地返回实例变量本身来避免。但是,这样做可能会导致问题。考虑代码课程 = 成绩()
course.add_student(格雷德)all_students=course.get_students()all_students附加(格雷德)
如果get_students返回self._students,则最后一行代码将产生(可能是意外的)副作用,即更改课程中的学生集。
实例变量 _is_sorted 用于跟踪自上次添加学生以来是否已对学生列表进行排序。这允许实现get_students以避免对已排序的列表进行排序。
图 10-7 包含一个函数,该函数使用班级成绩为参加名为 six_hundred 的课程的某些学生生成成绩报告。
生成成绩报告 运行时,打印图中的代码
面向对象编程的核心有两个重要的概念。首先是封装的概念。通过这种方式,我们表示将数据属性和操作方法捆绑在一起。例如,如果我们写
我们可以使用点表示法来访问诸如拉斐尔的姓名和标识号之类的属性。
第二个重要概念是信息隐藏。这是模块化的关键之一。如果程序中那些使用类的部分(即类的客户端)仅依赖于类中方法的规范,则实现该类的程序员可以自由地更改类的实现(例如,提高效率),而不必担心更改会破坏使用该类的代码。
一些编程语言(例如 Java 和 C++)提供了强制隐藏信息的机制。程序员可以将类的属性设为私有,以便类的客户端只能通过对象的 nethod 访问数据。Python 3 使用命名约定使属性在类之外不可见。当属性的名称以with__(双下划线)开头但不with_结尾时,该属性在类外不可见。请考虑图 10-8 中的类。
当我们运行代码时
它打印
然后引发异常
代码
打印:
然后提出异常
和代码
打印:
请注意,当子类尝试使用其超类的隐藏属性时,会发生属性错误。这可能会使使用__a隐藏信息变得麻烦。
因为它可能很麻烦,所以许多Python程序员没有利用__mechanism来隐藏属性 - 正如我们在本书中没有的那样。因此,例如,Person 的客户端可以编写表达式 Rafael。last_name而不是Rafael.get_ last_name()。我们通过在属性的开头放置一个_at来阻止这种不良行为,以指示我们希望客户端不直接访问它。
我们避免直接访问数据属性,因为客户端代码依赖于不属于规范的内容是危险的,因此可能会发生变化。如果更改了 Person 的实现,例如,在每次请求时提取姓氏,而不是将其存储在实例变量中,则直接accessed_last_name的客户端代码将不再有效。
Python不仅允许程序从类定义外部读取实例和类变量,还允许程序编写这些变量。例如,代码拉斐尔。生日=“8/21/50”是完全合法的。这将导致运行时类型错误,Rafael.get_age稍后在计算中调用。甚至可以从类定义外部创建实例变量。例如,如果赋值语句 me.age =Rafael.get_id_num(),Python 不会抱怨
发生在类定义之外。
虽然这种相对较弱的静态语义检查是Python中的一个缺陷,但它不是一个致命的缺陷。一个有纪律的程序员可以简单地遵循明智的规则,即不要从定义数据的类之外直接访问数据属性,就像我们在本书中所做的那样。