26°

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
</span><span style="color: #0000ff;">def</span> <span style="color: #800080;">__len__</span><span style="color: #000000;">(self):
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> 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这个参数叫实例方法,
<span style="color: #008000;">#</span><span style="color: #008000;">用staticmethod完成初始化</span>
date_str = <span style="color: #800000;">"</span><span style="color: #800000;">2019-12-01</span><span style="color: #800000;">"</span><span style="color: #000000;">
new_day </span>=<span style="color: #000000;"> Date.parse_from_string(date_str)
</span><span style="color: #0000ff;">print</span>(new_day) <span style="color: #008000;">#</span><span style="color: #008000;">2019/12/1</span>

<span style="color: #008000;">#</span><span style="color: #008000;"> 用classmethod完成初始化</span>
new_day =<span style="color: #000000;"> Date.from_string(date_str)
</span><span style="color: #0000ff;">print</span>(new_day) <span style="color: #008000;">#</span><span style="color: #008000;">2019/12/1</span>

<span style="color: #0000ff;">print</span>(Date.valid_str(<span style="color: #800000;">"</span><span style="color: #800000;">2019-12-01</span><span style="color: #800000;">"</span>)) <span style="color: #008000;">#</span><span style="color: #008000;">True</span></pre> 

1.7、数据封装和私有属性

导入的Date是上面写的类:

from chapter04.class_method import Date
class User:
    def __init__(self,birthday):
        self.__birthday = birthday#在属性前面加上双下划线,
        # 就变成了私有属性,外面实例化对象不能直接访问
<span style="color: #0000ff;">def</span><span style="color: #000000;"> get_age(self):
    </span><span style="color: #800000;">"""</span><span style="color: #800000;">
    希望用户看不见出生日期(我们提供计算年龄的接口)在这里只能用公共方法调用,子类都不能使用私有属性
    :return:返回用户年龄,
    </span><span style="color: #800000;">"""</span>
    <span style="color: #0000ff;">return</span> 2019 - self.<span style="color: #800080;">__birthday</span><span style="color: #000000;">.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)

</span><span style="color: #0000ff;">def</span><span style="color: #000000;"> perform_create(self, serializer):
    serializer.save()

</span><span style="color: #0000ff;">def</span><span style="color: #000000;"> get_success_headers(self, data):
    </span><span style="color: #0000ff;">try</span><span style="color: #000000;">:
        </span><span style="color: #0000ff;">return</span> {<span style="color: #800000;">'</span><span style="color: #800000;">Location</span><span style="color: #800000;">'</span><span style="color: #000000;">: str(data[api_settings.URL_FIELD_NAME])}
    </span><span style="color: #0000ff;">except</span><span style="color: #000000;"> (TypeError, KeyError):
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> {}

class ListModelMixin: """ List a queryset. """ def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset())

    page </span>=<span style="color: #000000;"> self.paginate_queryset(queryset)
    </span><span style="color: #0000ff;">if</span> page <span style="color: #0000ff;">is</span> <span style="color: #0000ff;">not</span><span style="color: #000000;"> None:
        serializer </span>= self.get_serializer(page, many=<span style="color: #000000;">True)
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> self.get_paginated_response(serializer.data)

    serializer </span>= self.get_serializer(queryset, many=<span style="color: #000000;">True)
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> 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)

    </span><span style="color: #0000ff;">if</span> getattr(instance, <span style="color: #800000;">'</span><span style="color: #800000;">_prefetched_objects_cache</span><span style="color: #800000;">'</span><span style="color: #000000;">, None):
        </span><span style="color: #008000;">#</span><span style="color: #008000;"> If 'prefetch_related' has been applied to a queryset, we need to</span>
        <span style="color: #008000;">#</span><span style="color: #008000;"> forcibly invalidate the prefetch cache on the instance.</span>
        instance._prefetched_objects_cache =<span style="color: #000000;"> {}

    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> Response(serializer.data)

</span><span style="color: #0000ff;">def</span><span style="color: #000000;"> perform_update(self, serializer):
    serializer.save()

</span><span style="color: #0000ff;">def</span> partial_update(self, request, *args, **<span style="color: #000000;">kwargs):
    kwargs[</span><span style="color: #800000;">'</span><span style="color: #800000;">partial</span><span style="color: #800000;">'</span>] =<span style="color: #000000;"> True
    </span><span style="color: #0000ff;">return</span> self.update(request, *args, **<span style="color: #000000;">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)

</span><span style="color: #0000ff;">def</span><span style="color: #000000;"> perform_destroy(self, instance):
    instance.delete()</span></pre> 

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

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 """

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

本文转载自博客园,原文链接:https://www.cnblogs.com/lishuntao/p/11967030.html

全部评论: 0

    我有话说: