面向对象
多继承的super
class A(object):
def func(self):
print('A')
class B(A):
def func(self):
# 在B里面调用super,寻找的是mro顺序中的B的后一个位置
super(B, self).func()
print('B')
class C(A):
def func(self):
super(C, self).func()
print('C')
class D(B,C):
def func(self):
super(D, self).func()
print('D')
d = D()
d.func()
结果:
A
C
B
D
从这个结果上来看,super并不是单纯地去找父类,而是按照D.mro() 的顺序去找。每调用一次super(),那么mro列表的指针就往后移动一个。
python3的类都是新式类,遵循广度优先的查询顺序。
想要查看一个类的继承链条,可以使用两个属性(只有类对象可以使用,实例对象没有这两个属性):bases 和 base, bases 永远看到是自己的父类,不包括父类的父类。要想看到方法的寻找顺序,就用mro(), 这个方法会把包含父类的父类的方法也打印出来
type和object的关系
type是创建类对象的类,object是继承的基类,一个是创建的概念,另外一个是继承的概念,所以两者没有本质的区别。值得注意的是python中的类
一般都是大写字母开头,但是基类object却是一个例外。type和object的关系其实是一个环状,type继承自object, object 又是type创建的。
抽象类和接口类
首先明确一点,抽象类和接口类都是为了约束的。在python中,接口类一般使用多继承,抽象类一般使用单继承。接口类一般不实现里面的方法逻辑,但是抽象类可以去实现代码逻辑。
接口类
from abc import abstractmethod,ABCMeta
class Swim_Animal(metaclass=ABCMeta):
@abstractmethod
def swim(self):pass
class Walk_Animal(metaclass=ABCMeta):
@abstractmethod
def walk(self):pass
class Fly_Animal(metaclass=ABCMeta):
@abstractmethod
def fly(self):pass
class Tiger(Walk_Animal,Swim_Animal):
def walk(self):
pass
def swim(self):
pass
class OldYing(Fly_Animal,Walk_Animal):pass
class Swan(Swim_Animal,Walk_Animal,Fly_Animal):pass
上述代码遵循了接口隔离原则:使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
抽象类:
import abc #利用abc模块实现抽象类
class All_file(metaclass=abc.ABCMeta):
all_type='file'
@abc.abstractmethod #定义抽象方法,无需实现功能
def read(self):
'子类必须定义读功能'
pass
@abc.abstractmethod #定义抽象方法,无需实现功能
def write(self):
'子类必须定义写功能'
pass
# class Txt(All_file):
# pass
#
# t1=Txt() #报错,子类没有定义抽象方法
class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('文本数据的读取方法')
def write(self):
print('文本数据的读取方法')
class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('硬盘数据的读取方法')
def write(self):
print('硬盘数据的读取方法')
class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('进程数据的读取方法')
def write(self):
print('进程数据的读取方法')
t = Txt()
s =Sata()
p = Process()
t.read()
s.read()
p.read()
上述代码遵循了依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程
接口类和抽象类的区别:抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
动态创建类
我们知道,一个类就是由类名,父类和类的属性(包括数据属性和方法属性)组成.
def eat(self):
print('eating...')
People = type('People', (object,), {'eat':eat})
# 等价于下面代码
# class People:
# def eat(self):
# print('eating...')
# print(People.__name__)
元类
在python中一切皆对象,那么类也是对象,没错,默认所有的类都是type创建的,默认所有的类继承object类,这里的创建和继承是两个概念。
class关键字在帮我们创建类时,必然帮我们调用了元类OldboyTeacher=type(...),那调用type时传入的参数是什么呢?必然是类的关键组成部分,一个类有三大组成部分,分别是
1、类名class_name='xxx'
2、基类们class_bases=(object,)
3、类的名称空间class_dic,类的名称空间是执行类体代码而得到的
调用type时会依次传入以上三个参数
在普通类的 new 方法里面, 调用super是这样的,super().new(cls), 这里不能传参数, 因为捅到底调用的,是object的__new__, 这个不能接受别的参数,但是如果是元类的话必须去传args kwargs 参数, 需要这样调用,super().new(cls, *args, **kwargs). object需要传cls参数,为啥super在__init__等需要传self参数的时候不需要传self参数呢呢?因为看源码发现object 的 new 是一个staticmethod.
exec:常用于动态创建三个参数
#参数一:包含一系列python代码的字符串
#参数二:全局作用域(字典形式),如果不指定,默认为globals()
#参数三:局部作用域(字典形式),如果不指定,默认为locals()
#可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
g={
'x':1,
'y':2
}
l={}
exec('''
global x,z
x=100
z=200
m=300
''',g,l)
print(g) #{'x': 100, 'y': 2,'z':200,......}
print(l) #{'m': 300}
自定义元类控制类的创建
# 这样定义相当于什么也没定制
# class MyType(type):
# def __init__(self, class_name, class_bases, class_dict):
# super(MyType, self).__init__(class_name, class_bases, class_dict)
#
# class People(object, metaclass=MyType):
# def __init__(self, name='jack'):
# self.name = name
#
# def eat(self):
# print('%s is eating...'%self.name)
class MyType(type):
def __init__(self, class_name, class_bases, class_dict):
if '__doc__' not in class_dict or len(class_dict['__doc__'].strip(' \n')) == 0:
raise TypeError('类中必须有文档注释,并且文档注释不能为空')
super(MyType, self).__init__(class_name, class_bases, class_dict)
class People(object, metaclass=MyType): # class 定义一个类,本质就是生成一个对象,就是MyType("People", (object,), {...}),调用的是MyType的__init__
def __init__(self, name='jack'):
self.name = name
def eat(self):
print('%s is eating...'%self.name)
class Mymeta(type):
def __init__(self, class_name, class_bases, class_dic):
super(Mymeta, self).__init__(class_name, class_bases, class_dic) # 重用父类的功能
def __new__(cls, *args, **kwargs):
# <class '__main__.Mymeta'> ('People', (<class 'object'>,),
# {'__module__': '__main__', '__qualname__': 'People', '__init__': <function People.__init__ at 0x1041a87b8>})
# 给类添加新的属性可以在这个里面做
print(cls, args, kwargs)
return super().__new__(cls, *args, **kwargs)
class People(object, metaclass=Mymeta):
def __init__(self):
self.age = 12
自定义元类控制类的调用
重写__call__方法
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __call__(self, *args, **kwargs):
print(self) #<class '__main__.People'>
print(args) #('xxx', 18)
print(kwargs) #{}
return 123
class People(object,metaclass=Mymeta):
def __init__(self,name,age):
self.name=name
self.age=age
# 0. 暂时摒弃之前People() 实例化调用__init__方法的理念
# 1. People这个对象加括号会调用Mymeta的__call__
# 2. __call__ 里返回什么 t1就是什么
t1=People('xxx',18)
print(t1) #123
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __call__(self, *args, **kwargs):
# self 必须传,因为 __new__ 没有被@claassmethod包裹,类去调用就是函数,需要手动传类这个参数
obj = self.__new__(self, *args, **kwargs)
self.__init__(obj, *args, **kwargs)
return obj
class People(object,metaclass=Mymeta):
def __init__(self,name,age):
self.name=name
self.age=age
t1=People('xxx',18)
print(t1) # <tests.People object at 0x00000000025A0CF8>
可以在元类的__call__ 里面做自定制,上面的代码相当于内部源码(做了最基本的三件事),所以没做任何自定制。
有了元类的属性查找
class Mymeta(type):
def eat(self):
print('Mymeta eat...')
class Bar(object):
pass
# def eat(self):
# print('Bar eat...')
class Foo(Bar):
pass
# def eat(self):
# print('Foo eat...')
class People(Foo,metaclass=Mymeta):
def __init__(self,name,age):
self.name=name
self.age=age
# def eat(self):
# print('People eat...')
People.eat()
# 不能使用p.eat 去调用,因为People才是Mymeta创建的对象
# 寻找顺序先对象层:People->Foo->Bar->object,然后元类层:Mymeta->type
正因为这个顺序,所以在元类一般不需要定义__new__, 因为怎么也走不到我们自定义元类的__new__(前面有object的__new__ 这道屏障, 元类的__new__ 可以在生成类的时候做定制化操作)。但我们还是推荐在__call__中使用self.new(self)去创造空对象,因为这种方式会检索三个类People->Foo->Bar,而object.__new__则是直接跨过了他们三个.
基于元类实现单例模式
import settings
class Mymeta(type):
def __init__(self,name,bases,dic): #定义类Mysql时就触发
# 事先先从配置文件中取配置来造一个Mysql的实例出来
self.__instance = object.__new__(self) # 产生对象
self.__init__(self.__instance, settings.HOST, settings.PORT) # 初始化对象
super().__init__(name,bases,dic)
def __call__(self, *args, **kwargs): #Mysql(...)时触发
if args or kwargs: # args或kwargs内有值
return super().__call__(*args, **kwargs)
return self.__instance
class Mysql(metaclass=Mymeta):
def __init__(self,host,port):
self.host=host
self.port=port
obj1=Mysql() # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址
obj2=Mysql()
obj3=Mysql()
print(obj1 is obj2 is obj3)
obj4=Mysql('1.1.1.4',3307)
补充装饰器实现
import settings
def single(cls):
__instance = cls(settings.HOST, settings.PORT)
def wrapper(*args, **kwargs):
if args or kwargs:
return cls(*args, **kwargs)
return __instance
return wrapper
@single
class Mysql:
def __init__(self,host,port):
self.host=host
self.port=port
obj1=Mysql() # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址
obj2=Mysql()
obj3=Mysql()
print(obj1 is obj2 is obj3)
obj4=Mysql('1.1.1.4',3307)
属性拦截器__getattrbute__
当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError.
class Foo:
def __init__(self,x):
self.x=x
def __getattr__(self, item):
print('执行的是我')
# return self.__dict__[item]
def __getattribute__(self, item):
print('不管属性是否存在,我都会执行')
raise AttributeError('哈哈')
f1=Foo(10)
# f1.x
f1.xxxxxx
描述符
描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),set(),delete()中的一个,这也被称为描述符协议。
描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
- 数据描述符: 至少实现了__get__()和__set__()
- 非数据描述符:没有实现__set__()
关于描述符的使用,有如下几点需要注意:
- 描述符本身应该定义成新式类,被代理的类也应该是新式类
- 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
- 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
有个认识
#描述符Str
class Str:
def __get__(self, instance, owner):
print('Str调用')
def __set__(self, instance, value):
print('Str设置...')
def __delete__(self, instance):
print('Str删除...')
class People:
name=Str()
def __init__(self,name): #name被Str类代理
self.name=name
p1=People('xxx')
#描述符Str的使用
p1.name
p1.name='ooo'
del p1.name
# 我靠,对象的__dict__ 里面竟然没有我设定的name
print(p1.__dict__)
# People 竟然有name,而且这里的name是一个对象
print(People.__dict__)
name对象放在People对象内存空间里,只放着一份,对象对name属性的操作就是对这个对象的操作,这就是代理的意思了
应用
限定对象属性的类型
#描述符Str
class Str:
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __get__(self, instance, owner):
# instance 就是People实例,owner 就是People 类对象
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
raise TypeError('类型必须是%s'%self.expected_type)
instance.__dict__[self.name] = value
def __delete__(self, instance):
print('Str删除...')
instance.__dict__.pop(self.name)
class People:
# 会调用Str的 __init__ 方法
name=Str('name', str)
def __init__(self,name): #name被Str类代理
self.name = name
p1 = People('jack')
print(p1.name)
如果我们的类有很多属性,那么这种方式仍然采用在定义一堆类属性的方式去实现,可以考虑在装饰器里循环添加的方法
class Str:
def __init__(self, instance_name, expected_type):
self.instance_name = instance_name
self.expected_type = expected_type
def __get__(self, instance, owner):
return instance.__dict__[self.instance_name]
def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
raise TypeError('类型必须是%s'%self.expected_type)
instance.__dict__[self.instance_name] = value
def __delete__(self, instance):
print('Str删除...')
instance.__dict__.pop(self.instance_name)
def constraint_type(**kwargs):
def wrapper(cls):
for name, expected_type in kwargs.items():
setattr(cls, name, Str(name, expected_type))
return cls
return wrapper
@constraint_type(name=str)
class People:
def __init__(self,name_value):
self.name = name_value
p1 = People('jack')
print(p1.name)
自定义property
class Myproperty(object):
__instance = None
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if not self.__instance:
self.__instance = self.func(instance)
return self.__instance
class Rectangle:
def __init__(self,width, height):
self.width = width
self.height = height
@Myproperty # area = Myproperty(area)
def area(self):
print(1)
return self.width * self.height
r = Rectangle(12, 23)
# area 只是执行一次,所谓的只执行一次(或者说单例模式),套路一般都是第一次把结果存在某个地方,下次来的时候去这个地方拿
print(r.area)
print(r.area)
动态给类或对象绑定方法属性
class People:
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print('%s is eating...'%self.name)
# setattr(People, 'eat', eat)
# p = People('jack', 12)
# p.eat()
# p = People('jack', 12)
# p.eat = eat
# p.eat() # TypeError: eat() missing 1 required positional argument: 'self'
import types
p = People('jack', 12)
# 既然eat不会帮我们自动传p对象,那么通过types.MethodType(eat, p) 就能让前面的p.eat调用的时候自动把p传进去
# xxx = types.MethodType(eat, p) xxx() 也可以进行调用
p.eat = types.MethodType(eat, p)
p.eat()
# 从面向对象的 是 函数 还是方法的角度就能理解上面的过程了
class C:
def f(self):
pass
print(C.__qualname__) # C
print(C.__name__) # C
print(C.f.__name__) # f
print(C.f.__qualname__) # C.f