• 70.Python的Mixins机制


    Python的Mixins机制

    大多数面向对象语言都不支持多重继承,因为这会导致菱形问题, 而 Python 虽然形式上支持多重继承,但其实现机制却是利用 mixin,从而有效 地避免了菱形问题。

    一、什么是 mixin

    Mixin本意是混入,程序中用来将不同功能(functionality)组合起来,从而为 类提供多种特性。而虽然继承(inheritance)也可以实现多种功能,但继承一般 有从属关系,即子类通常是父类更加具体的类。而 mixin 则更多的是功能上的 组合,因而相当于是接口(带实现的接口)。

    好比是联想电脑与电脑之间是继承关系,因而联想电脑具备电脑的各种功能;而 联想电脑与键盘之间则是 mixin 关系,同样也具备键盘的各种功能。

    一般编程语言都不允许多重继承,主要是为了避免菱形问题,即两个父 类如果有共同的祖父类,但对祖父类相同部分做了不同的修改,则这个类再继承 两个父类就会产生冲突。

    类似于 git 版本控制中,如果两个人对同一段代码做了不同的修改,则合并时 就需要手动解决冲突。编程语言如果碰到 diamond problem 时依赖程序员决定 用哪个父类的特性,就会变得非常复杂而且容易产生歧义。

    从上面分析可以看出其实单从功能上来说,完全可以用 mixin 取代继承,从而 可以不要类这个概念。最近几年新出的编程语言 Rust 和Go里面就没有类 (class)以及继承,但并不影响代码复用,它们也正是利用 mixin 这种机制实现 的代码复用,例如 Rust 中用特征(Trait)取代了类和接口。

    两种观点其实是两种不同的世界观,目前类与继承的概念则更为流行,而且符合 人们对事物的认知:人们对白猫、黑猫、花猫观察后更容易抽象出猫的概念,而 不是将这些事物作为无规律的组合去看待。

    二、Python 中的 mixin

    理解了 mixin 概念之后,再将其运用到 Python 中,理解(形式上)多重继承 就会容易许多。python 对于 mixin 命名方式一般以 MixIn, able, ible 为后缀

    由于 mixin 是组合,因而是做加法,为已有的类添加新功能,而不像继承一样 下一级会覆盖上一级相同的属性或方法,但在某些方面仍然表现得与继承一样, 例如类的实例也是每个 mixin 的实例。mixin 使用不当会导致类的命名空间污 染,所以要尽量避免 mixin 中定义相同方法,对于相同的方法,有时很难区分 实例到底使用的是哪个方法。

    class Mixin1(object):
        def test(self):
            print("mixin 1")
        def which_test(self):
            self.test()
    
    class Mixin2(object):
        def test(self):
            print("mixin 2")
    
    class MyClass1(Mixin1, Mixin2):
        pass                        # 按从左到右顺序从 mixin 中获取功能并添加到 MyClass
    
    class Myclass2(Mixin1, Mixin2):
        def test(self):             # 已有 test 方法,因而不会再添加 Mixin1, Mixin2 的 test 方法
            print("my class 2")
    
    c1 = MyClass1()
    c1.test()                       # => "mixin 1"
    c2 = MyClass2()
    c2.test()                       # => "my class 2"
    c2.which_test()                 # => "my class 2"
    isinstance(c1, Mixin1)          # => True
    issubclass(MyClass1, Mixin2)    # => True
    

    Mixin 强调的是功能而不像继承那样包括所有功能和数据域,但利用 mixin 同 样也可以实现代码复用,下面这段代码来自Stack Overflow,当然 functools.total_ordering() 装饰器已经提供相同功能了,这里仅用来说明 mixin 实现代码复用。

    class Comparable(object):
        def __ne__(self, other):
            return not (self == other)
    
        def __lt__(self, other):
            return self <= other and (self != other)
    
        def __gt__(self, other):
            return not self <= other
    
        def __ge__(self, other):
            return self == other or self > other
    
    
    class Integer(Comparable):
        def __init__(self, i):
            self.i = i
    
    
    class Char(Comparable):
        def __init__(self, c):
            self.c = c
    

    下面是 Python2 中动态加入 mixin 的方法[fn:1],python3 中已经不支持这种 方法了,python3 可能需要借助 type 等元编程工具实现[fn:2]动态 mixin

    def MixIn(pyClass, mixInClass, makeLast=0):
        if mixInClass not in pyClass.__bases__:
            if makeLast:
                pyClass.__bases__ += (mixInClass,)
            else:
                pyClass.__bases__ = (mixInClass,) + pyClass.__bases
    

    不过尽管动态 mixin 是可能的,但实际使用中要尽量避免这样做,因为可能会 使所有使用这个 mixin 的实例出现一些不可预知的问题。

    三、Python的mixin 和 Ruby的mixin

    Matthew J. Morrison提到的例子表明 Python 的 mixin 并不是纯粹意义上的 mixin,还是带有继承的特点。

    from datetime import datetime, date
    import json
    
    class Jsonable(object):
    
        def date_handler(self, obj):
            if isinstance(obj, (datetime, date)):
                return obj.isoformat()
    
        def save_json(self, file_name):
            with open(file_name, 'w') as output:
                output.write(json.dumps(self.__dict__, default=self.date_handler))
    
    class Person(Jsonable):
    
        def __init__(self, name, bday):
            self.name = name
            self.bday = bday
    
    
    if __name__ == '__main__':
        matt = Person('matt', date(1983, 07, 12))
        matt.save_json("matt.json")
        assert issubclass(Person, Jsonable)
        assert isinstance(matt, Person)
        assert isinstance(matt, Jsonable)
    

    而 Ruby 的 Mixin 则不带有继承的概念,直接使用 include 引入 mixin。从语 义上讲,的确用 include 描述比 inherit 更准确。

    require "json"
    
    module Jsonable
      def jsonify
        json_data = {}
        self.instance_variables.each do |v|
          json_data[v.to_s[1..-1]] = self.instance_variable_get(v)
        end
        return json_data.to_json
      end
    
      def save_json(file_name)
        File.open(file_name, 'w') {|f| f.write(self.jsonify) }
      end
    
    end
    
    class Person
      include Jsonable
      def initialize(name, bday)
        @name = name
        @bday = bday
      end
    end
    
    person = Person.new('name', '07/12/1983')
    person.save_json('ruby.json')
    raise "not instance" unless person.instance_of? Person
    raise "is instance" if person.instance_of? Jsonable
    raise "subclass" if Person.is_a? Jsonable
    
  • 相关阅读:
    解释DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
    MySQL性能优化
    MySQL中的binlog相关命令和恢复技巧
    保障MySQL安全的14个最佳方法
    MySQL忘记root密码的解决方案
    MySQL利用binlog来恢复数据库
    MySQL命令mysqldump参数大全
    MySQL REPLACE替换输出
    MySQL -A不预读数据库信息(use dbname 更快)
    MySQL 慢查询配置
  • 原文地址:https://www.cnblogs.com/LWX-YEER/p/12674812.html
Copyright © 2020-2023  润新知