• 03-深入类和对象


    一、深入类和对象

    1.1、鸭子类型和多态

    维基百科中的解释为:

      鸭子类型(英语:duck typing)在程序设计中是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。这个概念的名字来源于由詹姆斯·惠特科姆·莱利提出的鸭子测试,“鸭子测试”可以这样表述:

      “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型。

    class Cat():
        def say(self):
            print("I am a cat")
    
    class Dog():
        def say(self):
            print("I am a dog")
    
    class Duck():
        def say(self):
            print("I am a duck")
    
    animal_list = [Cat,Dog,Duck]
    for animal in animal_list:
        animal().say()#实例化对象,在调用say方法  三个类实现同一个方法名,这就是多态。然后可以将这些类归为一种类型(鸭子类型)
                      #python中的魔法函数充分也利用了鸭子类型的特性,可以在任一类中定义
    name_list = ["list1","list2"]
    name_list1 = ["love","python"]
    name_tuple = (3,4)
    name_set = set()
    name_set.add(5)
    name_set.add(6)
    name_list.extend(name_set)   #参数name_set:['list1', 'list2', 5, 6]参数name_tuple:['list1', 'list2', 3, 4] 
    print(name_list)              # 参数:name_list1:['list1', 'list2', 'love', 'python']
    """
    这里说的是只要传入的参数是一个可迭代的类型就可以,
    就连我们自定义的类将类的魔法函数__getitem__(返回 )、__iter__就可以变成可迭代的,都可以传入
     def extend(self, *args, **kwargs): # real signature unknown
            Extend list by appending elements from the iterable. 
            pass
    """
    #首先这三个类里面都包含了这个say()方法,如果在JAVA里边,要实现多态的话,需要继承父类在覆盖父类的方法实现多态。
    # 例如:一般情况先定义一个父类Animal,然后这个Animal有一个say()方法。
    # 然后在写其他类例如上面的Cat类,Cat类继承Animal类,然后重写say()方法。
    # 然后指定类型实例化这个Cat对象,在python中不需要指定类型,在JAVA中(静态语言)必须指定类型,
    #这是动态语言和静态语言最大的区别。在python中都要做的一件事就是每个对象下都要写这个say()方法

    1.2、抽象基类(abc)

      python里边的抽象基类,是不能够实例化的。python是动态语言,动态语言是没有变量的类型的。在python中变量只是一个符号而已,这个符号可以指向任何类型的对象。动态语言缺少编译时检查错误的环境,在python中编写代码是很难发现错误的,只有要运行解释器才能找到错误。这也是动态语言共有的一个缺陷。python信奉的是鸭子类型,鸭子类型贯穿于整个面向对象之中。抽象基类是什么意思?在这个基础的类当中,设定好一些方法,然后所有的继承这个基类的类,都必须覆盖这个抽象基类里面的方法。抽象基类是无法实例化的。

    ##################去检查某个类是否有某种方法#############################
    class Students(object):
        def __init__(self,student_list):
            self.student = student_list
    
        def __len__(self):
            return len(self.student)
    students = Students(["lishuntao","test","python"])
    # print(hasattr(students,"__len__"))#True
    # print(hasattr(students,"__getitem__"))#False
    
    ##############################判定某个对象的类型#####################################
    from collections.abc import Sized
    print(isinstance(students,Sized))#True
    
    ######################利用抽象基类实现接口的 强制规定##########################
    #强制某些子类必须实现某些方法
    #实现了一个web框架,集成cache(redis,cache,memorychache)
    #需要设计一个抽象基类,指定子类必须实现某些方法
    #如何去模拟一个抽象基类呢?
    class  CacheBase():
        def get(self,key):
            raise NotImplementedError
        def set(self,key,value):
            raise NotImplementedError
    #用户在实现这个抽象基类的子类时候,必须实现这里面的两个方法
    class RedisCache(CacheBase):
        pass
    redis = RedisCache()
    #redis.get("key")#抛出异常raise NotImplementedError NotImplementedError
    
    #但这样做不好,我们需要刚初始化的时候就抛出异常,接下来就换成abc实现个人基类
    import abc
    
    class Cache1Base(metaclass=abc.ABCMeta):
        @abc.abstractmethod
        def get(self,key):
            pass
        @abc.abstractmethod
        def set(self,key,value):
            pass
    
    class RedisCache1(Cache1Base):
        pass
    
    redis_cache1 = RedisCache1() #TypeError: Can't instantiate abstract class RedisCache1 with abstract methods get, set
    #利用抽象基类直接初始化抛出异常
    
    #在python当中已经实现了一些通用的抽象基类,放在
    from collections.abc import *

      抽象基类不是用来继承的,它只是利用抽象基类来理解继承之间的关系,以及接口的定义,我们去使用的时候一定要用我们的鸭子类型,如果一定要用接口的话,那么推荐使用mixin多继承的方式去实现它。抽象基类使用的时候设计过度,反而不容易理解它。

    1.3、isinstance和type的区别

    class A:
        pass
    class B(A):
        pass
    
    b = B()
    print(isinstance(b,B)) #True
    print(isinstance(b,A)) #True
    
    print(type(b) is B) #True   is与==的区别,==判断值是否相等,is判断是不是同一个对象(id(b)地址是否一样)
    print(type(b),A) #False  
    ###########判断类型:为什么更推荐用isinstance而不是type?##############
    #因为如果判断某个对象的类型的话,用isinstance会根据树的形状去搜索,从叶子搜索到跟就可以判断是否是相同类型,
    #就算是不同对象可能是相同类型,然而type是同种类型,但不同对象。

    1.4、类变量和实例变量

    class A:
        a = 1 #a是类变量
        def __init__(self,x,y):#self是类的实例 x与y已经绑定到实例上的属性上了
            self.x = x
            self.y = y
    
    num = A(2,3)
    # A.a = 11   #如果修改类属性,那么实例的值也会跟着变
    # num.a = 100  #如果修改实例属性,那么类属性的值不变,
    # 会在对象中新建一个实例属性的值,寻找的时候直接对象属性中寻找。
    print(num.x,num.y,num.a) #2 3 1  为什么实例num能够找到A的类属性呢,
    # 首先实例num先在实例属性种寻找,如果没有找到的话就会向上寻找,找到类属性
    print(A.a) # 1 类属性
    print(A.x)#AttributeError: type object 'A' has no attribute 'x'
    #类找实例属性找不到是因为类首先到自己的属性中找,如果没有找到的话,就不会向下寻找

    1.5、类和实例属性的查找顺序----mro查找

    类查找属性的查找顺序有深度优先查找广度优先查找

    广度优先查找:

    #python3以后称为新式类,全部都继承object
    class D:
        pass
    class C(D):
        pass
    class B(D):
        pass
    class A(B,C):
        pass
    print(A.__mro__) #(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
    #__mro__魔法方法直接显示出类查找属性的顺序

    深度优先查找:

    #python3以后称为新式类,全部都继承object
    class E:
        pass
    class D:
        pass
    class C(E):
        pass
    class B(D):
        pass
    class A(B,C):
        pass
    print(A.__mro__)
    #(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)

      但在python3中为了避免深度优先算法与广度优先算法混乱,出现了C3算法避免了两种算法出现的问题,例如菱形搜索应用深度优先算法,从AB再到D找到方法,可能C中重写了D的方法,因此深度优先算法不能解决菱形搜索的情况,然而C3算法解决了以上出现的两种情况。

    1.6、类方法、静态方法和实例方法

    class Date:
        def __init__(self,year,month,day):
            self.year = year
            self.month = month
            self.day = day
        #静态方法的缺点就是硬编码,如果换类名又要重新改返回的类名
        @staticmethod
        def parse_from_string(date_str):
            year,month,day = tuple(date_str.split("-"))
            return Date(int(year),int(month),int(day))
        #为啥不用classmethod替换staticmethod呢?
        #检查时间格式是否正确,不需要对象返回回来,因此这个时候它就有用了,而其余都是要将对象返回回来
        @staticmethod
        def valid_str(date_str):
            year, month, day = tuple(date_str.split("-"))
            if int(year)>0 and (int(month)>0 and int(month)<=12) and int(day)<=31:
                return True
            else:
                return False
        #类方法就解决掉刚才的硬编码问题
        @classmethod
        def from_string(cls,date_str):
            year, month, day = tuple(date_str.split("-"))
            return cls(int(year), int(month), int(day))
        def __str__(self):
            return "{year}/{month}/{day}".format(year=self.year,month=self.month,day=self.day)
    

    if __name__ == '__main__': days = Date(2019,12,1) print(days) #2019/12/1 #方法中传入self这个参数叫实例方法, #用staticmethod完成初始化 date_str = "2019-12-01" new_day = Date.parse_from_string(date_str) print(new_day) #2019/12/1 # 用classmethod完成初始化 new_day = Date.from_string(date_str) print(new_day) #2019/12/1 print(Date.valid_str("2019-12-01")) #True

    1.7、数据封装和私有属性

    导入的Date是上面写的类:

    from chapter04.class_method import Date
    class User:
        def __init__(self,birthday):
            self.__birthday = birthday#在属性前面加上双下划线,
            # 就变成了私有属性,外面实例化对象不能直接访问
    
        def get_age(self):
            """
            希望用户看不见出生日期(我们提供计算年龄的接口)在这里只能用公共方法调用,子类都不能使用私有属性
            :return:返回用户年龄,
            """
            return 2019 - self.__birthday.year
    
    if __name__ == '__main__':
        user = User(Date(1999,9,9))
        print(user._User__birthday) #1999/9/9  如果想要访问那么对象名._classname__attr就可以获取python的私有属性
        print(user.get_age())#20
        #从语言的角度来讲,没有绝对的私有属性的安全性的,都是可以突破的,

    1.8、python对象的自省机制

    自省:就是通过一定的机制查询到对象的内部结构。

    from chapter04.class_method import Date
    class Person:
        name = "user"
    
    
    class Student(Person):
        def __init__(self,school_name):
            self.school_name = school_name
    
    if __name__ == '__main__':
        student = Student("清华大学")#实例
        #通过__dict__查询属性
        print(student.__dict__) #{'school_name': '清华大学'}
        #上面打印的是实例的属性,为啥name属性没有进入__dict__呢?因为name属于Person类,
        # 实例查询到name的值,但并不是说name属性属于实例
        print(student.name) #user
        print(Person.__dict__)#结果如下:类的__dict__比对象也就是实例更加丰富
        #{'__module__': '__main__', 'name': 'user', '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
        #给实例添加属性
        student.__dict__["school_addr"] = "四川市"
        print(student.school_addr)#四川市
        #会列出我们对象的所有属性
        print(dir(student))
        #['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'school_addr', 'school_name']
        list1 = [1,2,3,4]
        print(dir(list1))#列表不可以用__dict__,列表没有这个属性
        #['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

    1.9、super真的是调用父类吗?

    class A:
        def __init__(self):
            print("A")
    
    class B(A):
        def __init__(self):
            print("B")
            super(B,self).__init__() # 想让他初始化B之后,运行A的初始化,这是python2的用法
            super().__init__() #python3的用法
    
    #既然重写了A的构造函数,为什么还要调用super?
    #super函数到底执行顺序是什么?(遵循__mro__算法逻辑顺序)
    from threading import Thread
    
    
    class MyThread(Thread):
        def __init__(self,name,user):
            self.user = user
            # self.name = name  #实际上父类Thread的参数有了name这个参数,我们直接可以调用父类
            super().__init__(name=name)#这样我们就不用写具体的name相关的逻辑了
    
    
    
    if __name__ == '__main__':
        b = B() #运行结果:B,A,A

    2.0、mixin继承案例----django-rest-framework

    mixin模式特点:

      1、Mixin类功能单一

      2、不和基类关联(mixin只是定义一个方法(接口)),可以和任一基类组合、基类可以不和mixin组合就能初始化成功

      3、在mixin中不要使用super的用法

      4、设置mixin的时候尽量以Mixin结尾,这样别人就可以读懂代码(规范)

    接下来展示的代码就是Django-REST-FrameworkMixins的设计模式(Mixin源代码):

    """
    Basic building blocks for generic class based views.
    
    We don't bind behaviour to http method handlers yet,
    which allows mixin classes to be composed in interesting ways.
    """
    from rest_framework import status
    from rest_framework.response import Response
    from rest_framework.settings import api_settings
    
    
    class CreateModelMixin:
        """
        Create a model instance.
        """
        def create(self, request, *args, **kwargs):
            serializer = self.get_serializer(data=request.data)
            serializer.is_valid(raise_exception=True)
            self.perform_create(serializer)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
    
        def perform_create(self, serializer):
            serializer.save()
    
        def get_success_headers(self, data):
            try:
                return {'Location': str(data[api_settings.URL_FIELD_NAME])}
            except (TypeError, KeyError):
                return {}
    
    
    class ListModelMixin:
        """
        List a queryset.
        """
        def list(self, request, *args, **kwargs):
            queryset = self.filter_queryset(self.get_queryset())
    
            page = self.paginate_queryset(queryset)
            if page is not None:
                serializer = self.get_serializer(page, many=True)
                return self.get_paginated_response(serializer.data)
    
            serializer = self.get_serializer(queryset, many=True)
            return Response(serializer.data)
    
    
    class RetrieveModelMixin:
        """
        Retrieve a model instance.
        """
        def retrieve(self, request, *args, **kwargs):
            instance = self.get_object()
            serializer = self.get_serializer(instance)
            return Response(serializer.data)
    
    
    class UpdateModelMixin:
        """
        Update a model instance.
        """
        def update(self, request, *args, **kwargs):
            partial = kwargs.pop('partial', False)
            instance = self.get_object()
            serializer = self.get_serializer(instance, data=request.data, partial=partial)
            serializer.is_valid(raise_exception=True)
            self.perform_update(serializer)
    
            if getattr(instance, '_prefetched_objects_cache', None):
                # If 'prefetch_related' has been applied to a queryset, we need to
                # forcibly invalidate the prefetch cache on the instance.
                instance._prefetched_objects_cache = {}
    
            return Response(serializer.data)
    
        def perform_update(self, serializer):
            serializer.save()
    
        def partial_update(self, request, *args, **kwargs):
            kwargs['partial'] = True
            return self.update(request, *args, **kwargs)
    
    
    class DestroyModelMixin:
        """
        Destroy a model instance.
        """
        def destroy(self, request, *args, **kwargs):
            instance = self.get_object()
            self.perform_destroy(instance)
            return Response(status=status.HTTP_204_NO_CONTENT)
    
        def perform_destroy(self, instance):
            instance.delete()

    看这些源代码是不是和上面的规范要求都是符合的呢?好接下来看我项目中的实战代码:

    class GoodsListViewSet(mixins.ListModelMixin,mixins.RetrieveModelMixin,viewsets.GenericViewSet):
        """
        商品列表页 分页 搜索 过滤 排序
        """
        queryset = Goods.objects.all()
        serializer_class = GoodsSerializer
        pagination_class = GoodsSetPagination
        filter_backends = [DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter]
        #这是精确搜索过滤,我们需要的是模糊搜索
        # filterset_fields = ['name', 'shop_price']
        filter_class = GoodsFilter
        search_fields = ("name","goods_brief","goods_desc")
        ordering_fields = ("shop_price","sold_num","add_time")

    基类和Mixin组合成新类,实现想要的功能。

    2.1、python中的with语句

    #try  except  else  finally
    def exe_try():
        try:
            print("coding is started")
            raise KeyError
        except KeyError as e:
            print("key error")
            return 2
        else: #要程序没有出异常就会运行else
            print("other coding")
            return 3
        finally:
            print("finally")
            return 4
    #python会自动识别自己的协议
    #上下文管理器协议(with调用)(简化try-finally用法的)
    class Sample():
        def __enter__(self):
            #获取资源
            print("enter")
            return self
        def __exit__(self, exc_type, exc_val, exc_tb):
            #释放资源
            print("exit")
        def do_something(self):
            print("do something")
    
    with Sample() as sample:
        sample.do_something()
        """
        当我们离开with语句的时候,就会调用__exit__方法
        运行结果:
        enter
        do something
        exit
        """
    
    # if __name__ == '__main__':
    #     result = exe_try()
    #     print(result)
        """
        运行结果:为什么会出现这样的结果?程序运行,直接抛出异常KeyError先返回2进入栈底(栈的知识),
        然后执行finally中的返回4,4进入栈顶,栈的规则是后进先出,则返回的是4
        coding is started
        key error
        finally
        4
        """

    2.2、contextlib简化上下文管理器

    import contextlib
    
    @contextlib.contextmanager#装饰器将函数变为上下文管理器(上下文管理器都可以用with用)
    def file_open(filename):
        print("file open") #yield之前的相当于__enter__函数的操作
        yield {}
        print("file end")#之后相当于__exit__函数的操作
    
    with file_open("test.txt") as fp:
        print("file open processing")
        """
        运行结果:
        file open
        file open processing
        file end
        """

    这样就大大简化我们的上下文管理器啦。

  • 相关阅读:
    python 学习——sqlalchemy 模块
    python学习——数据库基本知识mysql
    算法设计22——并行算法2 实际应用中的优化
    Perl 学习
    python学习——装饰器、生成器、迭代器
    算法设计19—— 全对最短路径 Floyd算法
    asp.net Core 使用过滤器判断请求客户端是否为移动端,并实现PC端和移动端请求映射和自动跳转
    在windows平台使用Apache James搭建邮件服务器以及使用C#向外网发送邮件
    asp.net core3.1策略授权问题
    Unity调用安卓中的方法遇到的问题
  • 原文地址:https://www.cnblogs.com/lishuntao/p/11967030.html
Copyright © 2020-2023  润新知