面向对象 多继承的super type和object的关系 抽象类和接口类 动态创建类 元类 属性拦截器__getattrbute__ 描述符 动态给类或对象绑定方法属性

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的类都是新式类,遵循广度优先的查询顺序。
想要查看一个类的继承链条,可以使用两个属性(只有类对象可以使用,实例对象没有这两个属性):basesbase, 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(' 
')) == 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()中的一个,这也被称为描述符协议。
描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

  1. 数据描述符: 至少实现了__get__()和__set__()
  2. 非数据描述符:没有实现__set__()

关于描述符的使用,有如下几点需要注意:

  1. 描述符本身应该定义成新式类,被代理的类也应该是新式类
  2. 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
  3. 要严格遵循该优先级,优先级由高到底分别是
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