参考链接: Python中的面向对象编程2(数据隐藏和对象打印)
文章目录
1、封装的含义2、封装示例1、python中封装功能的实现
3、封装的好处1、封装数据2、降低复杂度
4、封装的特性1、@property方法
1、封装的含义
例子:手机拨打电话,背后的实现是一个很复杂的流程: 1、手机内部功能实现 2、信号与基站进行交互 3、手机对收到的信号进行解码 4、调用手机听筒,将收到的信号实时解码并转化为音频,实现通话。 但是在实际使用中,使用者只需要输入号码拨打电话就可完成通话。
这就是面对对象封装的概念。所以封装的一大特点:就是将复杂的信息、流程给包起来,内部处理,让使用者只需要通过简单的操作步骤,就能实现。 参考:(https://segmentfault.com/a/1190000018963865?utm_source=tag-newest)
2、封装示例
1、python中封装功能的实现
'''
1.封装:类就是个麻袋,
2.定义私有的,只在类内部使用,外部无法访问(_,__)
3.明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给
外部使用。
'''
class People:
# __ :双下划线,这就是在类中封装的语法
__region = "武汉" # _ People__region 实际发生了变形
def __init__(self, name, age):
self.__name = name
self.age = age
def print_info(self):
print('姓名:%s,年龄:%s,地区:%s'%(self.__name,self.age,self.__region))
obj = People('金鞍少年', 18) # 实例化对象并传入参数
print(obj.age) # 结果-- 18
obj.print_info() # 结果-- 姓名:金鞍少年,年龄:18,地区:武汉
print(obj.__region) # 报错:AttributeError: 'People' object has no attribute '__region',证明__region被私有化封装了
print(People.__dict__) # 查看类的属性和方法
# {'__module__': '__main__', '_People__region': '武汉', '__init__': <function People.__init__ at 0x000001541032C160>, 'print_info': <function People.print_info at 0x000001541032C310>, '_People__FindName': <function People.__FindName at 0x000001541032C3A0>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
# 通过分析知道,在类中__region,是以_People__region存储的
print(obj._People__region) # 结果 :武汉
这种自动变形的特点,就是封装:
在属性前面加两个下划线,表示对该属性进行隐藏,设置成私有,在内部都会变成成:_类名.__属性名类中定义的__region只能在内部使用,如self._region,引用的就是变形的结果 self. People__region这种变形其实正是针对内部的变形,也就是在类内部定义阶段就发生了变形(参考下面代码)。
class Foo:
__N = 1 # 数据(属性)的封装
def __info(self): # 方法封装
print(self.__N)
def print_info(self):
self.__info() # 内部调用封装方法
obj = Foo()
obj.print_info() # 结果 1
obj._Foo__info() # 通过调用变形后的方法名进行调用
Foo.__M = 10 # 类定义之后,外部调用,语法不会发生变形。
print(Foo.__M) # 结果 10
封装在某种意义上就是简单的隐藏: 1,这种语法变形机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__方法,然后就可以访问了,如 obj._Foo__info() 2.变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形 ,如:Foo.__M = 10
3、封装的好处
虽说封装只是一个语法变形机制的实现,但是其好处有好几点:
1、封装数据
主要原因是:保护私隐,明确区分内外。将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
class people:
def __init__(self,name,age):
self.__name=name
self.__age=age
def print_info(self):
print('姓名:%s, 年龄:%s' %(self.__name,self.__age))
def set_info(self, name, age):
if not isinstance(name, str):
raise TypeError('姓名必须是字符串类型')
if not isinstance(age,int):
raise TypeError('年龄必须是整型')
self.__name = name
self.__age = age
obj = people('jasn',18)
obj.print_info() #结果为:姓名:jasn, 年龄:18
obj.set_info('金鞍少年', 19) # 对修改内容进行限制,规避掉sql注入的可能
obj.print_info() # 结果为:姓名:金鞍少年, 年龄:19
2、降低复杂度
文章开头就讲到封装的一大特点:就是将复杂的信息、流程给包起来,内部处理,让使用者只需要通过简单的操作步骤,就能实现。
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做隔离了复杂度,同时也提升了安全性
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('用户认证')
def __input(self):
print('输入取款金额')
def __print_bill(self):
print('打印账单')
def __take_money(self):
print('取款')
def withdraw(self):
self.__card()
self.__auth()
self.__input()
self.__print_bill()
self.__take_money()
a=ATM()
a.withdraw()
4、封装的特性
1、@property方法
Python内置的@property装饰器就是负责把一个方法变成属性
案例一
'''
BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
'''
class People:
def __init__(self, name, height, weight):
self.__name = name
self.__height = height//10
self.__weight = weight
@property
def bmi(self):
BMI = self.__weight / (self.__height ** 2)
return '{:.2f}%' .format(BMI * 100)
obj = People('金鞍少年', 179, 67)
print(obj.bmi)
案例二
# 我们知道一个人年龄必须是整数,而且是有范围限制的。
class People:
def __init__(self,name, age):
self.name = name
self.__age = age
@property # 负责查询
def age(self):
return self.__age
@age.setter # 对修改年龄加以限制
def age(self, value):
if not isinstance(value, int): # 在设定值之前进行类型检查,增加限制的扩展性
raise TypeError('年龄必须为整型!')
if value < 0 or value > 120:
raise TypeError('年龄超出范围!')
self.__age = value
@age.deleter # 删除属性接口
def age(self):
del self.__age # del self.age
obj = People('jasn', 18)
print(obj.age)
obj.age = 99 # 触发age.setter方法
print(obj.age)
del obj.age
print(obj.age) # 删除成功就报错,提示没有
'''
@property的实现比较复杂,我们先考察如何使用。把一个age方法变成属性,只需要加上@property就可以了,
此时,@property本身又创建了另一个装饰器@age.setter,负责把一个age方法变成属性赋值,
于是,我们就拥有一个可控的属性操作来限制age取值范围。
'''
被 property 装饰的属性会优先于对象的属性被使用(找到) 而被 property装饰的属性,分成三种 property 查询 age.setter 赋值,修改 age是方法名 age.deleter 删除 如果对象要修改数据属性的时候,在没有 property 的情况下,可以随便改,但是加了之后就有一个可控的属性操作来限制age取值范围。