【8.0】DRF之DRF视图扩展类

发布时间 2023-07-31 12:29:36作者: Chimengmeng

【一】5个视图扩展类

  • GenericAPIView + 5个视图扩展类 + 序列化类 + Response写接口

【1】5个视图扩展类对应五个方法

  • 查询所有数据
  • 新增一条数据
  • 查询单条数据
  • 修改一条数据
  • 删除一条数据

【2】写5个类的好处就是

  • 解耦合,提高代码的可扩展性

  • 这五个类不叫视图类,叫视图扩展类,需要配合GenericAPIView一起用

  • 每个类有一个方法,以后想写哪个接口,就继承哪个类即可

【3】5个视图扩展类

  • ListModelMixin:查询所有数据(list方法)

    class ListModelMixin:
        def list(self, request, *args, **kwargs):
            qs = self.get_queryset()
            ser = self.get_serializer(qs, many=True)
            return Response({'code': 100, 'msg': '成功', 'results': ser.data})
    
  • CreateModelMixin:新增一条数据(create方法)

    class CreateModelMixin:
        def create(self, request, *args, **kwargs):
            ser = self.get_serializer(data=request.data)
            if ser.is_valid():
                ser.save()
                return Response({'code': 100, 'msg': '成功'})
            else:
                return Response({'code': 100, 'msg': ser.errors})
    
  • RetrieveModelMixin:查询单条数据(retrieve方法)

    class RetrieveModelMixin:
        def retrieve(self, request, *args, **kwargs):
            book = self.get_object()
            ser = self.get_serializer(book)
            return Response({'code': 100, 'msg': '成功', 'results': ser.data})
    
  • DestroyModelMixin:删除一条数据(destroy方法)

    class DestroyModelMixin:
        def destroy(self, request, *args, **kwargs):
            self.get_object().delete()
            return Response({'code': 100, 'msg': '删除成功'})
    
  • UpdateModelMixin:修改一条数据(update方法)

    class UpdateModelMixin:
        def update(self, request, *args, **kwargs):
            book = self.get_object()
            ser = self.get_serializer(data=request.data, instance=book)
            if ser.is_valid():
                ser.save()
                return Response({'code': 100, 'msg': '更新成功'})
            else:
                return Response({'code': 100, 'msg': ser.errors})
    

【4】演示

class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier

    def get(self, request):
        return self.list(request)

    def post(self, request):
        return self.create(request)
  • 上述代码定义了一个名为BookView的类,该类继承自GenericAPIViewListModelMixinCreateModelMixin三个类。

    • GenericAPIView是Django REST Framework提供的通用视图类,它提供了一些常用的REST操作的实现,如GET、POST、PUT、DELETE等,并对请求进行了封装,使得编写视图变得更加简洁和易读。

    • ListModelMixin是一个Mixin类,它提供了列表视图(获取多个对象)的具体实现,包括处理GET请求和返回序列化后的数据列表。

    • CreateModelMixin也是一个Mixin类,它提供了创建视图(新建对象)的具体实现,包括处理POST请求和返回序列化后的新建对象数据。

  • BookView类中,我们还定义了以下属性和方法:

    • queryset:指定了查询集,即要进行操作的对象集合。这里使用Book.objects.all()表示查询所有的Book对象。

    • serializer_class:指定了用于序列化和反序列化的序列化器类,这里使用BookSerializer

    • get(self, request)方法:处理GET请求,调用父类list方法返回序列化后的数据列表。

    • post(self, request)方法:处理POST请求,调用父类create方法创建新的对象并返回序列化后的数据。

  • 通过上述代码,我们可以基于BookView类创建一个API视图,支持获取书籍列表和创建新的书籍对象的操作。

    • 当我们发送GET请求时,将返回所有书籍的序列化数据列表;
    • 当我们发送POST请求时,将根据请求的参数创建一个新的书籍对象,并返回该对象的序列化数据。
  • 总结起来,上述代码展示了使用DRF编写API视图的常见用法:

    • 使用Mixin类继承和基于通用视图类进行开发,从而简化了代码的编写,提高了开发效率。
    • 同时,通过配置查询集和序列化器类,我们可以指定对应的数据模型和序列化逻辑,实现对数据的获取和创建操作。
class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerialzier

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)
  • 上述代码定义了一个名为BookDetailView的类,该类继承自GenericAPIViewRetrieveModelMixinUpdateModelMixinDestroyModelMixin四个类。

    • GenericAPIView是Django REST Framework提供的通用视图类,它提供了一些常用的REST操作的实现,如GET、POST、PUT、DELETE等,并对请求进行了封装,使得编写视图变得更加简洁和易读。

    • RetrieveModelMixin是一个Mixin类,它提供了获取单个对象的具体实现,包括处理GET请求和返回序列化后的对象数据。

    • UpdateModelMixin是一个Mixin类,它提供了更新对象的具体实现,包括处理PUT请求和返回更新后的对象数据。

    • DestroyModelMixin是一个Mixin类,它提供了删除对象的具体实现,包括处理DELETE请求和返回删除成功的响应。

  • BookDetailView类中,我们同样定义了querysetserializer_class属性,用于指定操作的查询集和序列化器类。

  • 此外,我们还重写了以下方法:

    • get(self, request, *args, **kwargs)方法:

      • 处理GET请求,调用父类的retrieve方法来获取指定对象的序列化数据并返回。
    • put(self, request, *args, **kwargs)方法:

      • 处理PUT请求,调用父类的update方法来更新指定对象并返回更新后的序列化数据。
    • delete(self, request, *args, **kwargs)方法:

      • 处理DELETE请求,调用父类的destroy方法来删除指定对象并返回删除成功的响应。
  • 通过上述代码,我们可以基于BookDetailView类创建一个API视图,

    • 支持获取特定书籍对象
    • 更新特定书籍对象
    • 删除特定书籍对象的操作
  • 当我们发送GET请求时

    • 将返回指定书籍对象的序列化数据;
    • 当我们发送PUT请求时,将更新指定书籍对象的字段,并返回更新后的序列化数据;
    • 当我们发送DELETE请求时,将删除指定书籍对象,并返回删除成功的响应。

【5】小结

  • 5个视图扩展类--->不是视图类--->必须配合GenericAPIView及其子类使用--->不能配合APIView使用

  • 5个视图扩展类,每一个类中只有一个方法,完成5个接口中的其中一个,想写多个接口,就要继承多个

【二】9个视图子类

  • 基于视图子类写接口: 9个视图子类 ---> 视图类

【推导】

【1】原始办法

from rest_framework.mixins import ListModelMixin, CreateModelMixin
from rest_framework.generics import GenericAPIView, ListAPIView, CreateAPIView, ListCreateAPIView


class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    
    def get(self, request):
        return self.list(request)

    def post(self, request):
        return self.create(request)

【2】基于视图子类

from rest_framework.mixins import ListModelMixin, CreateModelMixin
from rest_framework.generics import GenericAPIView, ListAPIView, CreateAPIView, ListCreateAPIView


class ListAPIView(GenericAPIView, ListModelMixin):
    def get(self, request):
        return self.list(request)


class CreateAPIView(GenericAPIView, CreateModelMixin):
    def post(self, request):
        return self.create(request)


class ListCreateAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):
    def get(self, request):
        return self.list(request)

    def post(self, request):
        return self.create(request)
  • 查询所有
class BookView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
  • 新增数据
class CreateAPIView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
  • 既查询所有又新增数据
class ListCreateAPIView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

【基于DRF】

from rest_framework.generics import ListAPIView, CreateAPIView, DestroyAPIView, UpdateAPIView, RetrieveAPIView
from rest_framework.generics import ListCreateAPIView
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from rest_framework.generics import RetrieveDestroyAPIView
from rest_framework.generics import RetrieveUpdateAPIView
from rest_framework.generics import UpdateDestroyAPIView 
# 没有这个视图 : 更新删除的前提是有数据,即先查询

【三】视图集

【1】视图集导入

from rest_framework.viewsets import ModelViewSet

【2】视图函数

  • 继承视图集后,只需要在视图类中写两行
queryset = 模型表名.objects.all()
serializer_class = 自定义序列化类

【3】路由格式

path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
path('books/<int:pk>/', BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
  • 路由中必须根据请求指定触发的函数方法

【4】ModelViewSet 源码分析

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass
  • ModelViewSet 继承了

    • mixins.CreateModelMixin

    • mixins.RetrieveModelMixin

    • mixins.UpdateModelMixin

    • mixins.DestroyModelMixin

    • mixins.ListModelMixin

    • GenericViewSet

  • GenericViewSet 源码分析,继承了

    • ViewSetMixin
    • generics.GenericAPIView
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    """
    The GenericViewSet class does not provide any actions by default,
    but does include the base set of generic view behavior, such as
    the `get_object` and `get_queryset` methods.
    """
    pass

(1)继承了ModelViewSet,路由需要指定格式的原因

  • 只要继承了ModelViewSet,路由写法变了,谁控制它变的:ViewSetMixin

  • ViewSetMixin源码分析

    • 根据这句话,我们就可以直观的发现我们需要指定路由格式的原因
     """
        This is the magic.
    
        Overrides `.as_view()` so that it takes an `actions` keyword that performs
        the binding of HTTP methods to actions on the Resource.
    
        For example, to create a concrete view binding the 'GET' and 'POST' methods
        to the 'list' and 'create' actions...
    
        view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
        """
    
    这是一个魔法。
    
    覆写 `.as_view()` 方法,使其接受一个 `actions` 关键字参数,用于将 HTTP 方法绑定到资源的动作(action)上。
    
    例如,要创建一个具体的视图,并将 'GET' 和 'POST' 方法绑定到 'list' 和 'create' 动作上...
    
    ```
    view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
    ```
    
class ViewSetMixin:
    """
    This is the magic.

    Overrides `.as_view()` so that it takes an `actions` keyword that performs
    the binding of HTTP methods to actions on the Resource.

    For example, to create a concrete view binding the 'GET' and 'POST' methods
    to the 'list' and 'create' actions...

    view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
    """

    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
        """
        Because of the way class based views create a closure around the
        instantiated view, we need to totally reimplement `.as_view`,
        and slightly modify the view function that is created and returned.
        """
        # The name and description initkwargs may be explicitly overridden for
        # certain route configurations. eg, names of extra actions.
        cls.name = None
        cls.description = None

        # The suffix initkwarg is reserved for displaying the viewset type.
        # This initkwarg should have no effect if the name is provided.
        # eg. 'List' or 'Instance'.
        cls.suffix = None

        # The detail initkwarg is reserved for introspecting the viewset type.
        cls.detail = None

        # Setting a basename allows a view to reverse its action urls. This
        # value is provided by the router through the initkwargs.
        cls.basename = None

        # actions must not be empty
        if not actions:
            raise TypeError("The `actions` argument must be provided when "
                            "calling `.as_view()` on a ViewSet. For example "
                            "`.as_view({'get': 'list'})`")

        # sanitize keyword arguments
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r" % (
                    cls.__name__, key))

        # name and suffix are mutually exclusive
        if 'name' in initkwargs and 'suffix' in initkwargs:
            raise TypeError("%s() received both `name` and `suffix`, which are "
                            "mutually exclusive arguments." % (cls.__name__))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)

            if 'get' in actions and 'head' not in actions:
                actions['head'] = actions['get']

            # We also store the mapping of request methods to actions,
            # so that we can later set the action attribute.
            # eg. `self.action = 'list'` on an incoming GET request.
            self.action_map = actions

            # Bind methods to actions
            # This is the bit that's different to a standard view
            for method, action in actions.items():
                handler = getattr(self, action)
                setattr(self, method, handler)

            self.request = request
            self.args = args
            self.kwargs = kwargs

            # And continue as usual
            return self.dispatch(request, *args, **kwargs)

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())

        # We need to set these on the view function, so that breadcrumb
        # generation can pick out these bits of information from a
        # resolved URL.
        view.cls = cls
        view.initkwargs = initkwargs
        view.actions = actions
        return csrf_exempt(view)

    def initialize_request(self, request, *args, **kwargs):
        """
        Set the `.action` attribute on the view, depending on the request method.
        """
        request = super().initialize_request(request, *args, **kwargs)
        method = request.method.lower()
        if method == 'options':
            # This is a special case as we always provide handling for the
            # options method in the base `View` class.
            # Unlike the other explicitly defined actions, 'metadata' is implicit.
            self.action = 'metadata'
        else:
            self.action = self.action_map.get(method)
        return request

    def reverse_action(self, url_name, *args, **kwargs):
        """
        Reverse the action for the given `url_name`.
        """
        url_name = '%s-%s' % (self.basename, url_name)
        namespace = None
        if self.request and self.request.resolver_match:
            namespace = self.request.resolver_match.namespace
        if namespace:
            url_name = namespace + ':' + url_name
        kwargs.setdefault('request', self.request)

        return reverse(url_name, *args, **kwargs)

    @classmethod
    def get_extra_actions(cls):
        """
        Get the methods that are marked as an extra ViewSet `@action`.
        """
        return [_check_attr_name(method, name)
                for name, method
                in getmembers(cls, _is_extra_action)]

    def get_extra_action_url_map(self):
        """
        Build a map of {names: urls} for the extra actions.

        This method will noop if `detail` was not provided as a view initkwarg.
        """
        action_urls = OrderedDict()

        # exit early if `detail` has not been provided
        if self.detail is None:
            return action_urls

        # filter for the relevant extra actions
        actions = [
            action for action in self.get_extra_actions()
            if action.detail == self.detail
        ]

        for action in actions:
            try:
                url_name = '%s-%s' % (self.basename, action.url_name)
                namespace = self.request.resolver_match.namespace
                if namespace:
                    url_name = '%s:%s' % (namespace, url_name)

                url = reverse(url_name, self.args, self.kwargs, request=self.request)
                view = self.__class__(**action.kwargs)
                action_urls[view.get_view_name()] = url
            except NoReverseMatch:
                pass  # URL requires additional arguments, ignore

        return action_urls

(2)ViewSetMixin 如何控制路由写法格式

  • ViewSetMixin : 重写了 as_view 方法

  • BookView.as_view 的执行,其实是ViewSetMixin的as_view

  • ViewSetMixin 是一个混合类(mixin),用于为 ViewSet 类提供一些额外的功能。
    • 其中,重写了 as_view 方法,来控制路由写法格式。
  • 在调用 BookView.as_view 方法时
    • 其实是调用了 ViewSetMixin 的 as_view 方法。
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
    # 我们传入的 actions={'get': 'list', 'post': 'create'}
    def view(request, *args, **kwargs):
        self = cls(**initkwargs)
        for method, action in actions.items():
            # method: 'get'
            # action: 'list'
            # 通过反射,self 是 BookView 的对象,获取 BookView 对象中的 list 方法
            handler = getattr(self, action)
            # 通过反射:给 BookView 的对象设置属性,为 'get',其值为 list 方法
            setattr(self, method, handler)
            # 调用 APIView 的 dispatch 方法
            return self.dispatch(request, *args, **kwargs)
    return csrf_exempt(view)
  • 上述代码中,as_view 方法接受一个 actions 参数,这个参数是一个字典,用于将 HTTP 方法绑定到资源的动作上。
    • 在 for 循环中,遍历 actions 字典,获取每个方法对应的动作名。
  • 然后,通过反射机制,从 BookView 对象中获取相应的动作方法(如 list 方法)。
    • 再通过反射,将获取到的动作方法设置为 BookView 对象的属性,其属性名为方法名(如 'get'),属性值为动作方法(如 list 方法)。
  • 最后,调用 APIView 的 dispatch 方法,实现请求的分派和处理。
    • 最终返回 csrf_exempt(view),该函数用于取消 Django 中的 CSRF(跨站请求伪造)保护。
  • 这样,ViewSetMixin 的 as_view 方法控制了路由写法格式,将不同的 HTTP 方法与对应的动作(action)进行绑定。

【5】小结

  • 当我们继承了 ViewSetMixin 及其子类时,路由的写法发生了变化,必须传递 actions 参数来定义 HTTP 方法与对应的动作。

  • 在路由中,需要将路径(path)与视图类的 as_view 方法进行绑定,同时传递 actions 参数:

    path('books/', BookView.as_view({'get': 'list', 'post': 'create'})),
    

    这样,在匹配到 "books/" 路径的 GET 请求时,就会执行 BookView 中的 list 方法;在 POST 请求时,执行 create 方法。

  • 视图类中的方法名可以根据需要自由命名

    • 但需与 actions 中的动作名保持一致。
  • 为了实现正确的路由匹配和处理

    • ViewSetMixin 必须与特定的视图类配合使用

    • 包括 APIView、GenericAPIView 等九个视图子类。

  • 需要将 ViewSetMixin 类放在视图类之前进行继承

    • 以确保正确的路由处理流程。

【四】视图集总结

【1】两个视图基类

APIView

  • APIView 是 Django REST Framework 提供的一个基类,用于创建基于函数或基于类的视图。
  • 使用 APIView 可以根据需求自定义请求处理逻辑,对于简单的接口逻辑,可以直接继承 APIView 类。

GenericAPIView

  • GenericAPIViewAPIView 的一个子类,提供了一些常用的通用方法和属性,使开发者能够更方便地编写视图。
  • 通常与 Mixin 类一起使用,通过继承 GenericAPIView 和混入相应的功能类可以快速建立功能完善的视图。

【2】5 个视图扩展类

视图扩展类用于与 GenericAPIView 或其子类配合使用,提供常用的 CRUD(创建、读取、更新和删除)操作。

ListAPIView

  • 继承自 GenericAPIViewListMixin,实现获取多条记录的功能。

CreateAPIView

  • 继承自 GenericAPIViewCreateModelMixin,实现创建记录的功能。

DestroyAPIView

  • 继承自 GenericAPIViewDestroyModelMixin,实现删除记录的功能。

UpdateAPIView

  • 继承自 GenericAPIViewUpdateModelMixin,实现更新记录的功能。

RetrieveAPIView

  • 继承自 GenericAPIViewRetrieveModelMixin,实现获取单条记录的功能。

【3】9个视图子类

视图子类用于与 GenericAPIView 或其子类配合使用,提供特定的功能和集成多个操作。

CreateModelMixin

  • 该混入类提供 create() 方法
  • 用于根据传入的 POST 请求数据创建模型的新对象实例

ListModelMixin

  • 该混入类提供 list() 方法
  • 用于检索模型的对象列表,并将其序列化为响应数据

RetrieveModelMixin

  • 该混入类提供 retrieve() 方法
  • 根据提供的标识符或查找字段检索模型的单个对象实例

DestroyModelMixin

  • 该混入类提供 destroy() 方法
  • 根据提供的标识符或查找字段处理删除模型的单个对象实例

UpdateModelMixin

  • 该混入类提供 update() 方法
  • 根据提供的标识符或查找字段处理更新模型的单个对象实例
  • 上面5个混入类并不是独立的视图,而是用于与其他基于类的视图结合使用
  • 例如 APIViewGenericAPIViewViewSet
  • 通过继承这些混入类和适当的视图,开发人员可以轻松地实现所需的功能,避免编写重复的代码。

ListCreateAPIView

  • 继承自 GenericAPIViewListCreateModelMixin,实现获取多条记录和创建记录的功能。

RetrieveUpdateDestroyAPIView

  • 继承自 GenericAPIViewRetrieveModelMixinUpdateModelMixinDestroyModelMixin,实现获取单条记录、更新记录和删除记录的功能。

RetrieveDestroyAPIView

  • 继承自 GenericAPIViewRetrieveModelMixinDestroyModelMixin,实现获取单条记录和删除记录的功能。

RetrieveUpdateAPIView

  • 继承自 GenericAPIViewRetrieveModelMixinUpdateModelMixin,实现获取单条记录和更新记录的功能。

【4】视图集

  • 视图集是一种组织和管理视图的方式,它包括了多个接口,并允许使用相同的 URL 前缀来映射这些接口。
  • ModelViewSet

    • 继承自 GenericViewSetModelMixin,提供了常用的 CRUD 操作(创建、读取、更新和删除)。
    • 5个接口
  • ReadOnlyModelViewSet

    • 继承自 GenericViewSetListModelMixin,只提供读取操作(查询所有和查询单条)。
    • 2个接口
    • list和retrieve (查询所有和查询单条)
  • ViewSetMixin

    • 是一个"魔法"混入类,不能单独使用,必须与视图类一起使用,用于改变路由的写法。
  • ViewSet

    • 继承自 ViewSetMixinAPIView
    • 用于继承 APIView 并改变路由的写法
    • 在视图类中的方法可以任意命名。
  • GenericViewSet

    • 继承自 ViewSetMixinGenericAPIView
    • 用于继承 GenericAPIView 并改变路由的写法
    • 在视图类中的方法可以任意命名。

【五】图解各个视图类之间的关系

【1】总图

【2】视图类

【3】视图集

【补充】Python中的Mixin机制

【一】Mixin机制

(1)介绍

  • Mixin是一种在面向对象编程中常用的机制,它通过多重继承的方式实现代码的复用,并且可以灵活地组合不同的功能。

  • 在Python中,Mixin通常是一个普通的类,其中定义了一些具有某种功能的方法。

  • Mixin类通常不会单独使用,而是作为其他类的基类,通过多重继承的方式将其功能混入到目标类中。

  • Mixin类的命名通常以Mixin结尾,这是一种命名约定,方便开发者识别。

(2)主要特点

  • 代码复用:

    • Mixin可以把一些通用的方法封装到一个类中,然后通过多重继承的方式引入到其他类中,从而实现代码的复用。
  • 功能组合:

    • Mixin类可以通过多重继承的方式,将多个Mixin类的功能组合到一个类中,从而实现更灵活的功能组合。
  • 层次结构扁平化:

    • 使用Mixin可以避免多层继承带来的层次结构复杂化问题,Mixin类通常只有少量方法,使得继承关系变得扁平化。
  • 独立性:

    • Mixin类通常是独立于其他类的,即使在没有其他类的情况下,Mixin类仍然是可用的。

(3)注意

  • 命名约定:

    • 为了避免命名冲突,Mixin类的命名通常以Mixin结尾。
  • 方法覆盖:

    • 当多个Mixin类中有相同方法名时,子类继承这些Mixin类的顺序会影响方法的调用顺序。

    • 一般情况下,最后继承的Mixin类的方法会覆盖之前继承的Mixin类的方法。

  • Diamond继承问题:

    • 如果在Mixin类之间存在继承关系,并且最终被继承的类同时继承了具有相同方法的Mixin类,那么需要注意方法解析顺序,以避免出现冲突或不确定的情况。

(4)总结

  • Mixin是一种通过多重继承的方式实现功能复用和组合的机制。
  • 它可以帮助我们在编写代码时更加灵活地应对复杂的需求,并提高代码的复用性和可维护性。

【二】示例

  • 当使用Mixin机制时,我们可以通过一个示例来更详细地说明其用法和作用。

  • 假设我们正在开发一个图形库,其中包含一些常见的几何图形类,如矩形、圆形和三角形。

  • 首先,我们定义一个Mixin类,名为ColorMixin,用于给图形对象添加颜色功能:

class ColorMixin:
    def set_color(self, color):
        self.color = color

    def get_color(self):
        return self.color
  • 然后,我们定义几个基本的几何图形类,并使用ColorMixin将颜色功能混入到这些类中:
class Rectangle(ColorMixin):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Circle(ColorMixin):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return math.pi * self.radius**2

class Triangle(ColorMixin):
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return 0.5 * self.base * self.height
  • 在上述代码中,我们将ColorMixin作为基类,通过多重继承的方式将其功能添加到了RectangleCircleTriangle这几个具体的几何图形类中。

  • 现在,我们可以创建具体的几何图形对象,并使用set_color()get_color()方法来操作颜色属性:

rectangle = Rectangle(5, 3)
rectangle.set_color('red')
print(rectangle.get_color())  # 输出: red

circle = Circle(4)
circle.set_color('blue')
print(circle.get_color())     # 输出: blue

triangle = Triangle(6, 2)
triangle.set_color('green')
print(triangle.get_color())   # 输出: green
  • 通过上述示例,我们可以看到Mixin的作用:

    • 将一些通用的功能逻辑封装到Mixin类中,然后可以通过多重继承的方式将其混入到我们需要的类中,实现代码的复用和灵活的功能组合。
  • 同时,由于Mixin类是独立的,我们可以在需要的时候单独使用它们,而不仅限于在特定的类中使用。

    • 这使得我们的代码更加模块化和可扩展。
  • 需要注意的是

    • 在使用Mixin时,我们需要遵循一些约定和注意事项,如方法命名约定、方法调用顺序等,以避免潜在的命名冲突或方法覆盖的问题。
  • 总之,Mixin机制是一种强大的工具,可以帮助我们在面向对象编程中实现代码的复用和功能的灵活组合。

  • 通过合理地设计和使用Mixin类,我们可以提高代码的可维护性和可复用性。