封装
封装有两个层面:封装数据、封装功能。
在python中没有常量一样,只是将变量名用大写的方式来提醒你,但仍然可以修改。封装数据就是将数据隐藏起来,通过在名称前加双下划线:__attr
但是并没有真正的隐藏,只是做了一次变形,让你无法通过原来的名字访问到:__attr >>> _ClassName__attr, 通过后者,你仍然可以访问到。
有两点要注意:
1. 这种变形操作只发生在类的定义阶段或对象的定义阶段(实例化)。
2. 在类的外部,无法直接使用变形的属性,而在类的内部可以。
栗子一:
class Teacher:
__school = 'oldboy' # >>> _Teacher__school
def __init__(self,name,salary):
self.name=name
self.__salary=salary # >>> _Teacher__salary
def tell(self):
print('salary is: ',self.__salary)
# print('salary is: ',self._Teacher__salary)
t = Teacher('egon', 3000)
t.__salary # 类外部访问,提示没有__salary属性
t.tell() # 通过类内部的tell方法可以访问__salary(其实在定义阶段就已经变形过了)
栗子二:
class A():
def foo(self):
print('from A foo ')
self.bar()
def bar(self):
print('from A bar')
class B(A):
def bar(self):
print('from B bar')
obj = B()
obj.foo()
'''
# obj找foo属性: obj > B > A 找到了,执行 print('from A foo ')
from A foo
# 继续往下执行,self.bar(),找bar属性,obj > B,找到了,执行 print('from B bar')
from B bar
'''
现在,我们把上面这个栗子中的bar属性隐藏起来:
class A():
def foo(self):
print('from A foo ')
self.__bar() # >>> _A__bar :变形发生在定义阶段
def __bar(self): # >>> _A__bar
print('from A bar')
class B(A):
def __bar(self): # >>> _B__bar
print('from B bar')
obj = B()
obj.foo()
''' 输出结果变了:
from A foo
from A bar
'''
接口
单纯的封装没有意义,必须提供接口。
python并不会检查输入类型,通过封装和接口,在接口中人为进行限制:
class People:
def __init__(self,name,age):
self.__name = name
self.__age = age
def tell_info(self):
print('''
---- info ----
NAME: %s
AGE: %s
'''%(self.__name,self.__age))
def set(self,val1,val2): # python本身不会检查输入类型,可以通过在接口中人为的限制输入类型。
if not isinstance(val1, str):
raise TypeError('name must be str')
if not isinstance(val2, int):
raise TypeError('age must be int')
self.__name = val1
self.__age = val2
egon = People('egon', 18)
egon.set('林海峰',20)
egon.tell_info()
property
property是一个内置函数,访问它时会执行一段功能(函数)然后返回值。
作用:把被装饰的函数伪装成数据属性。
class People():
def __init__(self,name,height,weight):
self.__name=name
self.__height=height
self.__weight=weight
def bmi(self): # 计算体质指数bmi
res = self.__weight / (self.__height ** 2)
print('''
name: %s
bmi: %s
'''%(self.__name,round(res,2)))
egon = People('egon',18,1.75,65)
egon.bmi() # bmi更应该像是一个人的属性,而不是执行了一个方法
'''
name: egon
bmi: 21.22
'''
上面这个栗子,对于使用者来说,很容易察觉到是加括号执行了一段函数,这时property函数就派上用场了:
class People():
def __init__(self,name,height,weight):
self.__name=name
self.__height=height
self.__weight=weight
def bmi(self): # 计算体质指数bmi
res = self.__weight / (self.__height ** 2)
return res # 比起打印结果,返回一个值,更像在访问一个数据
bmi = property(bmi) # 调用property函数把bmi函数的执行结果赋值给bmi
egon = People('egon',1.75,65)
egon.bmi
# 拿到的是一个值。这样对于使用者来说,就像是访问数据属性一样简单自然,
# 而不必知道其实是执行了一个函数后的计算结果,这就将背后的逻辑隐藏起来。
# 而且不论函数内的逻辑如何改变,使用者都不用关心,只要通过bmi这个接口,就可以拿到想要的结果。
装饰器的本质是任何可以调用的对象,因此我们可以用装饰器的语法进一步简化:
class People():
def __init__(self,name,height,weight):
self.__name=name
self.__height=height
self.__weight=weight
@property
def bmi(self):
res = self.__weight / (self.__height ** 2)
return res
egon = People('egon',1.75,65)
egon.bmi
setter, deleter方法
class Teacher:
def __init__(self,name,permission=False):
self.__name = name
self.permission = permission # 标志位,下面的函数检测用
@property
def name(self): # obj通过obj.name这个接口访问self.__name
return self.__name
@name.setter # @propery创建的另一个装饰器
def name(self,val):
if not isinstance(val,str):
raise TypeError('%s must be str'% val)
self.__name = val # 通过类型检查后,可以为self.__name赋值
@name.deleter
def name(self):
if not self.permission: # 初始化时默认标志位为False
raise PermissionError('不让删')
del self.__name
# self.permission = False 也可以在操作完成后,恢复标志位
obj = Teacher('egon')
obj.name = 'EGON' # 修改对象的self.__name值,>>> @name.setter ,修改成功
print(obj.name) # 访问对象的self.__name >>> @property
del obj.name # 删除对象的self.__name属性,>>> @name.deleter 标志位判断失败,不让删。
上面的这个栗子体现了property提供统一访问接口的功能,使用者似乎是在访问或操作一个对象的属性,但其实是通过三个函数实现的。