封装是 Python 中面向对象的基本原则之一。它可以保护您的类不被意外更改或删除,并促进代码的可复用性与可维护性。来看一个简单的类定义:
class Smartphone :
def __init__ ( self, brand, os) :
self. brand = brand
self. os = os
iphone = Smartphone( "Apple" , "iOS 17" )
技术支持
许多 Python 程序员都会这样定义类。然而,这与专业 Python 开发者所遵循的最佳实践相去甚远。这个类的问题在您尝试修改其数据时就显现出来了:
iphone. os = "Android"
print ( iphone. os)
技术支持
Android技术支持
想象一下,一台 iPhone 跑着 Android——太离谱了!显然,我们需要在类的内部设置一些边界,使用户不能随心所欲地更改其属性。
如果他们确实要更改,那么必须在我们的规则之下进行。但我们仍希望 .os 或 .brand 这些属性在表面上保持不变。
以上这些目标都可以通过封装来实现,在本教程中,我们将全面学习它。让我们开始吧!
Python 如何实现封装?
Python 倾向于通过约定与编程实践来支持封装,而非强制性的访问修饰符。您看,Python 很多代码设计背后的基本理念是“大家都是成年人”。我们只能将封装作为一种约定来实现,并期望其他 Python 开发者信任并尊重我们的代码。
在 Java、C++ 等其他 OOP 语言中,封装通过 public、private 或 protected 等访问修饰符被严格强制执行,但 Python 并没有这些:
特性 Python Java C++ 访问修饰符 无强制修饰符;使用命名约定(_protected、__private) 通过 public、protected、private 关键字强制执行 通过 public、protected、private 关键字强制执行 Getter/Setter 方法 可选,常与 @property 装饰器配合用于受控访问 常见做法,通常以方法形式实现 常见做法,通常以方法形式实现 数据访问 通过命名约定访问;依赖开发者自律 由访问修饰符控制;由编译器强制 由访问修饰符控制;由编译器强制 理念 “大家都是成年人”——依赖约定与信任 严格强制访问控制 严格强制访问控制
因此,我接下来将展示的封装技术大多(如果不是全部)都是 Python 的约定。若您愿意,它们很容易被打破。但我相信您会在自己的开发项目中尊重并遵循它们。
Python 中的访问修饰符
假设我们有这样一个简单的类:
class Tree :
def __init__ ( self, height) :
self. height = height
pine = Tree( 20 )
print ( pine. height)
技术支持
20技术支持
它只有一个可打印的 height 属性。问题在于我们也可以把它改成任意值:
pine. height = 50
pine. height
技术支持
50技术支持
pine. height = "Grandma"
pine. height
技术支持
'Grandma'技术支持
那么,如何告知用户禁止更改 height 呢?我们可以通过在前面加一个下划线把它变成受保护成员 :
class Tree :
def __init__ ( self, height) :
self. _height = height
pine = Tree( 20 )
pine. _height
技术支持
20技术支持
现在,了解此约定的人会知道他们只能访问该属性,而我们强烈不鼓励他们使用和修改它。但如果他们愿意,仍然可以修改,没错。
那我们如何也阻止这一点?用另一种约定——通过在前面加双下划线,将属性变成私有成员 :
class Tree :
def __init__ ( self, height) :
self. __height = height
pine = Tree( 20 )
pine. __height
技术支持
AttributeError: 'Tree' object has no attribute '__height'
技术支持
现在,如果有人尝试访问该属性,Python 会抛出错误,更别提修改它了。
但您注意到我们刚刚做了什么吗?我们把与对象相关的唯一信息对用户隐藏了。我们的类变得毫无用处,因为它没有任何公共属性。
那么,如何既向用户公开树高,又能控制其访问和修改方式?例如,我们希望树高在特定范围内,且只能是整数。如何强制执行呢?
这时,使用 Java 的朋友可能会建议使用getter 和 setter 方法 。那我们先试试:
class Tree :
def __init__ ( self, height) :
self. __height = height
def get_height ( self) :
return self. __height
def set_height ( self, new_height) :
if not isinstance ( new_height, int ) :
raise TypeError( "Tree height must be an integer" )
if 0 < new_height <= 40 :
self. __height = new_height
else :
raise ValueError( "Invalid height for a pine tree" )
pine = Tree( 20 )
pine. get_height( )
技术支持
20技术支持
这样,您创建了私有属性 __height,但允许用户通过 get_height 和 set_height 方法以受控方式访问和修改它。
pine. set_height( 25 )
pine. get_height( )
技术支持
25技术支持
在设置新值之前,set_height 会确保新高度在某个范围内且为数值。
pine. set_height( "Password" )
技术支持
TypeError: Tree height must be an integer
技术支持
但这些方法对于一个简单操作来说似乎有些小题大做。而且,像这样写代码也不够优雅:
pine. set_height( pine. get_height( ) + 5 )
技术支持
如果我们能这样写:
pine. height += 5 技术支持
同时仍能强制高度的数据类型与范围正确,不是更优雅、更易读吗?答案是肯定的,我们将在下一节学习如何做到这一点。
在 Python 类中使用 @property 装饰器
我们引入一种新技巧——为属性创建属性(properties) :
class Tree :
def __init__ ( self, height) :
self. __height = height
@property
def height ( self) :
return self. __height
pine = Tree( 17 )
pine. height
技术支持
17技术支持
我们希望用户能像访问普通属性 height 那样访问隐藏的 __height。为此,我们定义一个名为 height 的方法,返回 self.__height,并用 @property 装饰它。
现在,我们可以调用 height 并访问该私有属性:
pine. height技术支持
17技术支持
但最妙的是,用户无法修改它:
pine. height = 15 技术支持
AttributeError: can't set attribute ' height'技术支持
因此,我们再添加一个名为 height(self, new_height) 的方法,并用 height.setter 装饰器包装。在该方法内,我们实现强制高度数据类型与范围的逻辑:
class Tree :
def __init__ ( self, height) :
self. __height = height
@property
def height ( self) :
return self. __height
@height. setter
def height ( self, new_height) :
if not isinstance ( new_height, int ) :
raise TypeError( "Tree height must be an integer" )
if 0 < new_height <= 40 :
self. __height = new_height
else :
raise ValueError( "Invalid height for a pine tree" )
技术支持
现在,当用户尝试修改 height 属性时,会调用 @height.setter,从而确保传入的值正确:
pine = Tree( 10 )
pine. height = 33
pine. height = 45
技术支持
ValueError: Invalid height for a pine tree
技术支持
我们还可以通过 @height.getter 自定义通过点号访问 height 属性的方式:
class Tree :
def __init__ ( self, height) :
self. __height = height
@property
def height ( self) :
return self. __height
@height. getter
def height ( self) :
return f"This tree is { self. __height} meters"
pine = Tree( 33 )
pine. height
技术支持
'This tree is 33 meters' 技术支持
即使我们用整数高度创建了 pine,也可以通过 @height.getter 修改返回值的形式。
以上就是在 Python 类中促进封装的一些示例。请记住,封装仍然是一种约定,因为我们依然可以破坏内部的 __height 私有成员:
pine. _Tree__height = "Gotcha!"
pine. height
技术支持
'This tree is Gotcha! meters'技术支持
Python 类中的一切都是公开的,私有方法 也是如此。这并非设计缺陷,而是“大家都是成年人”理念的一种体现。
下面是我们刚刚回顾过的 Python 访问“修饰符”概览:
修饰符/约定 说明 示例 Public 默认访问级别;属性与方法可从类外访问 self.attributeProtected 以下划线开头表示;用于指示部分受限访问的约定 self._attributePrivate 以下双下划线开头表示;名称改写提供有限的访问限制 self.__attributeProperties 使用 @property decorator 为私有属性提供受控访问 @property def attribute(self):只读属性 无 @attribute.setter;允许只读访问属性 @property def attribute(self):
实现封装的最佳实践
您可以遵循以下若干最佳实践,以确保您的代码与其他经验丰富的 OOP 开发者的代码保持良好一致:
如果某些属性或方法仅供您使用 ,请创建受保护或私有属性/方法。受保护或私有成员通常会被排除在文档之外,并向他人发出信号:作为开发者,您可以不经通知就更改它们,从而不鼓励他人使用。
不必为每个类属性都创建属性(property)。 对于具有大量属性的大型类,编写 attr.getter 和 attr.setter 方法会让人头大。
考虑在用户访问受保护成员(_)时发出警告 。
谨慎使用私有成员 ,因为它们可能会让不熟悉该约定的人难以阅读代码。
优先保证清晰而非晦涩。 由于封装旨在提升代码可维护性和数据保护,不要完全隐藏类的重要实现细节。
如果您想创建只读属性 ,不要实现 @attr.setter 方法。用户将能够访问该属性,但无法修改。
始终记住封装是一种约定 ,而非 Python 语法强制执行的内容。
对于简单的类,考虑使用数据类(dataclasses) ,它们可以用一行代码启用类的封装。不过,数据类适用于属性与方法可预期的简单类。参阅这篇数据类教程 了解更多。
结语与延伸资源
在本教程中,我们学习了 Python 面向对象编程的核心支柱之一:封装。
封装允许您为类对象内部存储的数据定义受控访问方式。这使您能够编写干净、可读且高效的代码,并防止类数据被意外更改或删除。
以下是一些相关资源,帮助您深化 OOP 知识:
封装是 Python 中面向对象的基本原则之一。它可以保护您的类不被意外更改或删除,并促进代码的可复用性与可维护性。来看一个简单的类定义:
许多 Python 程序员都会这样定义类。然而,这与专业 Python 开发者所遵循的最佳实践相去甚远。这个类的问题在您尝试修改其数据时就显现出来了:
想象一下,一台 iPhone 跑着 Android——太离谱了!显然,我们需要在类的内部设置一些边界,使用户不能随心所欲地更改其属性。
如果他们确实要更改,那么必须在我们的规则之下进行。但我们仍希望
.os或.brand这些属性在表面上保持不变。以上这些目标都可以通过封装来实现,在本教程中,我们将全面学习它。让我们开始吧!
Python 如何实现封装?
Python 倾向于通过约定与编程实践来支持封装,而非强制性的访问修饰符。您看,Python 很多代码设计背后的基本理念是“大家都是成年人”。我们只能将封装作为一种约定来实现,并期望其他 Python 开发者信任并尊重我们的代码。
在 Java、C++ 等其他 OOP 语言中,封装通过
public、private或protected等访问修饰符被严格强制执行,但 Python 并没有这些:_protected、__private)public、protected、private关键字强制执行public、protected、private关键字强制执行@property装饰器配合用于受控访问因此,我接下来将展示的封装技术大多(如果不是全部)都是 Python 的约定。若您愿意,它们很容易被打破。但我相信您会在自己的开发项目中尊重并遵循它们。
Python 中的访问修饰符
假设我们有这样一个简单的类:
它只有一个可打印的 height 属性。问题在于我们也可以把它改成任意值:
那么,如何告知用户禁止更改 height 呢?我们可以通过在前面加一个下划线把它变成受保护成员:
现在,了解此约定的人会知道他们只能访问该属性,而我们强烈不鼓励他们使用和修改它。但如果他们愿意,仍然可以修改,没错。
那我们如何也阻止这一点?用另一种约定——通过在前面加双下划线,将属性变成私有成员:
现在,如果有人尝试访问该属性,Python 会抛出错误,更别提修改它了。
但您注意到我们刚刚做了什么吗?我们把与对象相关的唯一信息对用户隐藏了。我们的类变得毫无用处,因为它没有任何公共属性。
那么,如何既向用户公开树高,又能控制其访问和修改方式?例如,我们希望树高在特定范围内,且只能是整数。如何强制执行呢?
这时,使用 Java 的朋友可能会建议使用getter 和 setter 方法。那我们先试试:
这样,您创建了私有属性
__height,但允许用户通过get_height和set_height方法以受控方式访问和修改它。在设置新值之前,
set_height会确保新高度在某个范围内且为数值。但这些方法对于一个简单操作来说似乎有些小题大做。而且,像这样写代码也不够优雅:
如果我们能这样写:
同时仍能强制高度的数据类型与范围正确,不是更优雅、更易读吗?答案是肯定的,我们将在下一节学习如何做到这一点。
在 Python 类中使用 @property 装饰器
我们引入一种新技巧——为属性创建属性(properties):
我们希望用户能像访问普通属性
height那样访问隐藏的__height。为此,我们定义一个名为height的方法,返回self.__height,并用@property装饰它。现在,我们可以调用
height并访问该私有属性:但最妙的是,用户无法修改它:
因此,我们再添加一个名为
height(self, new_height)的方法,并用height.setter装饰器包装。在该方法内,我们实现强制高度数据类型与范围的逻辑:现在,当用户尝试修改
height属性时,会调用@height.setter,从而确保传入的值正确:我们还可以通过
@height.getter自定义通过点号访问height属性的方式:即使我们用整数高度创建了
pine,也可以通过@height.getter修改返回值的形式。以上就是在 Python 类中促进封装的一些示例。请记住,封装仍然是一种约定,因为我们依然可以破坏内部的
__height私有成员:Python 类中的一切都是公开的,私有方法也是如此。这并非设计缺陷,而是“大家都是成年人”理念的一种体现。
下面是我们刚刚回顾过的 Python 访问“修饰符”概览:
self.attributeself._attributeself.__attribute@property decorator为私有属性提供受控访问@property def attribute(self):@attribute.setter;允许只读访问属性@property def attribute(self):实现封装的最佳实践
您可以遵循以下若干最佳实践,以确保您的代码与其他经验丰富的 OOP 开发者的代码保持良好一致:
attr.getter和attr.setter方法会让人头大。_)时发出警告。@attr.setter方法。用户将能够访问该属性,但无法修改。结语与延伸资源
在本教程中,我们学习了 Python 面向对象编程的核心支柱之一:封装。
封装允许您为类对象内部存储的数据定义受控访问方式。这使您能够编写干净、可读且高效的代码,并防止类数据被意外更改或删除。
以下是一些相关资源,帮助您深化 OOP 知识: