Some more writing, specifying the core elements of the class-based
introspection API. I'll add attribute descriptors tomorrow.
This commit is contained in:
parent
e480cceffe
commit
eb32cf6554
203
pep-0252.txt
203
pep-0252.txt
|
@ -1,7 +1,7 @@
|
|||
PEP: 252
|
||||
Title: Making Types Look More Like Classes
|
||||
Version: $Revision$
|
||||
Author: guido@python.org (Jeremy Hylton)
|
||||
Author: guido@python.org (Guido van Rossum)
|
||||
Status: Draft
|
||||
Type: Standards Track
|
||||
Python-Version: 2.2
|
||||
|
@ -47,17 +47,19 @@ Introspection APIs
|
|||
impossible to guarantee that there always is a way to get a list
|
||||
of all attributes supported by a specific object, but in practice
|
||||
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
|
||||
used by Digital Creations's ExtensionClasses. It assumes that all
|
||||
data attributes of an object x are stored in the dictionary
|
||||
x.__dict__, and that all methods and class variables can be found
|
||||
by inspection of x's class, written as x.__class__. Classes have
|
||||
a __dict__ attribute, which yields a dictionary containing methods
|
||||
and class variables defined by the class itself, and a __bases__
|
||||
attribute, which is a tuple of base classes that must be inspected
|
||||
recursively. Some assumption here are:
|
||||
The class-based introspection API is used primarily for class
|
||||
instances; it is also used by Digital Creations's
|
||||
ExtensionClasses. It assumes that all data attributes of an
|
||||
object x are stored in the dictionary x.__dict__, and that all
|
||||
methods and class variables can be found by inspection of x's
|
||||
class, written as x.__class__. Classes have a __dict__ attribute,
|
||||
which yields a dictionary containing methods and class variables
|
||||
defined by the class itself, and a __bases__ attribute, which is a
|
||||
tuple of base classes that must be inspected recursively. Some
|
||||
assumption here are:
|
||||
|
||||
- attributes defined in the instance dict override attributes
|
||||
defined by the object's class;
|
||||
|
@ -71,17 +73,18 @@ Introspection APIs
|
|||
(The last two rules together are often summarized as the
|
||||
left-to-right, depth-first rule for attribute search.)
|
||||
|
||||
The second introspection API is supported in one form or another
|
||||
by most built-in objects. It uses two special attributes,
|
||||
The type-based introspection API is supported in one form or
|
||||
another by most built-in objects. It uses two special attributes,
|
||||
__members__ and __methods__. The __members__ attribute, if
|
||||
present, is a list of method names supported by the object. The
|
||||
__methods__ attribute, if present, is a list of data attribute
|
||||
names supported by the object.
|
||||
|
||||
This API is sometimes combined by a __dict__ that works the same
|
||||
was as for instances (e.g., for function objects in Python 2.1,
|
||||
f.__dict__ contains f's dynamic attributes, while f.__members__
|
||||
lists the names of f's statically defined attributes).
|
||||
The type API is sometimes combined by a __dict__ that works the
|
||||
same was as for instances (e.g., for function objects in Python
|
||||
2.1, f.__dict__ contains f's dynamic attributes, while
|
||||
f.__members__ lists the names of f's statically defined
|
||||
attributes).
|
||||
|
||||
Some caution must be exercised: some objects don't list theire
|
||||
"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
|
||||
or not.
|
||||
|
||||
This second introspection API has never been carefully specified.
|
||||
It is part of folklore, and most 3rd party extensions support it
|
||||
The type API has never been carefully specified. It is part of
|
||||
Python folklore, and most third party extensions support it
|
||||
because they follow examples that support it. Also, any type that
|
||||
uses Py_FindMethod() and/or PyMember_Get() in its tp_getattr
|
||||
handler supports it, because these two functions special-case the
|
||||
attribute names __methods__ and __members__, respectively.
|
||||
|
||||
Digital Creations's ExtensionClasses ignore the second
|
||||
introspection API, and instead emulate the class instance
|
||||
introspection API, which is more powerful. In this PEP, I propose
|
||||
to phase out the second API in favor of supporting the class
|
||||
instance introspection API for all types.
|
||||
Digital Creations's ExtensionClasses ignore the type API, and
|
||||
instead emulate the class API, which is more powerful. In this
|
||||
PEP, I propose to phase out the type API in favor of supporting
|
||||
the class API for all types.
|
||||
|
||||
One argument in favor of the class instance introspection API is
|
||||
that it doesn't require you to create an instance in order to find
|
||||
out which attributes a type supports; this in turn is useful for
|
||||
documentation processors. For example, the socket module exports
|
||||
the SocketType object, but this currently doesn't tell us what
|
||||
methods are defined on socket objects.
|
||||
One argument in favor of the class API is that it doesn't require
|
||||
you to create an instance in order to find out which attributes a
|
||||
type supports; this in turn is useful for documentation
|
||||
processors. For example, the socket module exports the SocketType
|
||||
object, but this currently doesn't tell us what methods are
|
||||
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
|
||||
|
||||
|
@ -142,6 +277,10 @@ References
|
|||
|
||||
XXX
|
||||
|
||||
Copyright
|
||||
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
|
|
Loading…
Reference in New Issue