Checkpoint. Rewrote (and swapped) the sections on

isinstance/issubclass overriding and the support framework.
Next up: redesign the collection ABCs.
This commit is contained in:
Guido van Rossum 2007-05-11 20:49:12 +00:00
parent ca783cc71c
commit cef12733fb
1 changed files with 203 additions and 94 deletions

View File

@ -16,12 +16,12 @@ Abstract
This is a proposal to add Abstract Base Class (ABC) support to Python
3000. It proposes:
* An "ABC support framework" which defines a built-in decorator that
can be used to define abstract methods. A class containing an
abstract method that isn't overridden cannot be instantiated.
* A way to overload ``isinstance()`` and ``issubclass()``.
* A new module ``abc`` which serves as an "ABC support framework". It
defines a metaclass for use with ABCs and a decorator that can be
used to define abstract methods.
* Specific ABCs for containers and iterators, to be added to the
collections module.
@ -100,8 +100,8 @@ This PEP proposes a particular strategy for organizing these tests
known as Abstract Base Classes, or ABC. ABCs are simply Python
classes that are added into an object's inheritance tree to signal
certain features of that object to an external inspector. Tests are
done using isinstance(), and the presence of a particular ABC means
that the test has passed.
done using ``isinstance()``, and the presence of a particular ABC
means that the test has passed.
In addition, the ABCs define a minimal set of methods that establish
the characteristic behavior of the type. Code that discriminates
@ -123,25 +123,197 @@ Specification
The specification follows the categories listed in the abstract:
* An "ABC support framework" which defines a built-in decorator that
make it easy to define ABCs, and mechanisms to support it.
* A way to overload ``isinstance()`` and ``issubclass()``.
* A new module ``abc`` which serves as an "ABC support framework". It
defines a metaclass for use with ABCs and a decorator that can be
used to define abstract methods.
* Specific ABCs for containers and iterators, to be added to the
collections module.
ABC Support Framework
---------------------
Overloading ``isinstance()`` and ``issubclass()``
-------------------------------------------------
We define a new built-in decorator, ``@abstractmethod``, to be used to
declare abstract methods. A class containing at least one method
declared with this decorator that hasn't been overridden yet cannot be
instantiated. Such a methods may be called from the overriding method
in the subclass (using ``super`` or direct invocation). For example::
During the development of this PEP and of its companion, PEP 3141, we
repeatedly faced the choice between standardizing more, fine-grained
ABCs or fewer, course-grained ones. For example, at one stage, PEP
3141 introduced the following stack of base classes used for complex
numbers: MonoidUnderPlus, AdditiveGroup, Ring, Field, Complex (each
derived from the previous). And the discussion mentioned several
other algebraic categorizations that were left out: Algebraic,
Transcendental, and IntegralDomain, and PrincipalIdealDomain. In
earlier versions of the current PEP, we considered the use cases for
separate classes like Set, ComposableSet, MutableSet, HashableSet,
MutableComposableSet, HashableComposableSet.
class A:
The dilemma here is that we'd rather have fewer ABCs, but then what
should a user do who needs a less refined ABC? Consider e.g. the
plight of a mathematician who wants to define his own kind of
Transcendental numbers, but also wants float and int to be considered
Transcendental. PEP 3141 originally proposed to patch float.__bases__
for that purpose, but there are some good reasons to keep the built-in
types immutable (for one, they are shared between all Python
interpreters running in the same address space, as is used by
mod_python).
Another example would be someone who wants to define a generic
function (PEP 3124) for any sequences that has an ``append()`` method.
The ``Sequence`` ABC (see below) doesn't promise the ``append()``
method, while ``MutableSequence`` requires not only ``append()`` but
also various other mutating methods.
To solve these and similar dilemmas, the next section will propose a
metaclass for use with ABCs that will allow us to add an ABC as a
"virtual base class" (not the same concept as in C++) to any class,
including to another ABC. This allows the standard library to define
ABCs ``Sequence`` and ``MutableSequence`` and register these as
virtual base classes for built-in types like ``basestring``, ``tuple``
and ``list``, so that for example the following conditions are all
true::
isinstance([], Sequence)
issubclass(list, Sequence)
issubclass(list, MutableSequence)
isinstance((), Sequence)
not issubclass(tuple, MutableSequence)
isinstance("", Sequence)
issubclass(bytes, MutableSequence)
The primary mechanism proposed here is to allow overloading the
built-in functions ``isinstance()`` and ``issubclass()``. The
overloading works as follows: The call ``isinstance(x, C)`` first
checks whether ``C.__instancecheck__`` exists, and if so, calls
``C.__instancecheck__(x)`` instead of its normal implementation.
Similarly, the call ``issubclass(D, C)`` first checks whether
``C.__subclasscheck__`` exists, and if so, calls
``C.__subclasscheck__(D)`` instead of its normal implementation.
Note that the magic names are not ``__isinstance__`` and
``__issubclass__``; this is because the reversal of the arguments
could cause confusion, especially for the ``issubclass()`` overloader.
A prototype implementation of this is given in [12]_.
Here is an example with (naively simple) implementations of
``__instancecheck__`` and ``__subclasscheck__``::
class ABCMeta(type):
def __instancecheck__(cls, inst):
"""Implement isinstance(inst, cls)."""
return any(cls.__subclasscheck__(c)
for c in {type(inst), inst.__class__})
def __subclasscheck__(cls, sub):
"""Implement issubclass(sub, cls)."""
candidates = cls.__dict__.get("__subclass__", set()) | {cls}
return any(c in candidates for c in sub.mro())
class Sequence(metaclass=ABCMeta):
__subclass__ = {list, tuple}
assert issubclass(list, Sequence)
assert issubclass(tuple, Sequence)
class AppendableSequence(Sequence):
__subclass__ = {list}
assert issubclass(list, AppendableSequence)
assert isinstance([], AppendableSequence)
assert not issubclass(tuple, AppendableSequence)
assert not isinstance((), AppendableSequence)
The next section proposes a full-fledged implementation.
The ``abc`` Module: an ABC Support Framework
--------------------------------------------
The new standard library module ``abc``, written in pure Python,
serves as an ABC support framework. It defines a metaclass
``ABCMeta`` and a decorator ``@abstractmethod``. A sample
implementation is given by [13]_.
The ``ABCMeta`` class overrides ``__instancecheck__`` and
``__subclasscheck__`` and defines a ``register`` method. The
``register`` method takes one argument, which much be a class; after
the call ``B.register(C)``, the call ``issubclass(C, B)`` will return
True, by virtue of of ``B.__subclasscheck__(C)`` returning True.
Also, ``isinstance(x, B)`` is equivalent to ``issubclass(x.__class__,
B) or issubclass(type(x), B)``. (It is possible ``type(x)`` and
``x.__class__`` are not the same object, e.g. when x is a proxy
object.)
These methods are intended to be be called on classes whose metaclass
is (derived from) ``ABCMeta``; for example::
from abc import ABCMeta
class MyABC(metaclass=ABCMeta):
pass
MyABC.register(tuple)
assert issubclass(tuple, MyABC)
assert isinstance((), MyABC)
The last two asserts are equivalent to the following two::
assert MyABC.__subclasscheck__(tuple)
assert MyABC.__instancecheck__(())
Of course, you can also directly subclass MyABC::
class MyClass(MyABC):
pass
assert issubclass(MyClass, MyABC)
assert isinstance(MyClass(), MyABC)
Also, of course, a tuple is not a ``MyClass``::
assert not issubclass(tuple, MyClass)
assert not isinstance((), MyClass)
You can register another class as a subclass of ``MyClass``::
MyClass.register(list)
assert issubclass(list, MyClass)
assert issubclass(list, MyABC)
You can also register another ABC::
class AnotherClass(metaclass=ABCMeta):
pass
AnotherClass.register(basestring)
MyClass.register(AnotherClass)
assert isinstance(str, MyABC)
That last assert requires tracing the following superclass-subclass
relationships::
MyABC -> MyClass (using regular subclassing)
MyClass -> AnotherClass (using registration)
AnotherClass -> basestring (using registration)
basestring -> str (using regular subclassing)
The ``abc`` module also defines a new decorator, ``@abstractmethod``,
to be used to declare abstract methods. A class containing at least
one method declared with this decorator that hasn't been overridden
yet cannot be instantiated. Such a methods may be called from the
overriding method in the subclass (using ``super`` or direct
invocation). For example::
from abc import ABCMeta, abstractmethod
class A(metaclass=ABCMeta):
@abstractmethod
def foo(self): pass
@ -157,14 +329,17 @@ in the subclass (using ``super`` or direct invocation). For example::
C() # works
**Note:** The ``@abstractmethod`` decorator should only be used inside
a class body. Dynamically adding abstract methods to a class, or
**Notes:** The ``@abstractmethod`` decorator should only be used
inside a class body, and only for classes whose metaclass is (derived
from) ``ABCMeta``. Dynamically adding abstract methods to a class, or
attempting to modify the abstraction status of a method or class once
it is created, are not supported.
it is created, are not supported. The ``@abstractmethod`` only
affects subclasses derived using regular inheritance; "virtual
subclasses" registered with the ``register()`` method are not affected.
**Implementation:** The ``@abstractmethod`` decorator sets the
function attribute ``__isabstractmethod__`` to the value ``True``.
The ``type.__new__`` method computes the type attribute
The ``ABCMeta.__new__`` method computes the type attribute
``__abstractmethods__`` as the set of all method names that have an
``__isabstractmethod__`` attribute whose value is true. It does this
by combining the ``__abstractmethods__`` attributes of the base
@ -174,8 +349,8 @@ of all methods in the new class dict that don't have a true
``__isabstractmethod__`` attribute. If the resulting
``__abstractmethods__`` set is non-empty, the class is considered
abstract, and attempts to instantiate it will raise ``TypeError``.
(CPython can uses an internal flag ``Py_TPFLAGS_ABSTRACT`` to speed up
this check [6]_.)
(If this were implemented in CPython, an internal flag
``Py_TPFLAGS_ABSTRACT`` could be used to speed up this check [6]_.)
**Discussion:** Unlike C++ or Java, abstract methods as defined here
may have an implementation. This implementation can be called via the
@ -186,77 +361,8 @@ cooperative multiple-inheritance [7]_, [8]_.
**Open issues:** Should we also provide a standard way to declare
abstract data attributes? If so, how should these be spelled?
Perhaps place ``@abstractattribute`` decorators on properties? Or use
an ``@attributes(name1, name2, ...)`` class decorator?
Overloading ``isinstance()`` and ``issubclass()``
-------------------------------------------------
During the development of this PEP and of its companion, PEP 3141, we
repeatedly faced the choice between standardizing more, fine-grained
ABCs or fewer, course-grained ones. For example, at one stage, PEP
3141 introduced the following stack of base classes used for complex
numbers: MonoidUnderPlus, AdditiveGroup, Ring, Field, Complex (each
derived from the previous). And the discussion mentioned several
other algebraic categorizations that were left out: Algebraic,
Transcendental, and IntegralDomain, and PrincipalIdealDomain. In this
PEP, we are wondering about the use cases for separate classes like
Set, ComposableSet, MutableSet, HashableSet, MutableComposableSet,
HashableComposableSet.
The dilemma here is that we'd rather have fewer ABCs, but then what
should a user do who needs a less refined ABC? Consider e.g. the
plight of a mathematician who wants to define his own kind of
Transcendental numbers, but also wants float and int to be considered
Transcendental. PEP 3141 originally proposed to patch float.__bases__
for that purpose, but there are some good reasons to keep the built-in
types immutable (for one, they are shared between all Python
interpreters running in the same address space, as is used by
mod_python).
The solution proposed here is to allow overloading the built-in
functions ``isinstance()`` and ``issubclass()``. The overloading
works as follows: The call ``isinstance(x, C)`` first checks whether
``C.__instancecheck__`` exists, and if so, calls
``C.__instancecheck__(x)`` instead of its normal implementation.
Similarly, the call ``issubclass(D, C)`` first checks whether
``C.__subclasscheck__`` exists, and if so, calls
``C.__subclasscheck__(D)`` instead of its normal implementation.
Note that the magic names are not ``__isinstance__`` and
``__issubclass__``; this is because the reversal of the arguments
could cause confusion, especially for the ``issubclass()`` overloader.
(We could also provide a default implementation of these that
implements the old algorithms; this would be more regular but would
have additional backwards compatibility issues, since the old
algorithms special-case objects not deriving from ``type`` in order to
support a different kind of overloading of these operations.)
A prototype implementation of this is given in [12]_.
Here is an example with (very simple) implementations of
``__instancecheck__`` and ``__subclasscheck__``::
assert issubclass(set, HashableSet) # Assume this is given
class ABC:
def __instancecheck__(cls, inst):
"""Implement isinstance(inst, cls)."""
return any(cls.__subclasscheck__(c)
for c in {type(inst), inst.__class__})
def __subclasscheck__(cls, sub):
"""Implement issubclass(sub, cls)."""
candidates = cls.__dict__.get("__subclass__", set()) | {cls}
return any(c in candidates for c in sub.mro())
class NoncomposableHashableSet(Set, Hashable, metaclass=ABC):
__subclass__ = {HashableSet}
assert issubclass(set, NoncomposableHashableSet)
assert isinstance({1, 2, 3}, NoncomposableHashableSet)
an ``@attributes(name1, name2, ...)`` class decorator? Strawman:
let's back off on this; it's easy enough to add this later.
ABCs for Containers and Iterators
@ -831,6 +937,9 @@ References
.. [12] Make isinstance/issubclass overloadable
(http://python.org/sf/1708353)
.. [13] ABCMeta sample implementation
(http://svn.python.org/view/sandbox/trunk/abc/xyz.py)
Copyright
=========