Python面向对象之三大特征-封装
三大特征
【引】属性查找顺序
- 对象的名称空间里只存放着对象独有的属性,而对象们相似的属性是存放于类中的。
- 对象在访问属性时,会优先从对象本身的
__dict__
中查找,未找到,则去类的__dict__
中查找
封装
【一】概要
- 封装是将数据和操作数据的方法打包在一个单元(类)中,实现数据隐藏、代码组织、隔离变化的目的。这使得对象的内部实现细节对外部用户不可见,同时提供了清晰的接口供外部代码使用。
- 通俗来讲,封装就是一种思想,将数据和功能整合到一起,也就是“整合”代码。
- 针对封装到对象或者类中的属性,我们还可以严格控制对它们的访问,分两步实现:隐藏和开放接口
【二】常用方法
- 隐藏
- 通过在属性名前加双下划线
__
,来实现隐藏属性
- 通过在属性名前加双下划线
- 开放接口
- 隐藏后的属性具有私有性,对于外部是隐藏的,对于内部是开放的,通过暴露接口来使外界访问
【三】详解
- 模块化组织: 封装有助于将代码划分为相对独立、可重用的模块。每个模块都可以包含特定功能的一组类、函数或变量,使得代码结构更加清晰,易于维护。
- 隐藏实现细节: 封装允许将对象的内部实现细节隐藏起来,只暴露必要的接口。这样,其他部分的代码不需要关心对象内部是如何实现的,只需要通过公共接口与对象进行交互。这种隐藏减少了代码之间的耦合性,提高了代码的可维护性和可扩展性。
- 信息隐藏: 封装还提供了对对象内部信息的控制能力。通过将数据成员设为私有,只能通过公共方法进行访问,可以确保对数据的修改和访问都经过一定的逻辑控制,增强了程序的健壮性。
【1】隐藏属性
- 通过在属性名前加双下划线
__
,来实现隐藏属性
class Class(object):
name = 'user'
def func(self):
print('实例方法')
@classmethod
def func1(cls):
print('类方法')
@staticmethod
def func2():
print('静态方法')
'''正常情况下,通过类名+【.】都是可以访问到属性的'''
print(Class.name)
print(Class.func(self=Class()))
print(Class.func1())
print(Class.func2())
class Class(object):
_name = 'user001'
__name = 'user'
def __func(self):
print('实例方法')
@classmethod
def __func1(cls):
print('类方法')
@staticmethod
def __func2():
print('静态方法')
'''通过单下划线并不能实现隐藏属性,外界依旧可以访问的到'''
print(Class._name) # user001
print(Class.__name) # AttributeError: type object 'Class' has no attribute '__name'.
print(Class.__func(self=Class()))
print(Class.__func1())
print(Class.__func2())
【1.1】实现隐藏的本质:变形
- 当我们查看类的名称空间就可以发现端倪
class Class(object):
_name = 'user001'
__name = 'user'
def __func(self):
print('实例方法')
print(Class.__dict__)
# {'__module__': '__main__', '_name': 'user001', '_Class__name': 'user', '_Class__func': <function Class.__func at 0x00000162D3B327A0>,……}
'''通过【_类名__属性名】是可以拿到值的'''
print(Class._Class__name) # user
'''是可以通过变形后的名称对属性进行修改的'''
print(Class._Class__name) # user
Class._Class__name = 'user002'
print(Class._Class__name) # user002
'''但是不可以通过【__属性名】为其添加隐藏属性'''
Class.__age = 20
print(Class._Class__age) # AttributeError: 'Class' object has no attribute '_Class__age'.
'''再次查看名称空间可以发现,是新增了一个键为【__age】,而不是同上述的隐藏属性进行了变形'''
print(Class.__dict__) # {......,'__age': 20}
'''由此可知,变形只在定义类的时候触发'''
- 以上是类中的隐藏属性,也可以通过
__init__
为对象添加自己的隐藏属性
class Class(object):
def __init__(self,name,age):
self.__name = name
self.__age = age
c=Class(name='001',age=18)
# print(c.name) # AttributeError: 'Class' object has no attribute 'name'
# print(c.age)
print(c.__dict__)
# {'_Class__name': '001', '_Class__age': 18}
【2】开放接口
- 隐藏后的属性具有私有性,对于外部是隐藏的,对于内部是开放的,通过暴露接口来使外界访问
class Class(object):
_name = 'user001'
__name = 'user'
def __func(self):
print('实例方法')
@classmethod
def __func1(cls):
print('类方法')
@staticmethod
def __func2():
print('静态方法')
def main(self):
'''开放的接口'''
func_choice = input("请输入想要使用的功能:").strip()
if func_choice == 'func':
# 调用隐藏的实例方法
self.__func()
elif func_choice == 'func1':
# 调用隐藏的类方法
self.__func1()
elif func_choice == 'func2':
# 调用隐藏的静态方法
self.__func2()
else:
# 打印隐藏的名字属性
print(self.__name)
Class.main(self=Class())
- main函数就是作为接口开放给外界的,可以通过main来调用到类中的隐藏属性
【2.1】封装的隔离变化作用
-
当类的内部实现需要改变时,如果外部代码依赖于类的内部细节,那么这种变化可能会对外部代码产生影响。通过封装,类的内部细节可以被隐藏起来,从而降低了对外部代码的影响,使得类的实现可以更自由地进行修改。
-
类外部只需拿到一个接口,只要接口名、参数不变,则无论设计者如何改变内部实现代码,使用者均无需改变代码。
class Class(object):
_name = 'user001'
__name = 'user'
def __func(self):
print('实例方法')
print("我可以对函数随意改变,只要不修改传参的内容和返回值,接口都不需要修改")
@classmethod
def __func1(cls):
print(cls._name)
print("我可以对函数随意改变,只要不修改传参的内容和返回值,接口都不需要修改")
@staticmethod
def __func2():
print(Class.__name)
print('func2')
'''我可以对函数随意改变,只要不修改传参的内容和返回值,接口都不需要修改'''
def main(self):
'''开放的接口'''
func_choice = input("请输入想要使用的功能:").strip()
if func_choice == 'func':
# 调用隐藏的实例方法
self.__func()
elif func_choice == 'func1':
# 调用隐藏的类方法
self.__func1()
elif func_choice == 'func2':
# 调用隐藏的静态方法
self.__func2()
else:
# 打印隐藏的名字属性
print(self.__name)
Class.main(self=Class())
【3】扩展:装饰器@property
- property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
- 通俗来讲,就是将一个方法(函数属性)包装成数据属性
# property 的源码解释
class property(object):
"""
Property attribute.
# 属性装饰器。
fget
function to be used for getting an attribute value
fset
function to be used for setting an attribute value
fdel
function to be used for del'ing an attribute
doc
docstring
Typical use is to define a managed attribute x:
# 典型的用法是定义一个受管理的属性 x:
class C(object):
def getx(self): return self._x
def setx(self, value): self._x = value
def delx(self): del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
Decorators make defining new properties or modifying existing ones easy:
# 使用装饰器可以更轻松地定义新属性或修改现有属性:
class C(object):
@property
def x(self):
"I am the 'x' property."
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
"""
def deleter(self, *args, **kwargs): # real signature unknown
""" Descriptor to obtain a copy of the property with a different deleter. """
""" 用于获取具有不同删除器的属性副本的描述符。 """
pass
def getter(self, *args, **kwargs): # real signature unknown
""" Descriptor to obtain a copy of the property with a different getter. """
""" 用于获取具有不同获取器的属性副本的描述符。 """
pass
def setter(self, *args, **kwargs): # real signature unknown
""" Descriptor to obtain a copy of the property with a different setter. """
""" 用于获取具有不同设置器的属性副本的描述符。 """
pass
【3.1】@property
的具体用法
'''基本用法'''
class Class(object):
__name = "user001"
@property
def get_name(self):
return self.__name
print(Class().get_name) # user001
'''具体案例'''
# 当我想要获取到我自己的bmi值时
'''不加property'''
class Person(object):
def __init__(self, height, weight):
'''初始化得到对象的身高体重'''
self.height = height
self.weight = weight
def get_bmi(self):
bmi = self.weight / (self.height ** 2)
return bmi
p = Person(height=1.8,weight=65)
# 需要通过函数来调用,但实际情况下,bmi只是一个数值,作为对象的一个数据属性
print(p.get_bmi()) # 20.061728395061728
'''加了property'''
class Person(object):
def __init__(self, height, weight):
'''初始化得到对象的身高体重'''
self.height = height
self.weight = weight
@property
def bmi(self):
bmi = self.weight / (self.height ** 2)
return bmi # 注:加了装饰器property的一定要有返回值
p = Person(height=1.8, weight=65)
# 可以通过【.bmi】直接获取到值
print(p.bmi) # 20.061728395061728
【4】扩展:装饰器@property
的扩展用法@x.setter
,@x.deleter
'''通过打印上述bmi的类,可以看到他属于property类下的对象了'''
print(Person.bmi.__class__) # <class 'property'>
'''方式一:使用装饰器'''
class Person(object):
def __init__(self, username):
'''初始化得到对象的身高体重'''
# 隐藏属性
self.__username = username
@property
def name(self):
return self.__username
'''使用装饰器的方法限制函数名必须为property装饰的函数名'''
@name.setter
def name(self, value):
# def name_setter(self, value): # AttributeError: can't set attribute 'name'
self.__username = value
@name.deleter
def name(self):
del self.__username
p = Person(username='user')
print(p.__dict__) # {'_Person__username': 'user'}
print(p.name) # user
p.name = '001' # 当出现赋值的等于号时,将自动触发@property.setter下的方法
print(p.name) # 001
del p.name # 当出现del时,将自动触发@property.deleter下的方法
print(p.__dict__) # {}
'''方式二:使用property()'''
class Person(object):
def __init__(self, username):
'''初始化得到对象的身高体重'''
# 隐藏属性
self.__username = username
def name(self):
return self.__username
def name_setter(self, value): # AttributeError: can't set attribute 'name'
self.__username = value
def name_deleter(self):
del self.__username
'''使用property函数时,不限制函数名,按照关键字传参可以不按照顺序,但位置参数顺序不可以乱'''
name = property(fget=name, fset=name_setter, fdel=name_deleter)
# name = property(name_setter, name,name_deleter) # TypeError: Person.name_setter() missing 1 required positional argument: 'value'
p = Person(username='user')
print(p.__dict__) # {'_Person__username': 'user'}
print(p.name) # user
p.name = '001' # 当出现赋值的等于号时,将自动触发@property.setter下的方法
print(p.name) # 001
del p.name # 当出现del时,将自动触发@property.deleter下的方法
print(p.__dict__) # {}