zoukankan      html  css  js  c++  java
  • AttributeError: type object 'Tombola' has no attribute '_abc_registry' --《流畅的 Python》第 11 章笔记

    这一章的内容基本上都是容易理解的。在一边读一边敲代码的过程中,发现了一个问题。

    tombola_runner.py (在书中是示例 11-15) 中有这样一行代码:

     virtual_subclasses = list(Tombola._abc_registry)
    

    运行会报错:

    AttributeError: type object 'Tombola' has no attribute '_abc_registry'
    

    经过多次排查,最终发现问题出在了 Python 版本上。我的 Python 版本是 3.8,它是不支持抽象基类中的 _abc_registry 这一数据属性的。作者用的版本似乎是 Python 3.4,所以我借来了同学的电脑,配置了 Python 3.4 的环境,然后测试,发现可以正常运行:

    20210301002418

    下面就通过查看源码的方式来稍微探究一下 Python 3.8 和 Python 3.4 的区别(当然其他的高版本的 Python 可能也有这样的问题),这里用到工具是 PyCharm。

    在 Python 3.4 中,abc.py 的源码是这样的:

    # Copyright 2007 Google, Inc. All Rights Reserved.
    # Licensed to PSF under a Contributor Agreement.
    
    """Abstract Base Classes (ABCs) according to PEP 3119."""
    
    from _weakrefset import WeakSet
    
    
    def abstractmethod(funcobj):
        """A decorator indicating abstract methods.
    
        Requires that the metaclass is ABCMeta or derived from it.  A
        class that has a metaclass derived from ABCMeta cannot be
        instantiated unless all of its abstract methods are overridden.
        The abstract methods can be called using any of the normal
        'super' call mechanisms.
    
        Usage:
    
            class C(metaclass=ABCMeta):
                @abstractmethod
                def my_abstract_method(self, ...):
                    ...
        """
        funcobj.__isabstractmethod__ = True
        return funcobj
    
    
    class abstractclassmethod(classmethod):
        """
        A decorator indicating abstract classmethods.
    
        Similar to abstractmethod.
    
        Usage:
    
            class C(metaclass=ABCMeta):
                @abstractclassmethod
                def my_abstract_classmethod(cls, ...):
                    ...
    
        'abstractclassmethod' is deprecated. Use 'classmethod' with
        'abstractmethod' instead.
        """
    
        __isabstractmethod__ = True
    
        def __init__(self, callable):
            callable.__isabstractmethod__ = True
            super().__init__(callable)
    
    
    class abstractstaticmethod(staticmethod):
        """
        A decorator indicating abstract staticmethods.
    
        Similar to abstractmethod.
    
        Usage:
    
            class C(metaclass=ABCMeta):
                @abstractstaticmethod
                def my_abstract_staticmethod(...):
                    ...
    
        'abstractstaticmethod' is deprecated. Use 'staticmethod' with
        'abstractmethod' instead.
        """
    
        __isabstractmethod__ = True
    
        def __init__(self, callable):
            callable.__isabstractmethod__ = True
            super().__init__(callable)
    
    
    class abstractproperty(property):
        """
        A decorator indicating abstract properties.
    
        Requires that the metaclass is ABCMeta or derived from it.  A
        class that has a metaclass derived from ABCMeta cannot be
        instantiated unless all of its abstract properties are overridden.
        The abstract properties can be called using any of the normal
        'super' call mechanisms.
    
        Usage:
    
            class C(metaclass=ABCMeta):
                @abstractproperty
                def my_abstract_property(self):
                    ...
    
        This defines a read-only property; you can also define a read-write
        abstract property using the 'long' form of property declaration:
    
            class C(metaclass=ABCMeta):
                def getx(self): ...
                def setx(self, value): ...
                x = abstractproperty(getx, setx)
    
        'abstractproperty' is deprecated. Use 'property' with 'abstractmethod'
        instead.
        """
    
        __isabstractmethod__ = True
    
    
    class ABCMeta(type):
    
        """Metaclass for defining Abstract Base Classes (ABCs).
    
        Use this metaclass to create an ABC.  An ABC can be subclassed
        directly, and then acts as a mix-in class.  You can also register
        unrelated concrete classes (even built-in classes) and unrelated
        ABCs as 'virtual subclasses' -- these and their descendants will
        be considered subclasses of the registering ABC by the built-in
        issubclass() function, but the registering ABC won't show up in
        their MRO (Method Resolution Order) nor will method
        implementations defined by the registering ABC be callable (not
        even via super()).
    
        """
    
        # A global counter that is incremented each time a class is
        # registered as a virtual subclass of anything.  It forces the
        # negative cache to be cleared before its next use.
        # Note: this counter is private. Use `abc.get_cache_token()` for
        #       external code.
        _abc_invalidation_counter = 0
    
        def __new__(mcls, name, bases, namespace):
            cls = super().__new__(mcls, name, bases, namespace)
            # Compute set of abstract method names
            abstracts = {name
                         for name, value in namespace.items()
                         if getattr(value, "__isabstractmethod__", False)}
            for base in bases:
                for name in getattr(base, "__abstractmethods__", set()):
                    value = getattr(cls, name, None)
                    if getattr(value, "__isabstractmethod__", False):
                        abstracts.add(name)
            cls.__abstractmethods__ = frozenset(abstracts)
            # Set up inheritance registry
            cls._abc_registry = WeakSet() # ②
            cls._abc_cache = WeakSet()
            cls._abc_negative_cache = WeakSet()
            cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
            return cls
    
        def register(cls, subclass):
            """Register a virtual subclass of an ABC.
    
            Returns the subclass, to allow usage as a class decorator.
            """
            if not isinstance(subclass, type):
                raise TypeError("Can only register classes")
            if issubclass(subclass, cls):
                return subclass  # Already a subclass
            # Subtle: test for cycles *after* testing for "already a subclass";
            # this means we allow X.register(X) and interpret it as a no-op.
            if issubclass(cls, subclass):
                # This would create a cycle, which is bad for the algorithm below
                raise RuntimeError("Refusing to create an inheritance cycle")
            cls._abc_registry.add(subclass) # ①
            ABCMeta._abc_invalidation_counter += 1  # Invalidate negative cache
            return subclass
    
        def _dump_registry(cls, file=None):
            """Debug helper to print the ABC registry."""
            print("Class: %s.%s" % (cls.__module__, cls.__name__), file=file)
            print("Inv.counter: %s" % ABCMeta._abc_invalidation_counter, file=file)
            for name in sorted(cls.__dict__.keys()):
                if name.startswith("_abc_"):
                    value = getattr(cls, name)
                    print("%s: %r" % (name, value), file=file)
    
        def __instancecheck__(cls, instance):
            """Override for isinstance(instance, cls)."""
            # Inline the cache checking
            subclass = instance.__class__
            if subclass in cls._abc_cache:
                return True
            subtype = type(instance)
            if subtype is subclass:
                if (cls._abc_negative_cache_version ==
                    ABCMeta._abc_invalidation_counter and
                    subclass in cls._abc_negative_cache):
                    return False
                # Fall back to the subclass check.
                return cls.__subclasscheck__(subclass)
            return any(cls.__subclasscheck__(c) for c in {subclass, subtype})
    
        def __subclasscheck__(cls, subclass):
            """Override for issubclass(subclass, cls)."""
            # Check cache
            if subclass in cls._abc_cache:
                return True
            # Check negative cache; may have to invalidate
            if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter:
                # Invalidate the negative cache
                cls._abc_negative_cache = WeakSet()
                cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
            elif subclass in cls._abc_negative_cache:
                return False
            # Check the subclass hook
            ok = cls.__subclasshook__(subclass)
            if ok is not NotImplemented:
                assert isinstance(ok, bool)
                if ok:
                    cls._abc_cache.add(subclass)
                else:
                    cls._abc_negative_cache.add(subclass)
                return ok
            # Check if it's a direct subclass
            if cls in getattr(subclass, '__mro__', ()):
                cls._abc_cache.add(subclass)
                return True
            # Check if it's a subclass of a registered class (recursive)
            for rcls in cls._abc_registry:
                if issubclass(subclass, rcls):
                    cls._abc_cache.add(subclass)
                    return True
            # Check if it's a subclass of a subclass (recursive)
            for scls in cls.__subclasses__():
                if issubclass(subclass, scls):
                    cls._abc_cache.add(subclass)
                    return True
            # No dice; update negative cache
            cls._abc_negative_cache.add(subclass)
            return False
    
    
    class ABC(metaclass=ABCMeta):
        """Helper class that provides a standard way to create an ABC using
        inheritance.
        """
        pass
    
    
    def get_cache_token():
        """Returns the current ABC cache token.
    
        The token is an opaque object (supporting equality testing) identifying the
        current version of the ABC cache for virtual subclasses. The token changes
        with every call to ``register()`` on any ABC.
        """
        return ABCMeta._abc_invalidation_counter
    

    ① 在代码中标记 “①” 的地方,我们可以看到这样一行代码:

    cls._abc_registry.add(subclass)
    

    这行代码是在 register(cls, subclass) 方法体中,很显然,它表明了,在注册虚拟子类时,该函数将每一个相应的虚拟子类(subclass)加入了 _abc_registry 属性中,而这个属性,是属于抽象基类(cls)的。

    ② 从 __new__() 方法中,我们可以看到 _abc_registry 属性被赋值:

    cls._abc_cache = WeakSet()
    

    我们再来看 Python 3.8 中的 abc.py 源码:

    # Copyright 2007 Google, Inc. All Rights Reserved.
    # Licensed to PSF under a Contributor Agreement.
    
    """Abstract Base Classes (ABCs) according to PEP 3119."""
    
    
    def abstractmethod(funcobj):
        """A decorator indicating abstract methods.
    
        Requires that the metaclass is ABCMeta or derived from it.  A
        class that has a metaclass derived from ABCMeta cannot be
        instantiated unless all of its abstract methods are overridden.
        The abstract methods can be called using any of the normal
        'super' call mechanisms.  abstractmethod() may be used to declare
        abstract methods for properties and descriptors.
    
        Usage:
    
            class C(metaclass=ABCMeta):
                @abstractmethod
                def my_abstract_method(self, ...):
                    ...
        """
        funcobj.__isabstractmethod__ = True
        return funcobj
    
    
    class abstractclassmethod(classmethod):
        """A decorator indicating abstract classmethods.
    
        Deprecated, use 'classmethod' with 'abstractmethod' instead.
        """
    
        __isabstractmethod__ = True
    
        def __init__(self, callable):
            callable.__isabstractmethod__ = True
            super().__init__(callable)
    
    
    class abstractstaticmethod(staticmethod):
        """A decorator indicating abstract staticmethods.
    
        Deprecated, use 'staticmethod' with 'abstractmethod' instead.
        """
    
        __isabstractmethod__ = True
    
        def __init__(self, callable):
            callable.__isabstractmethod__ = True
            super().__init__(callable)
    
    
    class abstractproperty(property):
        """A decorator indicating abstract properties.
    
        Deprecated, use 'property' with 'abstractmethod' instead.
        """
    
        __isabstractmethod__ = True
    
    
    try:
        from _abc import (get_cache_token, _abc_init, _abc_register,
                          _abc_instancecheck, _abc_subclasscheck, _get_dump,
                          _reset_registry, _reset_caches)
    except ImportError:
        from _py_abc import ABCMeta, get_cache_token
        ABCMeta.__module__ = 'abc'
    else:
        class ABCMeta(type):
            """Metaclass for defining Abstract Base Classes (ABCs).
    
            Use this metaclass to create an ABC.  An ABC can be subclassed
            directly, and then acts as a mix-in class.  You can also register
            unrelated concrete classes (even built-in classes) and unrelated
            ABCs as 'virtual subclasses' -- these and their descendants will
            be considered subclasses of the registering ABC by the built-in
            issubclass() function, but the registering ABC won't show up in
            their MRO (Method Resolution Order) nor will method
            implementations defined by the registering ABC be callable (not
            even via super()).
            """
            def __new__(mcls, name, bases, namespace, **kwargs): # ①
                cls = super().__new__(mcls, name, bases, namespace, **kwargs)
                _abc_init(cls)
                return cls
    
            def register(cls, subclass):
                """Register a virtual subclass of an ABC.
    
                Returns the subclass, to allow usage as a class decorator.
                """
                return _abc_register(cls, subclass)
    
            def __instancecheck__(cls, instance):
                """Override for isinstance(instance, cls)."""
                return _abc_instancecheck(cls, instance)
    
            def __subclasscheck__(cls, subclass):
                """Override for issubclass(subclass, cls)."""
                return _abc_subclasscheck(cls, subclass)
    
            def _dump_registry(cls, file=None): # ②
                """Debug helper to print the ABC registry."""
                print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
                print(f"Inv. counter: {get_cache_token()}", file=file)
                (_abc_registry, _abc_cache, _abc_negative_cache,
                 _abc_negative_cache_version) = _get_dump(cls)
                print(f"_abc_registry: {_abc_registry!r}", file=file)
                print(f"_abc_cache: {_abc_cache!r}", file=file)
                print(f"_abc_negative_cache: {_abc_negative_cache!r}", file=file)
                print(f"_abc_negative_cache_version: {_abc_negative_cache_version!r}",
                      file=file)
    
            def _abc_registry_clear(cls):
                """Clear the registry (for debugging or testing)."""
                _reset_registry(cls)
    
            def _abc_caches_clear(cls):
                """Clear the caches (for debugging or testing)."""
                _reset_caches(cls)
    
    
    class ABC(metaclass=ABCMeta):
        """Helper class that provides a standard way to create an ABC using
        inheritance.
        """
        __slots__ = ()
    

    ① 我们再来观察这个源码的 __new__() 方法,发现它的实现被封装好的 _abc_init() 函数给隐藏起来了,那我们再进一步查看 _abc_init() 方法:

    def _abc_init(*args, **kwargs): # real signature unknown
        """ Internal ABC helper for class set-up. Should be never used outside abc module. """
        pass
    

    可以发现,它的注释说,真实的签名是未知的,并且,它的文档注释也说明了一点,这个函数的内容是不会在 abc 这个模块的外面被使用的。也就是说,在我们看不到里面具体实现的情况下,我们不清楚抽象基类是否会有 _abc_registry 这个属性,即使有这个属性,它也不能够被我们访问到。事实上,这个 _abc_registry 属性确实还是在的,我们可以看上面的代码的 ② 部分。

    ② 这个函数是 _dump_registry(),里面有这样的语句:

    print(f"Inv. counter: {get_cache_token()}", file=file)
    (_abc_registry, _abc_cache, _abc_negative_cache,
        _abc_negative_cache_version) = _get_dump(cls)
    print(f"_abc_registry: {_abc_registry!r}", file=file)
    

    我们在抽象基类上调用这个方法:

    Tombola._dump_registry()
    

    打印的结果如下:

    Inv. counter: 49
    _abc_registry: {<weakref at 0x0000027AFC3520E0; to 'type' at 0x0000027AFB364650 (TomboList)>}
    _abc_cache: set()
    _abc_negative_cache: {<weakref at 0x0000027AFC352040; to 'type' at 0x0000027AFB364650 (TomboList)>}
    _abc_negative_cache_version: 48
    

    其中的 _abc_registry 的内容和我在 Python 3.4 环境下调用 print(Tombola._abc_registry) 打印的结果是一样的。

    至此,我们大致解决了这个问题。尚有一点不明确的,就是 Python 高版本中是否有其他方式可以在我们自己的代码中获取这个 _abc_registry 数据属性。我目前还没有找到这个方法。

  • 相关阅读:
    springboot文件上传: 单个文件上传 和 多个文件上传
    Eclipse:很不错的插件-devStyle,将你的eclipse变成idea风格
    springboot项目搭建:结构和入门程序
    POJ 3169 Layout 差分约束系统
    POJ 3723 Conscription 最小生成树
    POJ 3255 Roadblocks 次短路
    UVA 11367 Full Tank? 最短路
    UVA 10269 Adventure of Super Mario 最短路
    UVA 10603 Fill 最短路
    POJ 2431 Expedition 优先队列
  • 原文地址:https://www.cnblogs.com/fanlumaster/p/14461500.html
Copyright © 2011-2022 走看看