封装与接口
# 封装
# ☆什么是封装?
[封:] 属性对外是隐藏的, 对内是开放的..
装: 申请一个名称空间,往里面装入一系列的名字/属性..
不用做任何操作, 类与实例化对象就满足装的概念啦;但不满足封, 因为它们的属性, 内外都能访问的到.
class People:
country = 'China'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
print(People.country) # -- 内部访问 类属性
def eat(self):
print('%s is eating....' % self.name) # -- 内部访问 实例化对象属性
# print(People.__country) # 注意哦!报错,因为People类在这里还没有定义完..
peo1 = People('小川', 20, 'male')
print(peo1.name) # -- 外部访问 实例化属性
print(People.country) # -- 外部访问 类属性
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ☆如何封装?
在属性前加上
__
开头..
这种隐藏是对外不对内的, 即在类的内部可以直接访问,而在类的外部无法直接访问..
class People:
__country = 'China'
def __init__(self, name, age, sex):
self.__name = name
self.age = age
self.sex = sex
def speak(self):
print('is speaking...')
print(People.__country)
def eat(self):
print('is eating...')
print(self.__name)
def __run(self):
print('is running...')
# -- 类属性 内部能访问,外部不能
People.speak(123)
# People.__run(123) # type object 'People' has no attribute '__run'
# print(People.__country) # type object 'People' has no attribute '__country'
# -- 实例化对象属性 内部能访问,外部不能
peo1 = People('小川', 20, 'male')
peo1.eat()
# print(peo1.__name) # 'People' object has no attribute 'name'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# ☆封装底层原理
封装的隐藏属性的底层原理/特定:
1> 这种隐藏仅仅只是一种 语法上的变形操作_类名__属性名
2> 这种语法上的变形 只在类定义阶段发生一次 .. 因为类体代码仅仅只在类定义阶段检测一次..
3> 这种隐藏是 对外不对内 的,即在类的内部可以直接访问,而在类的外部无法直接访问, 原因是
在类定义阶段, 类体内代码 统一 发生了一次变形...
4> 如果不想让子类的方法覆盖父类的, 可以将该方法名前加一个__开头.
当运行到类体代码,会从上到下,先检测当前行的语法(遇到函数的话,也会先检测函数体里的代码), 在检测过程中就将封装属性的名字进行了变形!检测后执行当前行代码.丢进名称空间的名字就变成了检测时变形的名字. 所以能在内部可以直接访问到,在外部不能直接访问到..
阐述的好啰嗦... 反复横跳 只可意会不可言传.(´▽`)
# {'_People__name': '小川', 'age': 20, 'sex': 'male'}
print(peo1.__dict__)
# {... '_People__country': 'China', ... ,
# '_People__run': <function People.__run at 0x7fedb7f1cc10> ...}
print(People.__dict__)
2
3
4
5
这意味着,在类体外,以封装的方式试图添加一个隐藏属性..不会对其变形!!
# {'_People__name': '小川', 'age': 20, 'sex': 'male', '__height': 173}
peo1.__height = 173
print(peo1.__dict__)
2
3
在继承那一小节,分析 '实例化对象查找属性的顺序'时, 举了以下这个例子:
验证上方底层原理的第4点 ...不想让子类的方法覆盖父类的... : 若想访问到的是A类里的func1,如何做??
采用双下划线开头的方式将方法设置为私有的!
class A:
def func1(self):
print('A.func1')
def func2(self):
print('A.func2')
self.func1() # obj.func1()
class B(A):
def func1(self):
print('B.func1')
obj = B()
obj.func2()
"""
A.func2
B.func1
"""
# -- ( ̄O ̄;) 用封装将其变个形!!看似相同实则发生了变形.
# 子类隐藏父类不隐藏;父类隐藏子类不隐藏;都隐藏 ... 都能实现上方变更的需求.
class A:
def __func1(self): # _A.__func1
print('A.func1')
def func2(self):
print('A.func2')
self.__func1() # self._A__func1 obj._A__func1
class B(A):
def __func1(self): # _B__func1
print('B.func1')
obj = B()
obj.func2()
"""
A.func2
A.func1
"""
# -------------
class Foo:
__x = 111 # _Foo__x
class Bar(Foo):
__x = 222 # _Bar__x -- 没有覆盖的效果
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# ☆开发接口
# 隐藏数据属性
封装数据属性的目的/应用场景:
首先定义属性的目的就是为了给类外部的使用者使用的.
隐藏之后是 为了不让外部使用者直接使用 ,需要在类内部开辟一个接口.
然后让类外部的使用者通过接口来间接地操作隐藏的属性.精髓在于 -- 我们可以在接口之上附加任意的逻辑,从而严格控制使用者对属性的操作 !'增删改查'
class People:
def __init__(self, name, age):
self.__name = name
self.__age = age
def tell_info(self):
print(f'姓名:{self.__name},年龄:{self.__age}')
def set_info(self, name, age):
if type(name) is not str:
print('用户名必须为str类型!')
return
if not isinstance(age, int):
# 主动抛出一个错误让程序结束运行
raise TypeError('年龄必须为整型!')
self.__name = name
self.__age = age
peo1 = People('egon', 18)
peo1.set_info('小川', 20)
peo1.tell_info() # 姓名:小川,年龄:20
"""
封装数据属性并不是让使用者不用,只是不让他直接用,让其使用内部开发的接口实现间接使用,
而开发者可以在接口上添加逻辑严格控制使用者的操作行为
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 隐藏函数属性
封装函数属性的目的:
首先定义属性的目的就是为了给类外部的使用者使用的.
隐藏函数属性是为了不让外部使用者直接使用,需要类内部开辟一个接口.
然后在接口内去调用隐藏的功能精髓在于 -- 隔离了复杂度!
栗子0: 电视机本身是个黑盒子,隐藏了所有细节,但是一定会对外提供一堆按钮,这些按钮也正是接口的概念
栗子1: 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来啦.
"""
取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来
很明显这么做,隔离了复杂度,同时也提升了安全性
"""
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()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# @property
方法伪装数据属性
property装饰器用于将被装饰的方法 伪装成一个数据属性 ,在使用时可以不用加括号而直接使用.
class People:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
# 不妥, 因为bmi指数应该是随着身高体重的变化而变化的
# self.bmi = self.weight / (self.height**2)
@property
def bmi(self):
return self.weight / (self.height**2)
peo1 = People('egon', 75, 1.8)
print(peo1.bmi) # 23.148148148148145
# BMI指数听起来更像是一个特征(数据属性)而不是技能(函数属性).
# @property 将bmi这个技能伪装成了一个特征
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
结合封装的应用
与封装的属性 结合着 使用! >> 查看 - 修改 - 删除 <<
class People:
def __init__(self, name):
self.__name = name
@property
def name(self):
return f'姓名:{self.__name}'
@name.setter # 前提是name方法被property装饰过了.
def name(self, name):
if type(name) is not str:
raise TypeError('名字必须为str类型..')
self.__name = name
@name.deleter
def name(self):
# del self.__name
# raise PermissionError('不允许删除!')
print('不允许删除!')
peo1 = People('egon')
print(peo1.name) # 姓名:egon
peo1.name = '小川'
print(peo1.name) # 姓名:小川
del peo1.name # 不允许删除!
"""Ps 远古的用法,实现的效果是一样的.
class People:
def __init__(self, name):
self.__name = name
def get_name(self):
return f'姓名:{self.__name}'
def set_name(self, name):
if type(name) is not str:
raise TypeError('名字必须为str类型..')
self.__name = name
def del_name(self):
print('不允许删除!')
name = property(get_name, set_name, del_name)
peo1 = People('egon')
print(peo1.name) # 姓名:egon
peo1.name = '小川'
"""
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52