zoukankan      html  css  js  c++  java
  • 你真的了解__instancecheck__、__subclasscheck__、__subclasshook__三者的用法吗

    楔子

    下面我们说几个魔法方法,这几个魔法方法比较特殊,因为我们不经常用。但是相信你在看完之后,能够对python的类有更深刻的理解。下面我们就来介绍一下__instancecheck____subclasscheck____subclasshook__这几个魔法方法。

    __instancecheck__

    __instancecheck__是专门用于isinstance函数,检测一个实例对象是否属于某个类的实例。但是注意:这个方法一定要定义在元类当中,比如isinstance(obj, A),实际上会调用type(A)__instancecheck__方法,而A是一个类,那么type(A)不就是一个元类吗?我们举个例子。

    class A:
    
        def __instancecheck__(self, instance):
            print("__instancecheck__被调用")
            return True
    
    
    print(isinstance(123, A))  # False
    # 上面打印了False,很正常,因为123显然不是A的实例对象。
    # 虽然A中定义了__instancecheck__,但是没用,因为调用的是type(A)的__instancecheck__
    
    
    # 于是聪明如你可能想到了
    print(isinstance(123, A()))
    """
    __instancecheck__被调用
    True
    """
    # 如果我们将A改成A()不就行了吗,这样的话会调用type(A())、也就是A的__instancecheck__
    # 确实如此,但这没有什么意义。
    # 而且事实上isinstance的第二个参数不可以是实例对象,否则报错
    try:
        isinstance(123, object())
    except Exception as e:
        # 告诉我们isinstance的第二个参数必须是一个类,或者是一个包含的多个类的元组
        print(e)  # isinstance() arg 2 must be a type or tuple of types
        
    # 而我们上面的isinstance(123, A())之所以没有报错,就是因为我们内部定义了__instancecheck__
    # 如果没有定义这个魔法方法,那么也会报出同样的错误
    
    

    因此__instancecheck__这个魔法方法是要定义在元类当中,尽管定义在普通的类里面也可以使用,但是没有什么意义

    class MyType(type):
    
        def __instancecheck__(self, instance):
            # 当我们调用isinstance(obj, cls)的时候
            # 那么这个obj就会传递到这里的instance参数,前提是cls这个类是由这里的MyType实例化得到的
            if hasattr(instance, "hanser"):
                # 如果instance内部有hanser这个属性或者方法的话,返回True
                return True
            # 否则返回False
            return False
    
    
    class A(metaclass=MyType):
        pass
    
    
    # 整型显然没有hanser这个属性或方法,所以是False
    print(isinstance(123, A))  # False
    
    from flask import Flask
    print(isinstance(Flask(__name__), A))  # False
    setattr(Flask, "hanser", "xxx")
    print(isinstance(Flask(__name__), A))  # True
    """
    一开始Flask内部没有hanser这个属性或方法,所以isinstance(Flask(__name__), A)为False
    但是我们通过setattr设置一个名为hanser的属性,所以再次执行isinstance(Flask(__name__), A),返回True
    """
    
    # 尽管Flask(__name__)是A的实例对象,但是Flask并不是A的子类
    print(issubclass(Flask, A))  # False
    # 之所以说这一点,是为了和后面的两个魔法方法作区分
    

    __subclasscheck__

    __subclasscheck__这个不用想,肯定是用于issubclass。这个内置函数不用我多说,接收两个类,判断一个类是不是另一个类的子类。但是这个方法同样需要定义在元类里面才有意义

    class MyType(type):
    
        def __subclasscheck__(self, subclass):
            # 当调用issubclass(cls1, cls2)的时候,cls1就会传递给这里的subclass
            # 但前提是cls2的元类是这里的MyType
            if hasattr(subclass, "hanser"):
                # 如果subclass内部有hanser这个属性或者方法的话,返回True
                return True
            # 否则返回False
            return False
    
    
    class A(metaclass=MyType):
        pass
    
    
    from flask import Flask
    print(issubclass(Flask, A))  # False
    setattr(Flask, "hanser", "xxx")
    print(issubclass(Flask, A))  # True
    
    # 原因无需再解释,和isinstance类似
    print(isinstance(Flask(__name__), A))  # False
    # 但此时Flask(__name__)不是A的实例对象
    

    如果我们不定义在元类中,看看会怎么样

    class A:
        def __subclasscheck__(self, subclass):
            # 全部返回True
            return True
    
    
    # 惊了,object居然是A的实例对象的子类。
    print(issubclass(object, A()))  # True
    # A的实例对象压根就不是一个类,它居然摇身一变,成为了python中万物之父的类object的父类
    # 究其原因就是因为A内部定义了__subclasscheck__,issubclass(object, A())的时候,会调用A的__subclasscheck__方法
    

    无论是__instancecheck__,还是__subclasscheck__,它们都应该定义在元类里面,而不是类里面。如果定义在类里面,那么要想使这两个魔法方法生效,那么就必须使用该类的实例对象。而isinstance和issubclass的第二个参数接收的都是类(或者包含多个类的元组),我们传入实例对象理论上是会报错的,只不过生成该实例对象的类里面定义了相应的魔法方法,所以才不会报错。但即便如此,我们也不要这么做,因为这样没有什么意义。而且,如果你用的是pycharm这种智能的编辑器的话,也会给你标黄

    所以我们不要传入一个实例对象,也就是不要将这两个魔法方法定义在普通的类中。而是要定义在继承自type的类中,也就是元类。

    __subclasshook__

    上面那两个魔法方法是属于预定义的,需要定义在元类中。但是__subclasshook__不是,它是定义在抽象基类中。

    我们可以去模块collections.abc(或者直接去_collections_abc)中看一下,里面定义了大量的抽象基类。比如Iterable、Sized、Container等等一大堆

    class Iterable(metaclass=ABCMeta):
    
        __slots__ = ()
    	
        # 如果想要继承Iterable,那么必须实现__iter__方法
        @abstractmethod
        def __iter__(self):
            while False:
                yield None
    
        @classmethod
        def __subclasshook__(cls, C):
            # 重点来了,当我们调用issubclass(cls, Iterable)的时候
            # 那么cls会传递给这里的C,注意这个方法是一个类方法,__subclasshook__里面cls指的是Iterable本身
            # 而我们在调用issubclass(cls, Iterable)的时候,cls会传给这里的C
            if cls is Iterable:
                return _check_methods(C, "__iter__")
            return NotImplemented
    
    class Sized(metaclass=ABCMeta):
    
        __slots__ = ()
    	
        # Sized,可以使用len方法的,那么内部必须实现__len__
        @abstractmethod
        def __len__(self):
            return 0
    
        @classmethod
        def __subclasshook__(cls, C):
            # 和Iterable类似
            if cls is Sized:
                return _check_methods(C, "__len__")
            return NotImplemented
        
        
    class Container(metaclass=ABCMeta):
    
        __slots__ = ()
    	
        # 容器,内部必须实现__contains__方法,换句话说就是可以使用in
        # 比如:if 1 in [1, 2, 3] 等价于 if [1, 2, 3].__contains__(1)
        @abstractmethod
        def __contains__(self, x):
            return False
    
        @classmethod
        def __subclasshook__(cls, C):
            if cls is Container:
                return _check_methods(C, "__contains__")
            return NotImplemented
    

    所以关键就在于这个__subclasshook__,下面我们就可以自己实现了

    from abc import ABCMeta
    
    
    class A(metaclass=ABCMeta):
    
        @classmethod
        def __subclasshook__(cls, C):
            if hasattr(C, "hanser"):
                return True
            return False
    
    
    from flask import Flask
    setattr(Flask, "hanser", "xxx")
    print(isinstance(Flask(__name__), A))  # True
    print(issubclass(Flask, A))  # True
    

    我们看到如果定义了__subclasshook__,那么会同时作用于isinstance和issubclass。而__instancecheck__只作用于isinstance函数,__subclasscheck__只作用于issubclass函数。

    并且我们还可以进行继承

    from abc import ABCMeta
    
    
    class A(metaclass=ABCMeta):
    
        @classmethod
        def __subclasshook__(cls, C):
            if hasattr(C, "hanser"):
                return True
            return False
    
    
    class B(A):
        pass
    
    
    from flask import Flask
    setattr(Flask, "hanser", "xxx")
    print(isinstance(Flask(__name__), B))  # True
    print(issubclass(Flask, B))  # True
    

    以上就简单的介绍了它们的用法,或者说逻辑。至于怎么在项目中使用,就看你自己的啦。

  • 相关阅读:
    在.net 4.0程序中使用TPL Dataflow
    打算把我的视频工具整合一下
    Visual Studio 2012 Updater 2 发布了
    Entity Framework学习(二)基本操作
    Entity Framework学习(一)CodeFirst入门
    VS2012中对C++注释高亮的改进
    【翻译】(12)NDK GDB
    (3)NDK Development
    【翻译】(10)Import Module
    【翻译】(7)CPU Arch ABIs
  • 原文地址:https://www.cnblogs.com/traditional/p/11731676.html
Copyright © 2011-2022 走看看