Some more writing, specifying the core elements of the class-based

introspection API.  I'll add attribute descriptors tomorrow.
This commit is contained in:
Guido van Rossum 2001-04-20 04:01:57 +00:00
parent e480cceffe
commit eb32cf6554
1 changed files with 171 additions and 32 deletions

View File

@ -1,7 +1,7 @@
PEP: 252 PEP: 252
Title: Making Types Look More Like Classes Title: Making Types Look More Like Classes
Version: $Revision$ Version: $Revision$
Author: guido@python.org (Jeremy Hylton) Author: guido@python.org (Guido van Rossum)
Status: Draft Status: Draft
Type: Standards Track Type: Standards Track
Python-Version: 2.2 Python-Version: 2.2
@ -47,17 +47,19 @@ Introspection APIs
impossible to guarantee that there always is a way to get a list impossible to guarantee that there always is a way to get a list
of all attributes supported by a specific object, but in practice of all attributes supported by a specific object, but in practice
two conventions have appeared that together work for almost all two conventions have appeared that together work for almost all
objects. objects. I'll call them the class-based introspection API and the
type-based introspection API; class API and type API for short.
The first API is used primarily for class instances; it is also The class-based introspection API is used primarily for class
used by Digital Creations's ExtensionClasses. It assumes that all instances; it is also used by Digital Creations's
data attributes of an object x are stored in the dictionary ExtensionClasses. It assumes that all data attributes of an
x.__dict__, and that all methods and class variables can be found object x are stored in the dictionary x.__dict__, and that all
by inspection of x's class, written as x.__class__. Classes have methods and class variables can be found by inspection of x's
a __dict__ attribute, which yields a dictionary containing methods class, written as x.__class__. Classes have a __dict__ attribute,
and class variables defined by the class itself, and a __bases__ which yields a dictionary containing methods and class variables
attribute, which is a tuple of base classes that must be inspected defined by the class itself, and a __bases__ attribute, which is a
recursively. Some assumption here are: tuple of base classes that must be inspected recursively. Some
assumption here are:
- attributes defined in the instance dict override attributes - attributes defined in the instance dict override attributes
defined by the object's class; defined by the object's class;
@ -71,17 +73,18 @@ Introspection APIs
(The last two rules together are often summarized as the (The last two rules together are often summarized as the
left-to-right, depth-first rule for attribute search.) left-to-right, depth-first rule for attribute search.)
The second introspection API is supported in one form or another The type-based introspection API is supported in one form or
by most built-in objects. It uses two special attributes, another by most built-in objects. It uses two special attributes,
__members__ and __methods__. The __members__ attribute, if __members__ and __methods__. The __members__ attribute, if
present, is a list of method names supported by the object. The present, is a list of method names supported by the object. The
__methods__ attribute, if present, is a list of data attribute __methods__ attribute, if present, is a list of data attribute
names supported by the object. names supported by the object.
This API is sometimes combined by a __dict__ that works the same The type API is sometimes combined by a __dict__ that works the
was as for instances (e.g., for function objects in Python 2.1, same was as for instances (e.g., for function objects in Python
f.__dict__ contains f's dynamic attributes, while f.__members__ 2.1, f.__dict__ contains f's dynamic attributes, while
lists the names of f's statically defined attributes). f.__members__ lists the names of f's statically defined
attributes).
Some caution must be exercised: some objects don't list theire Some caution must be exercised: some objects don't list theire
"intrinsic" attributes (e.g. __dict__ and __doc__) in __members__, "intrinsic" attributes (e.g. __dict__ and __doc__) in __members__,
@ -90,29 +93,161 @@ Introspection APIs
it's anybody's guess whether the value found in __dict__ is used it's anybody's guess whether the value found in __dict__ is used
or not. or not.
This second introspection API has never been carefully specified. The type API has never been carefully specified. It is part of
It is part of folklore, and most 3rd party extensions support it Python folklore, and most third party extensions support it
because they follow examples that support it. Also, any type that because they follow examples that support it. Also, any type that
uses Py_FindMethod() and/or PyMember_Get() in its tp_getattr uses Py_FindMethod() and/or PyMember_Get() in its tp_getattr
handler supports it, because these two functions special-case the handler supports it, because these two functions special-case the
attribute names __methods__ and __members__, respectively. attribute names __methods__ and __members__, respectively.
Digital Creations's ExtensionClasses ignore the second Digital Creations's ExtensionClasses ignore the type API, and
introspection API, and instead emulate the class instance instead emulate the class API, which is more powerful. In this
introspection API, which is more powerful. In this PEP, I propose PEP, I propose to phase out the type API in favor of supporting
to phase out the second API in favor of supporting the class the class API for all types.
instance introspection API for all types.
One argument in favor of the class instance introspection API is One argument in favor of the class API is that it doesn't require
that it doesn't require you to create an instance in order to find you to create an instance in order to find out which attributes a
out which attributes a type supports; this in turn is useful for type supports; this in turn is useful for documentation
documentation processors. For example, the socket module exports processors. For example, the socket module exports the SocketType
the SocketType object, but this currently doesn't tell us what object, but this currently doesn't tell us what methods are
methods are defined on socket objects. defined on socket objects. Using the class API, SocketType shows
us exactly what the methods for socket objects are, and we can
even extract their docstrings, without creating a socket. (Since
this is a C extension module, the source-scanning approach to
docstring extraction isn't feasible in this case.)
Specification Specification of the class-based introspection API
XXX Objects may have two kinds of attributes: static and dynamic. The
names and sometimes other properties of static attributes are
knowable by inspection of the object's type or class, which is
accessible through obj.__class__ or type(obj). (I'm using type
and class interchangeably, because that's the goal of the
exercise.)
(XXX static and dynamic are lousy names, because the "static"
attributes may actually behave quite dynamically.)
The names and values of dynamic properties are typically stored in
a dictionary, and this dictionary is typically accessible as
obj.__dict__. The rest of this specification is more concerned
with discovering the names and properties of static attributes
than with dynamic attributes.
Examples of dynamic attributes are instance variables of class
instances, module attributes, etc. Examples of static attributes
are the methods of built-in objects like lists and dictionaries,
and the attributes of frame and code objects (c.co_code,
c.co_filename, etc.). When an object with dynamic attributes
exposes these through its __dict__ attribute, __dict__ is a static
attribute.
In the discussion below, I distinguish two kinds of objects:
regular objects (e.g. lists, ints, functions) and meta-objects.
Meta-objects are types and classes. Meta-objects are also regular
objects, but we're mostly interested in them because they are
referenced by the __class__ attribute of regular objects (or by
the __bases__ attribute of meta-objects).
The class introspection API consists of the following elements:
- the __class__ and __dict__ attributes on regular objects;
- the __bases__ and __dict__ attributes on meta-objects;
- precedence rules;
- attribute descriptors.
1. The __dict__ attribute on regular objects
A regular object may have a __dict__ attribute. If it does,
this should be a mapping (not necessarily a dictionary)
supporting at least __getitem__, keys(), and has_item(). This
gives the dynamic attributes of the object. The keys in the
mapping give attribute names, and the corresponding values give
their values.
Typically, the value of an attribute with a given name is the
same object as the value corresponding to that name as a key in
the __dict__. In othe words, obj.__dict__['spam'] is obj.spam.
(But see the precedence rules below; a static attribute with
the same name *may* override the dictionary item.)
2. The __class__ attribute on regular objects
A regular object may have a __class__ attributes. If it does,
this references a meta-object. A meta-object can define static
attributes for the regular object whose __class__ it is.
3. The __dict__ attribute on meta-objects
A meta-object may have a __dict__ attribute, of the same form
as the __dict__ attribute for regular objects (mapping, etc).
If it does, the keys of the meta-object's __dict__ are names of
static attributes for the corresponding regular object. The
values are attribute descriptors; we'll explain these later.
(An unbound method is a special case of an attribute
descriptor.)
Becase a meta-object is also a regular object, the items in a
meta-object's __dict__ correspond to attributes of the
meta-object; however, some transformation may be applied, and
bases (see below) may define additional dynamic attributes. In
other words, mobj.spam is not always mobj.__dict__['spam'].
(This rule contains a loophole because for classes, if
C.__dict__['spam'] is a function, C.spam is an unbound method
object.)
4. The __bases__ attribute on meta-objects
A meta-object may have a __bases__ attribute. If it does, this
should be a sequence (not necessarily a tuple) of other
meta-objects, the bases. An absent __bases__ is equivalent to
an empty sequece of bases. There must never be a cycle in the
relationship between meta objects defined by __bases__
attributes; in other words, the __bases__ attributes define an
inheritance tree, where the root of the tree is the __class__
attribute of a regular object, and the leaves of the trees are
meta-objects without bases. The __dict__ attributes of the
meta-objects in the inheritance tree supply attribute
descriptors for the regular object whose __class__ is at the
top of the inheritance tree.
5. Precedence rules
When two meta-objects in the inheritance tree both define an
attribute descriptor with the same name, the left-to-right
depth-first rule applies. (XXX define rigorously.)
When a dynamic attribute (one defined in a regular object's
__dict__) has the same name as a static attribute (one defined
by a meta-object in the inheritance tree rooted at the regular
object's __class__), the dynamic attribute *usually* wins, but
for some attributes the meta-object may specify that the static
attribute overrides the dynamic attribute.
(We can't have a simples rule like "static overrides dynamic"
or "dynamic overrides static", because some static attributes
indeed override dynamic attributes, e.g. a key '__class__' in
an instance's __dict__ is ignored in favor of the statically
defined __class__ pointer, but on the other hand most keys in
inst.__dict__ override attributes defined in inst.__class__.
The mechanism whereby a meta-object can specify that a
particular attribute has precedence is not yet specified.)
6. Attribute descriptors
XXX
The introspection API is a read-only API. We don't define the
effect of assignment to any of the special attributes (__dict__,
__class__ and __bases__), nor the effect of assignment to the
items of a __dict__. Generally, such assignments should be
considered off-limits. An extension of this PEP may define some
semantics for some such assignments. (Especially because
currently instances support assignment to __class__ and __dict__,
and classes support assignment to __bases__ and __dict__.)
Discussion Discussion
@ -142,6 +277,10 @@ References
XXX XXX
Copyright
This document has been placed in the public domain.
Local Variables: Local Variables:
mode: indented-text mode: indented-text