7.11 封装
封装不是单纯意义上的隐藏
首先封装怎么实现隐藏
属性的隐藏(数据属性,函数属性)
python类中,以__开头命名的属性会被隐藏。
注意:__开头__结尾的是python内置函数的意思,不是隐藏。
实际上,隐藏就是属性名的变形操作,在类定义的时候发生变形。
- 隐藏属性的特点:
- 在类的外部无法用定义时的属性名来调用:obj__AttrName
在类的内部可以直接用定义时的属性名来调用: obj__AttrName
在类定义的时候隐藏属性都发生了变化,所以也直接用变形后的属性名调用了
子类无法覆盖父类__开头的属性
class A: __x = 1 # 实际上隐藏是在定义类的时候变形了属性名,变成了 _A__x def __init__(self, name): self.__name = name # 变形成 _A__name def __foo(self): # 变形成 _A__foo print('run foo') def bar(self): self.__foo() # 类调用的时候也被变形成 _A__foo了,所以跟上面变化后相同,所以在类内部可以调用 print('from bar') # print(A.__x) # 会报错 # print(A.__foo) # 会报错 a = A('a') # print(a.__name) # 会报错 print(a._A__name) # 隐藏属性的特点1,不过不推荐这么访问 # 我们看看他们的名称空间发生了什么 print(A.__dict__) print(a.__dict__) a.bar() # 隐藏属性的特点2
执行结果:
a {'__module__': '__main__', '_A__x': 1, '__init__': <function A.__init__ at 0x0000000001E5A6A8>, '_A__foo': <function A.__foo at 0x0000000001E5A7B8>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} {'_A__name': 'a'} run foo from bar
我们可以看到,在定义类的时候,隐藏的属性发生了变形。
隐藏属性特点第3点代码例:
class Foo: def __func(self): # _Foo__func print('from foo') class Bar(Foo): def __func(self): # _Bar__func print('from bar') b = Bar() print(Bar.__dict__)
总结这种变形需要注意的问题:
- 这种异常不是真正意义上的隐藏,只是变形。
这种变形只在类定义的时候就会发生,且只发生一次。类定义之后就全都变形了。
在定义之后给类增加一个__开头的属性,定义的属性不会变形,而是以原名添加到类中。
```
class B:
__x = 1def __init__(self, name): self.__name = name
b = B(‘b‘)
b.__age = 11
print(b.__dict__)
B.__y = 2
print(B.dict) # __y 会原样的增加到类的命名空间中
print(b.dict) # __age 会原样的增加到类的命名空间中
执行结果:
{‘_B__name‘: ‘b‘}
{‘module‘: ‘main‘, ‘_B__x‘: 1, ‘init‘: <function B.__init__ at 0x000000000288A6A8>, ‘dict‘: <attribute ‘dict‘ of ‘B‘ objects>, ‘weakref‘: <attribute ‘weakref‘ of ‘B‘ objects>, ‘doc‘: None, ‘__y‘: 2}
{‘_B__name‘: ‘b‘, ‘__age‘: 11}
```在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为隐藏的
```
class A:
def __foo(self):
print(‘A.foo‘)def bar(self): print('A.bar') self.__foo()
class B(A):
def __foo(self):
print(‘B.foo‘)b = B()
b.bar() # 如果foo不是隐藏属性,调用父类的bar里的foo会调用B类中的,而使用了隐藏属性就会调用A类的__foo
执行结果:
A.bar
A.foo
```
封装的意义
1. 封装数据属性的意义:来明确区分内外,控制外部对隐藏属性的操作。
在类的外部不能直接访问封装属性,但是在类的内部还是可以直接访问。
从外部只能间接访问封装的属性。我们可以通过定义接口函数来控制访问的方法。
在外部不能直接修改类中的属性,只能通过接口改,
例:
class People: def __init__(self, name, age): self.__name = name # 封装数据属性 self.__age = age def tell_info(self): # 设置接口让用户只能以提供的接口访问数据 print('Name:%s ; Age:%s' % (self.__name, self.__age)) def set_info(self, name, age): # 设置接口函数来限制用户修改的方式 if not isinstance(name, str): # 设置条件 print('名字必须是字符串') return if not isinstance(age, int): # 设置条件 print('年龄必须是int类型') return self.__name = name self.__age = age p = People('a',11) p.tell_info() p.set_info('b', '12')
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()
封装与可扩展性
我们有一个Room的类,开始没有height属性,后来追加。 因为用户调用的是接口方法,所以我们内部不论怎么修改,用户都是以相同的方式调用。 这就体现出了一种可扩展性
class Room: def __init__(self, name, owner, weight, lenth, height): # 后增加了height self.name = name self.owner = owner self.__weight = weight self.__lenth = lenth self.__height = height # 追加height属性 def tell_result(self): # return self.__weight * self.__lenth * self.__height # 追加了height r = Room('卫生间','a', 10, 10, 10) # 追加属性 # 用户调用接口是完全没有变化的 r.tell_result()