一、封装
1. 什么是封装
- 封装,就是把类属性的内容,对外隐藏,对内开放
- 隐藏表示不能被使用,开放表示可以被使用
2. 如何隐藏类的属性
- 隐藏属性的语法:在属性开头前加
__
,比如__数据属性名或函数属性名
,那么__数据属性名或函数属性名
就是隐藏属性- 这种隐藏是对外不对内的,即在类的内部可以直接访问,而在类的外部则无法直接访问
class People:
__country='China'
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def eat(self):
print('eat......')
print(People.__country)
peo1=People('xut',19,'male')
# 隐藏的数据属性不能在外部内使用
peo1.__country
# 需要间接的,利用类中未隐藏的属性去调用,对内开放
peo1.eat()
=============================================================
AttributeError: 'People' object has no attribute '__country'
eat......
China
隐藏的原理:
- 这种隐藏只是语法上的变形操作
- 内部能访问的原因:在检查语法时,会自动改变属性的语法,变成
_类名__属性名
- 这种变形,只在类定义阶段的语法检查时,发生一次,只能在类内定义,类外定义无效
- 因此,如果是在类外,也可以通过
_类名__属性名
使用,但是这样就失去了隐藏的意义- 如果不想让子类的方法覆盖父类的,可以将该方法开头前加
__
__init__
用于初始化对象
class People:
__country='China'
__n=100
def __init__(self,name):
self.__name=name
peo1=People('xut')
print(People.__dict__)
print(peo1.__dict__)
=============================================
{'__module__': '__main__', '_People__country': 'China', '_People__n': 100, ...省略
{'_People__name': 'xut'}
- 如果不想让子类的方法覆盖父类的,可以将该方法开头前加
__
class Foo:
def f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.f1()
class Bar(Foo):
def f1(self):
print('Bar.f1')
obj=Bar()
obj.f2()
============================
Foo.f2
Bar.f1
# 如果不想让子类的方法覆盖父类的,可以将该方法开头前加`__`
class Foo:
def __f1(self): # _Foo__f1
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.__f1() # self._Foo__f1
class Bar(Foo):
def __f1(self): # _Bar__f1
print('Bar.f1')
obj=Bar()
obj.f2()
===========================================
Foo.f2
Foo.f1
3. 为什么要封装
① 封装数据属性的目的
- 定义属性的目的就是给类外部的使用者使用
- 隐藏
数据属性
是为了不让外部使用者直接使用,需要类内部开辟一个接口- 外部的使用者,通过接口间接的操作隐藏的数据属性
这样就可以在接口上,定制逻辑和规则,严格控制使用者对数据属性的操作
② 封装函数属性的目的
- 隐藏函数属性,也是为了不让外部直接使用,需要类内部开辟一个接口
- 接口内可以调用隐藏的函数属性,比如,将多个隐藏函数,放入到一个未隐藏函数中,由这个未隐藏的函数对外使用
- 好处是
隔离了复杂度
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
#隔离了复杂度,同时也提升了安全性
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. 封装属性的使用
- 先隐藏属性,再设计接口,才能控制使用者对属性的操作
① 定义查看接口
class People:
def __init__(self,name,age):
self.__name=name
self.__age=age
# 对外开放接口,这个接口就是程序员自己设计的标准
def tell_info(self):
print('%s:%s' %(self.__name,self.__age))
peo1=People('xut',18)
peo1.tell_info
=================================================
xut:18
② 定义修改接口
class People:
def __init__(self,name,age):
self.__name=name
self.__age=age
def tell_info(self):
print('%s:%s' %(self.__name,self.__age))
# 定义修改接口
def set_info(self,x,y):
self.__name=x
self.__age=y
peo1=People('xut',18)
peo1.set_info('XDW',20)
peo1.tell_info()
=================================================
XDW:20
class People:
def __init__(self,name,age):
self.__name=name
self.__age=age
def tell_info(self):
print('%s:%s' %(self.__name,self.__age))
# 定义修改接口,并指定规则
def set_info(self,name,age):
if type(name) is not str:
print('用户必须为str类型')
return
if type(age) is not int:
print('年龄必须为int类型')
return
self.__name=name
self.__age=age
peo1=People('xut',18)
peo1.set_info(111,20)
peo1.set_info('xde','xdw')
====================================================
用户必须为str类型
年龄必须为int类型
class People:
def __init__(self,name,age):
self.__name=name
self.__age=age
def tell_info(self):
print('%s:%s' %(self.__name,self.__age))
# 定义修改接口,并指定规则
def set_info(self,name,age):
if type(name) is not str:
# raise 异常处理,主动报错
raise TypeError('用户必须为str类型')
if type(age) is not int:
# raise 异常处理,主动报错
raise TypeError('年龄必须为int类型')
self.__name=name
self.__age=age
peo1=People('xut',18)
peo1.set_info(111,20)
peo1.set_info('xde','xdw')
======================================================
TypeError: 用户必须为str类型
TypeError: 年龄必须为int类型
5. property
property
内置函数,可以将类中函数伪装成数据属性的样子,达到查看、修改、删除property
可以将类内的函数伪装成一个数据属性去访问- 类加括号就是实例化的过程
① 未使用property
- 没有使用伪装前,使用类似于特征类的属性,需要使用加括号使用
class People:
def __init__(self,name,weight,height):
self.name=name
self.weight=weight
self.height=height
def bmi(self):
return self.weight / (self.height ** 2)
peo1=People('xut',75,1.8)
print(peo1.bmi())
=====================================================
23.148148148148145
② 使用@property
伪装函数
class People:
def __init__(self,name,weight,height):
self.name=name
self.weight=weight
self.height=height
# 添加property内置装饰器
@property
def bmi(self):
return self.weight / (self.height ** 2)
peo1=People('xut',75,1.8)
# 不用再bmi() 直接bmi就能触发功能,拿到返回值
print(peo1.bmi)
===================================================
23.148148148148145
③ 将接口伪装成属性
- 伪装前,使用者需要使用
tell_name()
的形式
class People:
def __init__(self,name):
self.__name=name
def tell_name(self):
return self.__name
peo1=People('xut')
print(peo1.tell_name())
================================
xut
class People:
def __init__(self,name):
self.__name=name
# 伪装成属性
@property
def name(self):
return self.__name
peo1=People('xut')
# 访问属性
print(peo1.name)
=================================
xut
④ @name.setter
修改属性需求
- 只要是被
property
装饰过的函数,就可以使用.setter
修改属性
class People:
def __init__(self,name):
self.__name=name
@property
def name(self):
return self.__name
# 修改属性
@name.setter
def name(self,name):
if type(name) is not str:
raise TypeError('名字必须为str类型')
self.__name=name
peo1=People('xut')
peo1.name='davide'
print(peo1.name)
===================================================
davide
⑤ @name.deleter
删除属性需求
- 只要是被
property
装饰过的函数,就可以使用.deleter
修改属性
class People:
def __init__(self,name):
self.name=name
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
if type(name) is not str:
raise TypeError('名字必须为str类型')
self.__name=name
# 可以删除属性
@name.deleter
def name(self):
raise PermissionError('禁止删除')
peo1=People('xut')
del peo1.name
==========================================================
PermissionError: 禁止删除
⑥ 古老的写法
class People:
def __init__(self,name):
self.name=name
def tell_name(self):
return self.__name
def set_name(self,name):
if type(name) is not str:
raise TypeError('名字必须为str类型')
self.__name=name
def del_name(self):
raise PermissionError('禁止删除')
name=property(tell_name,set_name,del_name)
二、绑定方法与非绑定方法
绑定方法:
- 绑定给谁就应该由谁来调用,谁来调用就会将谁当作第一个参数
自动传入
1. 绑定方法分为两类
① 绑定给对象的方法
- 在类内部定义的函数(没有被任何装饰器装饰的),默认就是绑定给对象
② 绑定给类的方法
- 在类内部定义的函数,如果被装饰器
@classmethod
装饰,那就是绑定给类的,需要由类来调用,类来调用,就自动将类作为第一个参数传入
③ 绑定方法如何选择
- 如果函数体代码,需要用到外部传入的类,则应该将该函数定义成绑定给类的方法
- 如果函数体代码,需要用到外部传入的对象,则应该将该函数定义成绑定给对象的方法
- 最终取决于用哪个,就用什么方法
class Foo:
@classmethod
def f1(cls):
print(cls)
def f2(self):
print(self)
obj=Foo()
print(obj.f2)
print(Foo.f1)
=====================================================================
# 都是绑定方法
<bound method Foo.f2 of <__main__.Foo object at 0x0000018D73E2BDA0>>
<bound method Foo.f1 of <class '__main__.Foo'>>
- 了解即可:绑定给类的,应该由类来调,但是对象也可以使用,但是自动传入的还是类
class Foo:
@classmethod
def f1(cls):
print(cls)
def f2(self):
print(self)
obj=Foo()
# 绑定给谁,就应该由谁使用
print(Foo.f1)
print(obj.f1)
=================================================
<bound method Foo.f1 of <class '__main__.Foo'>>
<bound method Foo.f1 of <class '__main__.Foo'>>
④ 类实例化的应用场景:从配置文件中读取配置信息
- 默认的实例化方式
# 模拟配置文件
import settings
class Mysql:
def __init__(self,ip,port):
self.ip=ip
self.port=port
def tell_info(self):
print('%s:%s' %(self.ip,self.port))
# 默认的实例化方式:类名(内容)
obj=Mysql('10.10.0.9',3307)
obj1=Mysql(settings.IP,settings.PORT)
- 从配置文件中读取配置,完成实例化
setting配置文件:
IP='10.0.0.1'
PORT=3306
# 模拟配置文件
import setting
class Mysql:
def __init__(self,ip,port):
self.ip=ip
self.port=port
def tell_info(self):
print('%s:%s' %(self.ip,self.port))
# 从配置文件实例化
@classmethod
def from_conf(cls):
# 从配置文件中获取到IP和端口
return cls(setting.IP,setting.PORT)
# 一种新的实例化方式:从配置文件中读取配置,完成实例化
obj1=Mysql.from_conf()
obj1.tell_info()
==========================================================
10.0.0.1:3306
2. 非绑定
- 既不与类绑定,又不与对象绑定,没有任何自动传值效果,就是一个普通函数,类和对象都可以调用
- 类中定义的函数,如果被
@staticmethod
装饰器装饰过,那么该函数就变成非绑定方法
根据不同应用场景选择绑定和非绑定
- 如果函数体代码需要用外部传入的类,则应该将该函数定义成绑定给类的方法
- 如果函数体代码需要用外部传入的对象,则应该将该函数定义成绑定给对象的方法
- 如果函数体代码既不需要外部传入的类,也不需要外部传入的对象,则应该将该函数定义成非绑定方法,即普通函数
import setting
class Mysql:
def __init__(self,ip,port):
self.ip=ip
self.port=port
def tell_info(self):
print('%s:%s' %(self.ip,self.port))
@staticmethod
def func():
print('不绑定,普通函数')
obj=Mysql('10.10.0.9',3307)
obj.func()
Mysql.func()
print(obj.func)
print(Mysql.func)
=========================================================
不绑定,普通函数
不绑定,普通函数
<function Mysql.func at 0x000001DD5C3DD2F0>
<function Mysql.func at 0x000001DD5C3DD2F0>
import uuid
class Mysql:
def __init__(self,ip,port):
self.ip=ip
self.port=port
self.uid=self.creat_uid()
def tell_info(self):
print('%s:%s' %(self.ip,self.port))
@staticmethod
def creat_uid():
return uuid.uuid1()
obj=Mysql('10.10.0.9',3307)
print(obj.uid)
==================================================
e34837b6-7aa9-11e8-bb1f-b0359f445c6f