• 面向对象


    多继承的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的类都是新式类,遵循广度优先的查询顺序。
    想要查看一个类的继承链条,可以使用两个属性(只有类对象可以使用,实例对象没有这两个属性):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
    
  • 相关阅读:
    2017年下半年软考合格标准出炉啦
    练网软考知识点整理-项目配置管理流程
    简练网软考知识点整理-项目招标投标中的法律责任
    简练网软考知识点整理-软件维护类型
    简练网软考知识点整理-软件质量保证及质量评价
    简练网软考知识点整理-项目配置管理配置审计
    简练网软考知识点整理-项目问题日志(Issue Log)
    简练网软考知识点整理-风险应对措施(应急计划、弹回计划和权变措施)
    简练网软考知识点整理-项目组织分解结构OBS
    简练网软考知识点整理-项目配置管理计划
  • 原文地址:https://www.cnblogs.com/longyunfeigu/p/9566019.html
Copyright © 2020-2023  润新知