第十七章 面向对象高级
面向对象高级
1.封装
1.1.什么是封装
装:往容器/名称空间里存入名字
封:代表将存放于名称空间中的名字给藏起来,这种隐藏对外不对内
1.2.如何封装
语法:
在类内定义的属性前加__开头
实例:
未封装的情况:
class Foo:
x = 111
y = 222
def __init__(self,name,age):
self.name = name
self.age = age
def func(self):
print('func')
def get_info(self):
print(self.x,self.y,self.name,self.age)
self.func()
# 属性
print(Foo.x)
print(Foo.y)
f=Foo('大海',18)
# 方法
f.func()
# 访问属性方法(接口)
f.get_info()
封装的情况:封装x,y属性和func方法
class Foo:
__x = 111
__y = 222
def __init__(self,name,age):
self.name = name
self.age = age
def __func(self):
print('func')
def get_info(self):
print(self.__x,self.__y,self.name,self.age)
self.__func()
# 实现了封装,正常访问不到
print(Foo.__x)
print(Foo.__y)
f=Foo('大海',18)
f.__func()
# 但是通过位封装的接口可以访问到
f.get_info()
__开头的属性到底如何实现的隐藏?
print(Foo.__dict__)
# 类定义的时候自动变形
# # _类名__属性名
# # 1. __开头的属性实现的隐藏仅仅只是一种语法意义上的变形,并不会真的限制类外部的
# 我们不这样做
print(Foo._Foo__x)
print(Foo._Foo__y)
f=Foo('大海',18)
f._Foo__func()
# 没意义
Foo.__z = 333
print(Foo.__z)
#
Foo._Foo__z = 333
print(Foo._Foo__z)
f=Foo('大海',18)
Foo.__x
f.__func()
# 隐藏对外不对内
# 通过一个新的方法,这个方法是不隐藏的
f.get_info()
例子:判断一下输出结果
class Foo:
def __f1(self):# _Foo__f1
print('Foo.f1')# 1
#
def f2(self):
print('Foo.f2')
#
self.__f1()# _Foo__f1 类定义的时候进行了统一 专属方法
#
class Bar(Foo):
def __f1(self):# _Bar__f1
print('Bar.f1')# 2
obj=Bar()
obj.f2()
1.3.为何要封装
1.3.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 type(name) is not str:
print('名字必须为字符串')
return
if type(age) is not int:
print('年龄必须为数字')
return
self.__name= name
self.__age = age
obj=People('大海',18)
# 严格的控制查看属性
# print(obj.__name)
# 可以通过接口间接的访问到属性
obj.tell_info()
# 严格的控制修改属性
# 这样只会变成添加而已 真实是_People__name
obj.__name = '夏洛'
# print(obj.__dict__)
print(obj.__name)
# obj.tell_info()
obj.set_info('夏洛','222')
obj.tell_info()
# print(obj.__dict__)
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()
3.property把方法变成属性访问
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值 *****
例:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
体质指数(BMI)=体重(kg)÷身高^2(m)
class People:
def __init__(self,name,weight,height):
self.name = name
self.weight = weight
self.height = height
@property
def bmi(self):
return self.weight / (self.height**2)
obj=People('大海',65,1.72)
bmi=obj.bmi
print(bmi)
4.反射
通过字符串来反射/映射到对象/类的属性上
4.1.按照属性名操作属性
class People:
def __init__(self,name,age):
self.name = name
self.age = age
def run(self):
print('%s is running'%self.name)
obj=People('dahai',18)
name = 'dahai11'
print(obj.__dict__)
# # 访问属性
print(obj.name)
print(name)
# # # 修改属性
obj.name = 'dahai1'
print(obj.name)
# # # 添加属性
obj.sex = 'man'
print(obj.__dict__)
4.2.按照字符串操作属性(反射)
# 让用户使用属性
attr = input('用户输入属性').strip()
# # 正常
# attr.name
# # 反射
# 没有这样的语法
# attr.'name'
# 所以用户输入的是一个字符串的形式,我们怎样把它变成属性名,就用到了反射
# 判断对象是否有属性,有就返回True,没有就算False
# 作者写的内置函数
print(hasattr(obj,'name'))
print(hasattr(obj,'sss'))
# 获取对象属性
print(getattr(obj,'name'))
print(getattr(obj,'age'))
# # 第三个参数找不到返回None
print(getattr(obj,'xxx',None))
# 修改对象属性
setattr(obj,'name','dahai2')
#
print(getattr(obj,'name'))
#
print(obj.__dict__)
# print(hasattr(对象,'对象的属性'))
# 类也可以说是对象
# # print(hasattr(类,'类的属性/或者方法'))
print(hasattr(list,'append'))
print(hasattr(list,'appeaaand'))
4.3.反射方法应用
# # 反射方法应用
class ShoppingCart:
def shopping(self):
print('shopping')
def login(self):
print('login')
def football(self):
print('football')
def run(self):
while True:
cmd = input('请输入>>>').strip()
# if cmd == 'login':
# self.login()
# elif cmd == 'football':
# self.football()
# elif cmd == 'shopping':
# self.shopping()
if hasattr(self,cmd):
# print(getattr(self, cmd))
method=getattr(self, cmd)
method()
else:
print('输入的方法不存在')
s = ShoppingCart()
s.run()
5.单例模式
5.1、什么是单例模式
单:同一个
例: 实例/对象
模式:一个模板或者一个方法
单例模式:基于某种方法实例化多次得到实例/对象是同一个
5.2、为何用单例模式
当实例化多次得到的对象中(存放的属性)都(一样)的情况,应该将多个对象指向同一个内存,即同一个实例
5.3、如何用
# 绑定类方法的单例模式
class Mysql:
# 封装一下 不对内 对外
__instance = None
def __init__(self,ip,port):
self.ip =ip
self.port = port
@classmethod
def from_conf(cls):
# print(cls)
# print(cls.__instance)
if cls.__instance is None:
# print(cls('127.0.0.1', 1234))
cls.__instance=cls('127.0.0.1', 1234)
return cls.__instance
# print(Mysql)
obj1=Mysql.from_conf()
obj2=Mysql.from_conf()
obj3=Mysql.from_conf()
# 单例
print(obj1)
print(id(obj1))
print(obj2)
print(id(obj2))
print(obj3)
print(id(obj3))
# 不单例
obj4=Mysql('127.0.0.1', 5678)
print(obj4)
print(id(obj4))
#示例二
class Foo(object):
def __init__(self,name,age):
self.name = name
self.age =age
def do_something(self):
print(123,self.name)
obj1 = Foo('哈哈',33) #1.根据类创建对象;2.执行这个类的__init__(当前创建的空对象,'哈哈',33)
obj2 = Foo('haa',31) #2.根据类创建对象;2.执行这个类的__init__(当前创建的空对象,'haa',31)
obj1.do_something()
obj2.do_something()
元类(了解)
6.什么是元类
6.1.根据一切皆对象
在python中统一了类与类型的概念
# @Author : 大海
# @File : 7.一切皆对象.py
# 在python中统一了类与类型的概念
class Foo:
def find(self):
print('我是绑定对象的方法')
# # <class '__main__.Foo'>
print(Foo)
obj = Foo()
# # <__main__.Foo object at 0x000001F7B83BEBC8>
print(obj)
obj.find()
# # obj的类型是Foo ,obj的类也是Foo
print(type(obj))
# # ctrl 按住不动 点击鼠标左键
print(str)
# # 其实python在'dahai'的前面自动加了一个str类
name = str('dahai')
print(name)
# # # 面向对象的理解:str这个类实例化生成了name这个对象
# # # dahai这个值赋值给name这个变量
print(type(name))
# # # 面向对象的理解:name对象的方法
# # # 数据类型的方法
print(name.startswith('d'))
#
obj.find()
type可以查看一个数据是什么类型
type也可以查看一个对象是什么类生成的
6.2.什么是元类
源自一句话:在python中,一切皆对象,而对象都是由类实例化得到的
class Teacher:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def run(self):
print('%s在跑步'%self.name)
t=Teacher('大海',18,'man')
print(t)
print(type(t))
# 如果把Teacher当做一个对象
print(type(Teacher))
6.3.推理:内置的元类是type
# 推理
# 对象t是调用Teacher类得到的,如果说一切皆对象,那么Teacher也是一个对象,只要是对象
# 都是调用一个类实例化得到的,即Teacher=元类(...),内置的元类是type
6.4.元类,自定义的类和自定义的对象关系
# 关系:
# 1. 调用元类---->自定义的类
# 2. 调用自定义的类---->自定义的对象
7.type元类创建自定义类
class Teacher:
HP = 100
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def run(self):
print('%s在跑步'%self.name)
type(Teacher)
7.1.自定义类的三个关键组成部分:
- 类名
- 类的基类们 object
- 类的名称空间
7.2.class关键字创建自定义类的底层的工作原理,分为四步
- 先拿到类名:'Teacher'
- 再拿到类的基类们:(object,)
- 然后拿到类的名称空间???(执行类体代码,将产生的名字放到类的名称空间也就是一个字典里,补充exec)
- 调用元类实例化得到自定义的类:Teacher=type('Teacher',(object,),{...})
# 不依赖class关键字创建一个自定义类
# 1. 拿到类名
class_name = 'Teacher'
# #2. 拿到类的基类/父类们:(object,)
class_bases = (object,)
#3. 拿到类的名称空间 类的属性 ,方法 也就是类的体代码
class_body = '''
HP = 100
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def run(self):
print('%s在跑步'%self.name)
'''
class_dic = {}
# 介绍 exec 函数 可以执行字符串里面的代码
# exec('print(1)')
# exec函数的使用,我要用第一个参数代码放到字符串里面,不用第二个参数是全局的字典因为类体代码没有这样的需求
# ,用第三个参数的局部的字典
exec(class_body,{},class_dic)
Teacher=type(class_name,class_bases,class_dic)
print(Teacher)
print(Teacher.HP)
print(Teacher.run)
a=Teacher('dahai',18,'man')
a.run()
8.自定义元类
# @Author : 大海
# @File : 9.自定义元类.py
class Mymeta(type):
# 但凡继承了type的类才能称之为自定义的元类,否则就是只是一个普通的类
# type(class_name,class_bases,class_dic)
def __init__(self,class_name,class_bases,class_dic):
print(self)
print(class_name)
print(class_bases)
print(class_dic)
# 这里必须写object ,默认的type元类会做,自定义元类的要自己写
class Teacher(object,metaclass=Mymeta):
school = '图灵'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def run(self):
print('%s在跑步' % self.name)
9.自定义元类控制类的产生
现在可以控制类的产生
1.类名必须用驼峰体
2.类体必须有文档注释,且文档注释不能为空
class Mymeta(type):
# 但凡继承了type的类才能称之为自定义的元类,否则就是只是一个普通的类
# type(class_name,class_bases,class_dic)
def __init__(self,class_name,class_bases,class_dic):
# print(self)
# 这个self是Teacher这个类
# print(class_name)
# print(class_bases)
# print(class_dic)
if class_name.islower():
raise TypeError('类名必须使用驼峰体')
# print(class_dic.get('__doc__'))
doc = class_dic.get('__doc__')
if doc is None or len(doc) == 0 or len(doc.strip('\n '))==0:
raise TypeError('类的体代码必须有文档注释,且不能为空')
# 这里必须写object ,默认的type元类会做,自定义元类的要自己写
class Teacher(object,metaclass=Mymeta):
'''注释'''
school = '图灵'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def run(self):
print('%s在跑步' % self.name)
10.自定义元类来控制类的调用产生自定义对象
10.1.对象的调用要类里面的__ call __方法
class Teacher():
'''注释'''
school = '图灵'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def run(self):
print('%s在跑步' % self.name)
def __call__(self, *args, **kwargs):
print('aaaaa')
tea1 = Teacher('大海',18,'man')
print(tea1.__dict__)
tea1()
结论:
对象的调用要类里面的__ call __方法
类调用实际上是用了 元类 __ call __方法
推理:
类里面写了__ call 方法 对象才可以调用
推理:如果一切皆对象,那么Teacher也是一个对象,
该对象之所可以调用,肯定是这个对象的类Mymeta中也定义了一个函数 call __
class Mymeta(type):
def __call__(self, *args, **kwargs):
print(self)
# self就是Teacher这个对象
print(args)
print(kwargs)
# 1. 先产生一个空对象__new__可以给Teacher这个类创建一个空对象
tea_obj = self.__new__(self)
# 2. 执行__init__方法,完成对象的初始属性操作
# Teacher的空对象调用了__init__方法
self.__init__(tea_obj,*args, **kwargs)
# 3. 返回初始化好的那个对象
return tea_obj
# 这里必须写object ,默认的type元类会做,自定义元类的要自己写
class Teacher(object,metaclass=Mymeta):
'''注释'''
school = '图灵'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def run(self):
print('%s在跑步' % self.name)
def __call__(self, *args, **kwargs):
print('aaaaa')
tea1 = Teacher('大海',18,'man')
print(tea1.__dict__)
#
# # 对象的调用要类里面的__call__方法
# # 类调用实际上是用了 元类 __call__方法
# tea1()
# tea1()
11.自定义元类来控制类的调用过程产生对象
元类Mymeta实例化Teacher,调用Teacher实际上是调用元类Mymeta的__call__方法会
- 先产生一个Teacher的空对象tea_obj
- Teacher执行__ init __方法,完成对象的初始属性操作
- 返回初始化好的那个对象
推理:调用Teacher(...)就是在调用Teacher的元类Mymeta中的__call__,那么在该__call__中就需要做上述三件事
11.1.自定义元类来控制类的调用(即类的实例化过程)
11.1.1 我们把对象实例化的属性全部变成隐藏属性
class Mymeta(type):
def __call__(self, *args, **kwargs):
# print(self)
# # self就是Teacher这个对象
# print(args)
# print(kwargs)
# 1. 先产生一个空对象__new__可以给Teacher这个类创建一个空对象
tea_obj = self.__new__(self)
# 2. 执行__init__方法,完成对象的初始属性操作
self.__init__(tea_obj,*args, **kwargs)
# 在这里改
# _类名__属性
# _Teacher__name
# print(tea_obj.__dict__)
# # 类的名字
# print(self.__name__)
# k1 = []
# v1 = []
# for k,v in tea_obj.__dict__.items():
# # print(k,v)
# # print('_%s__%s'%(self.__name__,k))
# # print(v)
# k1.append('_%s__%s'%(self.__name__,k))
# v1.append(v)
# # print(k1)
# # print(v1)
# # print(dict(zip(k1, v1)))
# # print(tea_obj.__dict__)
# tea_obj.__dict__ = dict(zip(k1, v1))
# print(tea_obj.__dict__)
tea_obj.__dict__ = {'_%s__%s'%(self.__name__,k) : v for k,v in tea_obj.__dict__.items()}
# {'name': '大海', 'age': 18, 'sex': 'man'}
# {'_Teacher__name': '大海', '_Teacher__age': 18, '_Teacher__sex': 'man'}
# 3. 返回初始化好的那个对象
return tea_obj
# 这里必须写object ,默认的type元类会做,自定义元类的要自己写
class Teacher(object,metaclass=Mymeta):
'''注释'''
school = '图灵'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def run(self):
print('%s在跑步' % self.name)
def __call__(self, *args, **kwargs):
print('aaaaa')
tea1 = Teacher('大海',18,'man')
print(tea1.__dict__)
# # 对象的调用要类里面的__call__方法
# # 类调用实际上是用了 元类 __call__方法
# tea1()
# tea1()
# 类里面写了__call__方法 对象才可以调用
# 推理:如果一切皆对象,那么Teacher也是一个对象,
# 该对象之所可以调用,肯定是这个对象的类Mymeta中也定义了一个函数__call__
回顾列表和字典生成式
# @Author : 大海
# @File : 列表和字典生成式.py
# 列表生成式
# L = []
# for i in range(1,10):
# L.append(i)
# print(L)
# L1 = [i for i in range(1,10)]
# print(L1)
# L1 = [i * 3 for i in range(1,10)]
# print(L1)
# L1 = [i * 3 for i in range(1,10)if i % 2 == 0]
# print(L1)
# 字典生成式
dict1={k+'aaa':v for k,v in {'name':'大海','age':18}.items() if k == 'age'}
print(dict1)