Commit f0860413 authored by Jim Fulton's avatar Jim Fulton

Removed previous ExtensionClass implementation.

The new implementation is provided as a collection of individual
packages, corresponding to the original extension modules.
parent 894c7cc6
Zope Public License (ZPL) Version 2.0
-----------------------------------------------
This software is Copyright (c) Zope Corporation (tm) and
Contributors. All rights reserved.
This license has been certified as open source. It has also
been designated as GPL compatible by the Free Software
Foundation (FSF).
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:
1. Redistributions in source code must retain the above
copyright notice, this list of conditions, and the following
disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions, and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
3. The name Zope Corporation (tm) must not be used to
endorse or promote products derived from this software
without prior written permission from Zope Corporation.
4. The right to distribute this software or to use it for
any purpose does not give you the right to use Servicemarks
(sm) or Trademarks (tm) of Zope Corporation. Use of them is
covered in a separate agreement (see
http://www.zope.com/Marks).
5. If any files are modified, you must cause the modified
files to carry prominent notices stating that you changed
the files and the date of any change.
Disclaimer
THIS SOFTWARE IS PROVIDED BY ZOPE CORPORATION ``AS IS''
AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL ZOPE CORPORATION OR ITS CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
This software consists of contributions made by Zope
Corporation and many individuals on behalf of Zope
Corporation. Specific attributions are listed in the
accompanying credits file.
See the file doc/ExtensionClass.stx or doc/ExtensionClass.html for a
description of extension class.
This version of ExtensionClass uses Python distutils for installation.
It requires Python version 1.5.2 or higher. To build, test, and
install, do the following:
% python setup.py build
% PYTHONPATH=build/lib-PLAT-VER python test/regrtest.py
% python setup.py install
The build directory is created setup.py during the build. The exact
name of the build subdirectory depends on the platform you are using
and the version of Python.
For more information, contact info@zope.com
# Compatibility module
# $Id: Xaq.py,v 1.1 1997/06/19 19:26:30 jim Exp $
import Acquisition
Acquirer = Acquisition.Explicit
<h1>Acquisition</h1>
<p> <a href="COPYRIGHT.html">Copyright (C) 1996-1998, Digital Creations</a>.</p>
<p> Acquisition <a href="#1">[1]</a> is a mechanism that allows objects to obtain
attributes from their environment. It is similar to inheritence,
except that, rather than traversing an inheritence hierarchy
to obtain attributes, a containment hierarchy is traversed.</p>
<p> The <a href="ExtensionClass.html">ExtensionClass</a>. release includes mix-in
extension base classes that can be used to add acquisition as a
feature to extension subclasses. These mix-in classes use the
context-wrapping feature of ExtensionClasses to implement
acquisition. Consider the following example:</p>
<PRE>
import ExtensionClass, Acquisition
class C(ExtensionClass.Base):
color='red'
class A(Acquisition.Implicit):
def report(self):
print self.color
a=A()
c=C()
c.a=A()
c.a.report() # prints 'red'
d=C()
d.color='green'
d.a=a
d.a.report() # prints 'green'
a.report() # raises an attribute error
</PRE>
<p> The class <code>A</code> inherits acquisition behavior from
<code>Acquisition.Implicit</code>. The object, <code>a</code>, "has" the color of
objects <code>c</code> and <code>d</code> when it is accessed through them, but it
has no color by itself. The object <code>a</code> obtains attributes
from it's environment, where it's environment is defined by
the access path used to reach <code>a</code>.</p>
<h2>Acquisition wrappers</h2>
<p> When an object that supports acquisition is accessed through
an extension class instance, a special object, called an
acquisition wrapper, is returned. In the example above, the
expression <code>c.a</code> returns an acquisition wrapper that
contains references to both <code>c</code> and <code>a</code>. It is this wrapper
that performs attribute lookup in <code>c</code> when an attribute
cannot be found in <code>a</code>.</p>
<p> Aquisition wrappers provide access to the wrapped objects
through the attributes <code>aq_parent</code>, <code>aq_self</code>, <code>aq_base</code>.
In the example above, the expressions:</p>
<PRE>
'c.a.aq_parent is c'
</PRE>
<p> and:</p>
<PRE>
'c.a.aq_self is a'
</PRE>
<p> both evaluate to true, but the expression:</p>
<PRE>
'c.a is a'
</PRE>
<p> evaluates to false, because the expression <code>c.a</code> evaluates
to an acquisition wrapper around <code>c</code> and <code>a</code>, not <code>a</code> itself.</p>
<p> The attribute <code>aq_base</code> is similar to <code>aq_self</code>. Wrappers may be
nested and <code>aq_self</code> may be a wrapped object. The <code>aq_base</code>
attribute is the underlying object with all wrappers removed.</p>
<h2>Acquisition Control</h2>
<p> Two styles of acquisition are supported in the current
ExtensionClass release, implicit and explicit aquisition.</p>
<h3>Implicit acquisition</h3>
<p> Implicit acquisition is so named because it searches for
attributes from the environment automatically whenever an
attribute cannot be obtained directly from an object or
through inheritence.</p>
<p> An attribute may be implicitly acquired if it's name does
not begin with an underscore, <code>_</code>.</p>
<p> To support implicit acquisition, an object should inherit
from the mix-in class <code>Acquisition.Implicit</code>.</p>
<h3>Explicit Acquisition</h3>
<p> When explicit acquisition is used, attributes are not
automatically obtained from the environment. Instead, the
method <code>aq_aquire</code> must be used, as in:</p>
<PRE>
print c.a.aq_acquire('color')
</PRE>
<p> To support explicit acquisition, an object should inherit
from the mix-in class <code>Acquisition.Explicit</code>.</p>
<h3>Controlled Acquisition</h3>
<p> A class (or instance) can provide attribute by attribute control
over acquisition. This is done by:</p>
<ul><li><p>subclassing from <code>Acquisition.Explicit</code>, and</p>
</li>
<li><p>setting all attributes that should be acquired to the special
value: <code>Acquisition.Acquired</code>. Setting an attribute to this
value also allows inherited attributes to be overridden with
acquired ones.</p>
<p> For example, in:</p>
<PRE>
class C(Acquisition.Explicit):
id=1
secret=2
color=Acquisition.Acquired
__roles__=Acquisition.Acquired
</PRE>
<p> The <em>only</em> attributes that are automatically acquired from
containing objects are <code>color</code>, and <code>__roles__</code>. Note also
that the <code>__roles__</code> attribute is acquired even though it's
name begins with an underscore. In fact, the special
<code>Acquisition.Acquired</code> value can be used in
<code>Acquisition.Implicit</code> objects to implicitly acquire selected
objects that smell like private objects.</p>
</li></ul>
<h3>Filtered Acquisition</h3>
<p> The acquisition method, <code>aq_acquire</code>, accepts two optional
arguments. The first of the additional arguments is a
"filtering" function that is used when considering whether to
acquire an object. The second of the additional arguments is an
object that is passed as extra data when calling the filtering
function and which defaults to <code>None</code>.</p>
<p> The filter function is called with five arguments:</p>
<ul><li><p>The object that the <code>aq_acquire</code> method was called on,</p>
</li>
<li><p>The object where an object was found,</p>
</li>
<li><p>The name of the object, as passed to <code>aq_acquire</code>,</p>
</li>
<li><p>The object found, and</p>
</li>
<li><p>The extra data passed to <code>aq_acquire</code>.</p>
</li></ul>
<p> If the filter returns a true object that the object found is
returned, otherwise, the acquisition search continues.</p>
<p> For example, in:</p>
<PRE>
from Acquisition import Explicit
class HandyForTesting:
def __init__(self, name): self.name=name
def __str__(self):
return &quot;%s(%s)&quot; % (self.name, self.__class__.__name__)
__repr__=__str__
class E(Explicit, HandyForTesting): pass
class Nice(HandyForTesting):
isNice=1
def __str__(self):
return HandyForTesting.__str__(self)+' and I am nice!'
__repr__=__str__
a=E('a')
a.b=E('b')
a.b.c=E('c')
a.p=Nice('spam')
a.b.p=E('p')
def find_nice(self, ancestor, name, object, extra):
return hasattr(object,'isNice') and object.isNice
print a.b.c.aq_acquire('p', find_nice)
</PRE>
<p> The filtered acquisition in the last line skips over the first
attribute it finds with the name <code>p</code>, because the attribute
doesn't satisfy the condition given in the filter. The output of
the last line is:</p>
<PRE>
spam(Nice) and I am nice!
</PRE>
<h2>Acquisition and methods</h2>
<p> Python methods of objects that support acquisition can use
acquired attributes as in the <code>report</code> method of the first example
above. When a Python method is called on an object that is
wrapped by an acquisition wrapper, the wrapper is passed to the
method as the first argument. This rule also applies to
user-defined method types and to C methods defined in pure mix-in
classes.</p>
<p> Unfortunately, C methods defined in extension base classes that
define their own data structures, cannot use aquired attributes at
this time. This is because wrapper objects do not conform to the
data structures expected by these methods.</p>
<h2>Acquiring Acquiring objects</h2>
<p> Consider the following example:</p>
<PRE>
from Acquisition import Implicit
class C(Implicit):
def __init__(self, name): self.name=name
def __str__(self):
return &quot;%s(%s)&quot; % (self.name, self.__class__.__name__)
__repr__=__str__
a=C(&quot;a&quot;)
a.b=C(&quot;b&quot;)
a.b.pref=&quot;spam&quot;
a.b.c=C(&quot;c&quot;)
a.b.c.color=&quot;red&quot;
a.b.c.pref=&quot;eggs&quot;
a.x=C(&quot;x&quot;)
o=a.b.c.x
</PRE>
<p> The expression <code>o.color</code> might be expected to return <code>"red"</code>. In
earlier versions of ExtensionClass, however, this expression
failed. Acquired acquiring objects did not acquire from the
environment they were accessed in, because objects were only
wrapped when they were first found, and were not rewrapped as they
were passed down the acquisition tree.</p>
<p> In the current release of ExtensionClass, the expression "o.color"
does indeed return <code>"red"</code>.</p>
<p> When searching for an attribute in <code>o</code>, objects are searched in
the order <code>x</code>, <code>a</code>, <code>b</code>, <code>c</code>. So, for example, the expression,
<code>o.pref</code> returns <code>"spam"</code>, not <code>"eggs"</code>. In earlier releases of
ExtensionClass, the attempt to get the <code>pref</code> attribute from <code>o</code>
would have failed.</p>
<p> If desired, the current rules for looking up attributes in complex
expressions can best be understood through repeated application of
the <code>__of__</code> method:</p>
<dl><dt> <code>a.x</code></dt><dd><p><code>x.__of__(a)</code></p>
</dd>
<dt> <code>a.b</code></dt><dd><p><code>b.__of__(a)</code></p>
</dd>
<dt> <code>a.b.x</code></dt><dd><p><code>x.__of__(a).__of__(b.__of__(a))</code></p>
</dd>
<dt> <code>a.b.c</code></dt><dd><p><code>c.__of__(b.__of__(a))</code></p>
</dd>
<dt> <code>a.b.c.x</code></dt><dd><p><code>x.__of__(a).__of__(b.__of__(a)).__of__(c.__of__(b.__of__(a)))</code></p>
</dd></dl>
<p> and by keeping in mind that attribute lookup in a wrapper
is done by trying to lookup the attribute in the wrapped object
first and then in the parent object. In the expressions above
involving the <code>__of__</code> method, lookup proceeds from left to right.</p>
<p> Note that heuristics are used to avoid most of the repeated
lookups. For example, in the expression: <code>a.b.c.x.foo</code>, the object
<code>a</code> is searched no more than once, even though it is wrapped three
times.</p>
<p> <a name="1">[1]</a> Gil, J., Lorenz, D.,
<a href="http://www.bell-labs.com/people/cope/oopsla/Oopsla96TechnicalProgramAbstracts.html#GilLorenz">Environmental Acquisition--A New Inheritance-Like Abstraction Mechanism</a>
OOPSLA '96 Proceedings, ACM SIG-PLAN, October, 1996</p>
<p>
<TABLE BORDER=1 CELLPADDING=2>
</TABLE></p>
Acquisition
"Copyright (C) 1996-1998, Digital Creations":COPYRIGHT.html.
Acquisition [1] is a mechanism that allows objects to obtain
attributes from their environment. It is similar to inheritence,
except that, rather than traversing an inheritence hierarchy
to obtain attributes, a containment hierarchy is traversed.
The "ExtensionClass":ExtensionClass.html. release includes mix-in
extension base classes that can be used to add acquisition as a
feature to extension subclasses. These mix-in classes use the
context-wrapping feature of ExtensionClasses to implement
acquisition. Consider the following example::
import ExtensionClass, Acquisition
class C(ExtensionClass.Base):
color='red'
class A(Acquisition.Implicit):
def report(self):
print self.color
a=A()
c=C()
c.a=A()
c.a.report() # prints 'red'
d=C()
d.color='green'
d.a=a
d.a.report() # prints 'green'
a.report() # raises an attribute error
The class 'A' inherits acquisition behavior from
'Acquisition.Implicit'. The object, 'a', "has" the color of
objects 'c' and 'd' when it is accessed through them, but it
has no color by itself. The object 'a' obtains attributes
from it's environment, where it's environment is defined by
the access path used to reach 'a'.
Acquisition wrappers
When an object that supports acquisition is accessed through
an extension class instance, a special object, called an
acquisition wrapper, is returned. In the example above, the
expression 'c.a' returns an acquisition wrapper that
contains references to both 'c' and 'a'. It is this wrapper
that performs attribute lookup in 'c' when an attribute
cannot be found in 'a'.
Aquisition wrappers provide access to the wrapped objects
through the attributes 'aq_parent', 'aq_self', 'aq_base'.
In the example above, the expressions::
'c.a.aq_parent is c'
and::
'c.a.aq_self is a'
both evaluate to true, but the expression::
'c.a is a'
evaluates to false, because the expression 'c.a' evaluates
to an acquisition wrapper around 'c' and 'a', not 'a' itself.
The attribute 'aq_base' is similar to 'aq_self'. Wrappers may be
nested and 'aq_self' may be a wrapped object. The 'aq_base'
attribute is the underlying object with all wrappers removed.
Acquisition Control
Two styles of acquisition are supported in the current
ExtensionClass release, implicit and explicit aquisition.
Implicit acquisition
Implicit acquisition is so named because it searches for
attributes from the environment automatically whenever an
attribute cannot be obtained directly from an object or
through inheritence.
An attribute may be implicitly acquired if it's name does
not begin with an underscore, '_'.
To support implicit acquisition, an object should inherit
from the mix-in class 'Acquisition.Implicit'.
Explicit Acquisition
When explicit acquisition is used, attributes are not
automatically obtained from the environment. Instead, the
method 'aq_aquire' must be used, as in::
print c.a.aq_acquire('color')
To support explicit acquisition, an object should inherit
from the mix-in class 'Acquisition.Explicit'.
Controlled Acquisition
A class (or instance) can provide attribute by attribute control
over acquisition. This is done by:
- subclassing from 'Acquisition.Explicit', and
- setting all attributes that should be acquired to the special
value: 'Acquisition.Acquired'. Setting an attribute to this
value also allows inherited attributes to be overridden with
acquired ones.
For example, in::
class C(Acquisition.Explicit):
id=1
secret=2
color=Acquisition.Acquired
__roles__=Acquisition.Acquired
The *only* attributes that are automatically acquired from
containing objects are 'color', and '__roles__'. Note also
that the '__roles__' attribute is acquired even though it's
name begins with an underscore. In fact, the special
'Acquisition.Acquired' value can be used in
'Acquisition.Implicit' objects to implicitly acquire selected
objects that smell like private objects.
Filtered Acquisition
The acquisition method, 'aq_acquire', accepts two optional
arguments. The first of the additional arguments is a
"filtering" function that is used when considering whether to
acquire an object. The second of the additional arguments is an
object that is passed as extra data when calling the filtering
function and which defaults to 'None'.
The filter function is called with five arguments:
- The object that the 'aq_acquire' method was called on,
- The object where an object was found,
- The name of the object, as passed to 'aq_acquire',
- The object found, and
- The extra data passed to 'aq_acquire'.
If the filter returns a true object that the object found is
returned, otherwise, the acquisition search continues.
For example, in::
from Acquisition import Explicit
class HandyForTesting:
def __init__(self, name): self.name=name
def __str__(self):
return "%s(%s)" % (self.name, self.__class__.__name__)
__repr__=__str__
class E(Explicit, HandyForTesting): pass
class Nice(HandyForTesting):
isNice=1
def __str__(self):
return HandyForTesting.__str__(self)+' and I am nice!'
__repr__=__str__
a=E('a')
a.b=E('b')
a.b.c=E('c')
a.p=Nice('spam')
a.b.p=E('p')
def find_nice(self, ancestor, name, object, extra):
return hasattr(object,'isNice') and object.isNice
print a.b.c.aq_acquire('p', find_nice)
The filtered acquisition in the last line skips over the first
attribute it finds with the name 'p', because the attribute
doesn't satisfy the condition given in the filter. The output of
the last line is::
spam(Nice) and I am nice!
Acquisition and methods
Python methods of objects that support acquisition can use
acquired attributes as in the 'report' method of the first example
above. When a Python method is called on an object that is
wrapped by an acquisition wrapper, the wrapper is passed to the
method as the first argument. This rule also applies to
user-defined method types and to C methods defined in pure mix-in
classes.
Unfortunately, C methods defined in extension base classes that
define their own data structures, cannot use aquired attributes at
this time. This is because wrapper objects do not conform to the
data structures expected by these methods.
Acquiring Acquiring objects
Consider the following example::
from Acquisition import Implicit
class C(Implicit):
def __init__(self, name): self.name=name
def __str__(self):
return "%s(%s)" % (self.name, self.__class__.__name__)
__repr__=__str__
a=C("a")
a.b=C("b")
a.b.pref="spam"
a.b.c=C("c")
a.b.c.color="red"
a.b.c.pref="eggs"
a.x=C("x")
o=a.b.c.x
The expression 'o.color' might be expected to return '"red"'. In
earlier versions of ExtensionClass, however, this expression
failed. Acquired acquiring objects did not acquire from the
environment they were accessed in, because objects were only
wrapped when they were first found, and were not rewrapped as they
were passed down the acquisition tree.
In the current release of ExtensionClass, the expression "o.color"
does indeed return '"red"'.
When searching for an attribute in 'o', objects are searched in
the order 'x', 'a', 'b', 'c'. So, for example, the expression,
'o.pref' returns '"spam"', not '"eggs"'. In earlier releases of
ExtensionClass, the attempt to get the 'pref' attribute from 'o'
would have failed.
If desired, the current rules for looking up attributes in complex
expressions can best be understood through repeated application of
the '__of__' method:
'a.x' -- 'x.__of__(a)'
'a.b' -- 'b.__of__(a)'
'a.b.x' -- 'x.__of__(a).__of__(b.__of__(a))'
'a.b.c' -- 'c.__of__(b.__of__(a))'
'a.b.c.x' --
'x.__of__(a).__of__(b.__of__(a)).__of__(c.__of__(b.__of__(a)))'
and by keeping in mind that attribute lookup in a wrapper
is done by trying to lookup the attribute in the wrapped object
first and then in the parent object. In the expressions above
involving the '__of__' method, lookup proceeds from left to right.
Note that heuristics are used to avoid most of the repeated
lookups. For example, in the expression: 'a.b.c.x.foo', the object
'a' is searched no more than once, even though it is wrapped three
times.
.. [1] Gil, J., Lorenz, D.,
"Environmental Acquisition--A New Inheritance-Like Abstraction Mechanism",
http://www.bell-labs.com/people/cope/oopsla/Oopsla96TechnicalProgramAbstracts.html#GilLorenz,
OOPSLA '96 Proceedings, ACM SIG-PLAN, October, 1996
<h1>Extension Classes, Python Extension Types Become Classes</h1>
<p> Jim Fulton, Digital Creations, Inc.
jim@digicool.com</p>
<p> <a href="COPYRIGHT.html">Copyright (C) 1996-1998, Digital Creations</a>.</p>
<h2>Abstract</h2>
<p> A lightweight mechanism has been developed for making Python
extension types more class-like. Classes can be developed in an
extension language, such as C or C++, and these classes can be
treated like other python classes:</p>
<ul><li><p>They can be sub-classed in python,</p>
</li>
<li><p>They provide access to method documentation strings, and</p>
</li>
<li><p>They can be used to directly create new instances.</p>
</li></ul>
<p> An example class shows how extension classes are implemented and how
they differ from extension types.</p>
<p> Extension classes provide additional extensions to class and
instance semantics, including:</p>
<ul><li><p>A protocol for accessing subobjects "in the context of" their
containers. This is used to implement custom method types
and <a href="Acquisition.html">environmental acquisition</a>.</p>
</li>
<li><p>A protocol for overriding method call semantics. This is used
to implement "synchonized" classes and could be used to
implement argument type checking.</p>
</li>
<li><p>A protocol for class initialization that supports execution of a
special <code>__class_init__</code> method after a class has been
initialized.</p>
</li></ul>
<p> Extension classes illustrate how the Python class mechanism can be
extended and may provide a basis for improved or specialized class
models. </p>
<h2>Releases</h2>
<p> To find out what's changed in this release,
see the <a href="release.html">release notes</a>.</p>
<h2>Problem</h2>
<p> Currently, Python provides two ways of defining new kinds of objects:</p>
<ul><li><p>Python classes</p>
</li>
<li><p>Extension types</p>
</li></ul>
<p> Each approach has it's strengths. Extension types provide much greater
control to the programmer and, generally, better performance. Because
extension types are written in C, the programmer has greater access to
external resources. (Note that Python's use of the term type has
little to do with the notion of type as a formal specification.)</p>
<p> Classes provide a higher level of abstraction and are generally much
easier to develop. Classes provide full inheritance support, while
support for inheritance when developing extension types is very
limited. Classes provide run-time meta-data, such as method documentation
strings, that are useful for documentation and discovery. Classes
act as factories for creating instances, while separate functions
must be provided to create instances of types.</p>
<p> It would be useful to combine the features of the two approaches. It
would be useful to be able to have better support for inheritance for
types, or to be able to subclass from types in Python. It would be
useful to be able to have class-like meta-data support for types and
the ability to construct instances directly from types.</p>
<p> Our software is developed in Python. When necessary, we convert
debugged Python routines and classes to C for improved
performance. In most cases, a small number of methods in a class
is responsible for most of the computation. It should be possible
to convert only these methods to C, while leaving the other method
in Python. A natural way to approach this is to create a base
class in C that contains only the performance-critical aspects of
a class' implementation and mix this base class into a Python
class. </p>
<p> We have need, in a number of projects, for semantics that are
slightly different than the usual class and instance semantics,
yet we don't want to do most of our development in C. For
example, we have developed a persistence mechanism <a href="#1">[1]</a> that
redefines <code>__getattr__</code> and <code>__setattr__</code> to take storage-related
actions when object state is accessed or modified. We want to be
able to take certain actions on <em>every</em> attribute reference, but
for python class instances, <code>__getattr__</code> is only called when
attribute lookup fails by normal means.</p>
<p> As another example, we would like to have greater control over how
methods are bound. Currently, when accessing a class
instance attribute, the attribute value is bound together with the
instance in a method object <em>if and only if</em> the attribute value is a
python function. For some applications, we might also want to be
able to bind extension functions, or other types of callable
objects, such as HTML document templates <a href="#2">[2]</a>. Furthermore,
we might want to have greater control over how objects are bound.
For example, we might want to bind instances and callable objects
with special method objects that assure that no more than one thread
accesses the object or method at one time.</p>
<p> We can provide these special semantics in extension types, but we
wish to provide them for classes developed in Python.</p>
<h2>Background</h2>
<p> At the first Python Workshop, Don Beaudry presented work <a href="#3">[3]</a> done
at V.I. Corp to integrate Python with C++ frameworks. This system
provided a number of important features, including:</p>
<ul><li><p>Definition of extension types that provide class-like meta-data
and that can be called to create instances.</p>
</li>
<li><p>Ability to subclass in python from C types.</p>
</li>
<li><p>Ability to define classes in python who's data are stored as
C structures rather than in dictionaries to better interface to
C and C++ libraries, and for better performance.</p>
</li>
<li><p>Less dynamic data structures. In particular, the data structure
for a class is declared during class definition.</p>
</li>
<li><p>Support for enumeration types.</p>
</li></ul>
<p> This work was not released, initially.</p>
<p> Shortly after the workshop, changes were made to Python to support
the sub-classing features described in <a href="#3">[3]</a>. These changes were not
documented until the fourth Python Workshop <a href="#4">[4]</a>.</p>
<p> At the third Python workshop, I presented some work I had done on
generating module documentation for extension types. Based on the
discussion at this workshop, I developed a meta-type proposal <a href="#5">[5]</a>.
This meta-type proposal was for an object that simply stored
meta-information for a type, for the purpose of generating module
documentation.</p>
<p> In the summer of 1996, Don Beaudry released the system described in
<a href="#3">[3]</a> under the name MESS <a href="#6">[6]</a>. MESS addresses a number of needs but
has a few drawbacks:</p>
<ul><li><p>Only single inheritance is supported.</p>
</li>
<li><p>The mechanisms for defining MESS extension types is very different
from and more complicated than the standard Python type creation
mechanism.</p>
</li>
<li><p>Defining MESS types requires the use of an extensive C
applications programming interface. This presents problems for
configuring dynamically-loaded extension modules unless the MESS
library is linked into the Python interpreter.</p>
</li>
<li><p>Because the system tries to do a number of different things, it is
fairly large, about 15,000 lines.</p>
</li>
<li><p>There is very little documentation, especially for the C
programming interface.</p>
</li>
<li><p>The system is a work in progress, with a number of outstanding
bugs.</p>
</li></ul>
<p> As MESS matures, we expect most of these problems to be addressed.</p>
<h2>Extension Classes</h2>
<p> To meet short term needs for a C-based persistence mechanism <a href="#1">[1]</a>, an
extension class module was developed using the mechanism described
in <a href="#4">[4]</a> and building on ideas from MESS <a href="#6">[6]</a>. The extension class module
recasts extension types as "extension classes" by seeking to
eliminate, or at least reduce semantic differences between types and
classes. The module was designed to meet the following goal:</p>
<ul><li><p>Provide class-like behavior for extension types, including
interfaces for meta information and for constructing instances.</p>
</li>
<li><p>Support sub-classing in Python from extension classes, with support
for multiple inheritance.</p>
</li>
<li><p>Provide a small hardened implementation that can be used for
current products.</p>
</li>
<li><p>Provide a mechanism that requires minimal modification to existing
extension types.</p>
</li>
<li><p>Provide a basis for research on alternative semantics for classes
and inheritance.</p>
</li></ul>
<p> <strong>Note:</strong> I use <em>non-standard</em> terminology here. By standard
<em>python</em> terminology, only standard python classes can be called
classes. ExtensionClass "classes" are technically just "types"
that happen to swim, walk and quack like python classes.</p>
<h3>Base extension classes and extension subclasses</h3>
<p> Base extension classes are implemented in C. Extension subclasses
are implemented in Python and inherit, directly or indirectly from
one or more base extension classes. An extension subclass may
inherit from base extension classes, extension subclasses, and
ordinary python classes. The usual inheritance order rules
apply. Currently, extension subclasses must conform to the
following two rules:</p>
<ul><li><p>The first super class listed in the class statement defining an
extension subclass must be either a base extension class or an
extension subclass. This restriction will be removed in
Python-1.5.</p>
</li>
<li><p>At most one base extension direct or indirect super class may
define C data members. If an extension subclass inherits from
multiple base extension classes, then all but one must be mix-in
classes that provide extension methods but no data.</p>
</li></ul>
<h3>Meta Information</h3>
<p> Like standard python classes, extension classes have the following
attributes containing meta-data:</p>
<dl><dt> <code>__doc__</code> </dt><dd><p>a documentation string for the class,</p>
</dd>
<dt> <code>__name__</code> </dt><dd><p>the class name,</p>
</dd>
<dt> <code>__bases__</code></dt><dd><p>a sequence of base classes,</p>
</dd>
<dt> <code>__dict__</code> </dt><dd><p>a class dictionary, and</p>
</dd>
<dt> <code>__module__</code></dt><dd><p>the name of the module in which the class was
defined. </p>
</dd></dl>
<p> The class dictionary provides access to unbound methods and their
documentation strings, including extension methods and special
methods, such as methods that implement sequence and numeric
protocols. Unbound methods can be called with instance first
arguments.</p>
<h3>Subclass instance data</h3>
<p> Extension subclass instances have instance dictionaries, just
like Python class instances do. When fetching attribute values,
extension class instances will first try to obtain data from the
base extension class data structure, then from the instance
dictionary, then from the class dictionary, and finally from base
classes. When setting attributes, extension classes first attempt
to use extension base class attribute setting operations, and if
these fail, then data are placed in the instance dictionary.</p>
<h2>Implementing base extension classes</h2>
<p> A base extension class is implemented in much the same way that an
extension type is implemented, except:</p>
<ul><li><p>The include file, <code>ExtensionClass.h</code>, must be included.</p>
</li>
<li><p>The type structure is declared to be of type <code>PyExtensionClass</code>, rather
than of type <code>PyTypeObject</code>.</p>
</li>
<li><p>The type structure has an additional member that must be defined
after the documentation string. This extra member is a method chain
(<code>PyMethodChain</code>) containing a linked list of method definition
(<code>PyMethodDef</code>) lists. Method chains can be used to implement
method inheritance in C. Most extensions don't use method chains,
but simply define method lists, which are null-terminated arrays
of method definitions. A macro, <code>METHOD_CHAIN</code> is defined in
<code>ExtensionClass.h</code> that converts a method list to a method chain.
(See the example below.)</p>
</li>
<li><p>Module functions that create new instances must be replaced by
<code>__init__</code> methods that initialize, but does not create storage for
instances.</p>
</li>
<li><p>The extension class must be initialized and exported to the module
with::</p>
<PRE>
PyExtensionClass_Export(d,&quot;name&quot;,type);
where 'name' is the module name and 'type' is the extension class
type object.
</PRE>
</li></ul>
<h3>Attribute lookup</h3>
<p> Attribute lookup is performed by calling the base extension class
<code>getattr</code> operation for the base extension class that includes C
data, or for the first base extension class, if none of the base
extension classes include C data. <code>ExtensionClass.h</code> defines a
macro <code>Py_FindAttrString</code> that can be used to find an object's
attributes that are stored in the object's instance dictionary or
in the object's class or base classes:</p>
<PRE>
v = Py_FindAttrString(self,name);
</PRE>
<p> where <code>name</code> is a C string containing the attribute name.</p>
<p> In addition, a macro is provided that replaces <code>Py_FindMethod</code>
calls with logic to perform the same sort of lookup that is
provided by <code>Py_FindAttrString</code>.</p>
<p> If an attribute name is contained in a Python string object,
rather than a C string object, then the macro <code>Py_FindAttr</code> should
be used to look up an attribute value.</p>
<h3>Linking</h3>
<p> The extension class mechanism was designed to be useful with
dynamically linked extension modules. Modules that implement
extension classes do not have to be linked against an extension
class library. The macro <code>PyExtensionClass_Export</code> imports the
<code>ExtensionClass</code> module and uses objects imported from this module
to initialize an extension class with necessary behavior.</p>
<h3>Example: MultiMapping objects</h3>
<p> An <a href="MultiMapping.html">example</a> is provided that illustrates the
changes needed to convert an existing type to an ExtensionClass.</p>
<h2>Implementing base extension class constructors</h2>
<p> Some care should be taken when implementing or overriding base
class constructors. When a Python class overrides a base class
constructor and fails to call the base class constructor, a
program using the class may fail, but it will not crash the
interpreter. On the other hand, an extension subclass that
overrides a constructor in an extension base class must call the
extension base class constructor or risk crashing the interpreter.
This is because the base class constructor may set C pointers that,
if not set properly, will cause the interpreter to crash when
accessed. This is the case with the <code>MultiMapping</code> extension base
class shown in the example above.</p>
<p> If no base class constructor is provided, extension class instance
memory will be initialized to 0. It is a good idea to design
extension base classes so that instance methods check for
uninitialized memory and perform initialialization if necessary.
This was not done above to simplify the example.</p>
<h2>Overriding methods inherited from Python base classes</h2>
<p> A problem occurs when trying to overide methods inherited from
Python base classes. Consider the following example:</p>
<PRE>
from ExtensionClass import Base
class Spam:
def __init__(self, name):
self.name=name
class ECSpam(Base, Spam):
def __init__(self, name, favorite_color):
Spam.__init__(self,name)
self.favorite_color=favorite_color
</PRE>
<p> This implementation will fail when an <code>ECSpam</code> object is
instantiated. The problem is that <code>ECSpam.__init__</code> calls
<code>Spam.__init__</code>, and <code>Spam.__init__</code> can only be called with a
Python instance (an object of type <code>"instance"</code>) as the first
argument. The first argument passed to <code>Spam.__init__</code> will be an
<code>ECSpam</code> instance (an object of type <code>ECSPam</code>).</p>
<p> To overcome this problem, extension classes provide a class method
<code>inheritedAttribute</code> that can be used to obtain an inherited
attribute that is suitable for calling with an extension class
instance. Using the <code>inheritedAttribute</code> method, the above
example can be rewritten as:</p>
<PRE>
from ExtensionClass import Base
class Spam:
def __init__(self, name):
self.name=name
class ECSpam(Base, Spam):
def __init__(self, name, favorite_color):
ECSpam.inheritedAttribute('__init__')(self,name)
self.favorite_color=favorite_color
</PRE>
<p> This isn't as pretty but does provide the desired result.</p>
<h2>New class and instance semantics</h2>
<h3>Context Wrapping</h3>
<p> It is sometimes useful to be able to wrap up an object together
with a containing object. I call this "context wrapping"
because an object is accessed in the context of the object it is
accessed through.</p>
<p> We have found many applications for this, including:</p>
<ul><li><p>User-defined method objects,</p>
</li>
<li><p><a href="Acquisition.html">Acquisition</a> and</p>
</li>
<li><p>Computed attributes</p>
</li></ul>
<h4>User-defined method objects</h4>
<p> Python classes wrap Python function attributes into methods. When a
class has a function attribute that is accessed as an instance
attribute, a method object is created and returned that contains
references to the original function and instance. When the method
is called, the original function is called with the instance as the
first argument followed by any arguments passed to the method.</p>
<p> Extension classes provide a similar mechanism for attributes that
are Python functions or inherited extension functions. In
addition, if an extension class attribute is an instance of an
extension class that defines an <code>__of__</code> method, then when the
attribute is accessed through an instance, it's <code>__of__</code> method
will be called to create a bound method.</p>
<p> Consider the following example:</p>
<PRE>
import ExtensionClass
class CustomMethod(ExtensionClass.Base):
def __call__(self,ob):
print 'a %s was called' % ob.__class__.__name__
class wrapper:
def __init__(self,m,o): self.meth, self.ob=m,o
def __call__(self): self.meth(self.ob)
def __of__(self,o): return self.wrapper(self,o)
class bar(ExtensionClass.Base):
hi=CustomMethod()
x=bar()
hi=x.hi()
</PRE>
<p> Note that <code>ExtensionClass.Base</code> is a base extension class that
provides very basic ExtensionClass behavior. </p>
<p> When run, this program outputs: <code>a bar was called</code>.</p>
<h4>Computed Attributes</h4>
<p> It is not uncommon to wish to expose information via the
attribute interface without affecting implementation data
structures. One can use a custom <code>__getattr__</code> method to
implement computed attributes, however, this can be a bit
cumbersome and can interfere with other uses of <code>__getattr__</code>,
such as for persistence.</p>
<p> The <code>__of__</code> protocol provides a convenient way to implement
computed attributes. First, we define a ComputedAttribute
class. a ComputedAttribute is constructed with a function to
be used to compute an attribute, and calls the function when
it's <code>__of__</code> method is called:</p>
<p> import ExtensionClass</p>
<p> class ComputedAttribute(ExtensionClass.Base):</p>
<p> def __init__(self, func): self.func=func</p>
<p> def __of__(self, parent): return self.func(parent)</p>
<p> Then we can use this class to create computed attributes. In the
example below, we create a computed attribute, <code>radius</code>:</p>
<p> from math import sqrt</p>
<p> class Point(ExtensionClass.Base):</p>
<p> def __init__(self, x, y): self.x, self.y = x, y</p>
<p> radius=ComputedAttribute(lambda self: sqrt(self.x**2+self.y**2))</p>
<p> which we can use just like an ordinary attribute:</p>
<p> p=Point(2,2)
print p.radius</p>
<h3>Overriding method calls</h3>
<p> Normally, when a method is called, the function wrapped by the
method is called directly by the method. In some cases, it is
useful for user-defined logic to participate in the actual
function call. Extension classes introduce a new protocol that
provides extension classes greater control over how their
methods are called. If an extension class defines a special
method, <code>__call_method__</code>, then this method will be called to
call the functions (or other callable object) wrapped by the
method. The method. <code>__call_method__</code> should provide the same
interface as provided by the Python builtin <code>apply</code> function.</p>
<p> For example, consider the expression: <code>x.meth(arg1, arg2)</code>. The
expression is evaluated by first computing a method object that
wraps <code>x</code> and the attribute of <code>x</code> stored under the name <code>meth</code>.
Assuming that <code>x</code> has a <code>__call_method__</code> method defined, then
the <code>__call_method__</code> method of <code>x</code> will be called with two
arguments, the attribute of <code>x</code> stored under the name <code>meth</code>,
and a tuple containing <code>x</code>, <code>arg1</code>, and <code>arg2</code>.</p>
<p> To see how this feature may be used, see the Python module,
<code>Syn.py</code>, which is included in the ExtensionClass distribution.
This module provides a mix-in class that provides Java-like
"synchonized" classes that limit access to their methods to one
thread at a time.</p>
<p> An interesting application of this mechanism would be to
implement interface checking on method calls.</p>
<h3>Method attributes</h3>
<p> Methods of ExtensionClass instances can have user-defined
attributes, which are stored in their associated instances.</p>
<p> For example:</p>
<PRE>
class C(ExtensionClass.Base):
def f(self):
&quot;Get a secret&quot;
....
c=C()
c.f.__roles__=['Trusted People']
print c.f.__roles__ # outputs ['Trusted People']
print c.f__roles__ # outputs ['Trusted People']
print C.f.__roles__ # fails, unbound method
</PRE>
<p> A bound method attribute is set by setting an attribute in it's
instance with a name consisting of the concatination of the
method's <code>__name__</code> attribute and the attribute name.
Attributes cannot be set on unbound methods.</p>
<h3>Class initialization</h3>
<p> Normal Python class initialization is similar to but subtley
different from instance initialization. An instance <code>__init__</code>
function is called on an instance immediately <em>after</em> it is
created. An instance <code>__init__</code> function can use instance
information, like it's class and can pass the instance to other
functions. On the other hand, the code in class statements is
executed immediately <em>before</em> the class is created. This means
that the code in a class statement cannot use class attributes,
like <code>__bases__</code>, or pass the class to functions.</p>
<p> Extension classes provide a mechanism for specifying code to be
run <em>after</em> a class has been created. If a class or one of it's
base classes defines a <code>__class_init__</code> method, then this method
will be called just after a class has been created. The one
argument passed to the method will be the class, <em>not</em> an
instance of the class.</p>
<h2>Useful macros defined in ExtensionClass.h</h2>
<p> A number of useful macros are defined in ExtensionClass.h.
These are documented in <code>ExtensionClass.h</code>.</p>
<h2>Pickleability</h2>
<p> Classes created with ExtensionClass, including extension base
classes are automatically pickleable. The usual gymnastics
necessary to pickle <code>non-standard</code> types are not necessray for
types that have been modified to be extension base classes.</p>
<h2>Status</h2>
<p> The current release of the extension class module is <a href="http://www.digicool.com/releases/ExtensionClass/ExtensionClass-1.1.tar.gz">1.1</a>.
The core implementation has less than four thousand lines of code,
including comments. This release requires Python 1.4 or higher.</p>
<p> To find out what's changed in this release, see the
<a href="release.html">release notes</a>.</p>
<p> <a href="Installation.html">Installation instructions</a> are provided.</p>
<h2>Issues</h2>
<p> There are a number of issues that came up in the course of this work
and that deserve mention.</p>
<ul><li><p>In Python 1.4, the class extension mechanism described in <a href="#4">[4]</a> required
that the first superclass in a list of super-classes must be of the
extended class type. This may not be convenient if mix-in
behavior is desired. If a list of base classes starts with a
standard python class, but includes an extension class, then an
error was raised. It would be more useful if, when a list of base
classes contains one or more objects that are not python classes,
the first such object was used to control the extended class
definition. To get around this, the <code>ExtensionClass</code> module exports
a base extension class, <code>Base</code>, that can be used as the first base
class in a list of base classes to assure that an extension
subclass is created.</p>
<p> Python 1.5 allows the class extension even if the first non-class
object in the list of base classes is not the first object in
the list. This issue appears to go away in Python 1.5, however,
the restriction that the first non-class object in a list of
base classes must be the first in the list may reappear in later
versions of Python.</p>
</li>
<li><p>Currently, only one base extension class can define any data in
C. The data layout of subclasses-instances is the same as for the
base class that defines data in C, except that the data structure
is extended to hold an instance dictionary. The data structure
begins with a standard python header, and extension methods expect
the C instance data to occur immediately after the object header. If
two or more base classes defined C data, the methods for the
different base classes would expect their data to be in the same
location. A solution might be to allocate base class instances and
store pointers to these instances in the subclass data structure.
The method binding mechanism would have to be a more complicated
to make sure that methods were bound to the correct base data
structure. Alternatively, the signature of C methods could be
expanded to allow pointers to expected class data to be passed
in addition to object pointers.</p>
</li>
<li><p>There is currently no support for sub-classing in C, beyond that
provided by method chains.</p>
</li>
<li><p>Rules for mixed-type arithmetic are different for python class
instances than they are for extension type instances. Python
classes can define right and left versions of numeric binary
operators, or they can define a coercion operator for converting
binary operator operands to a common type. For extension types,
only the latter, coercion-based, approach is supported. The
coercion-based approach does not work well for many data types for
which coercion rules depend on the operator. Because extension
classes are based on extension types, they are currently limited
to the coercion-based approach. It should be possible to
extend the extension class implementation to allow both types of
mixed-type arithmetic control.</p>
</li>
<li><p>I considered making extension classes immutable, meaning that
class attributes could not be set after class creation. I also
considered making extension subclasses cache inherited
attributes. Both of these are related and attractive for some
applications, however, I decided that it would be better to retain
standard class instance semantics and provide these features as
options at a later time.</p>
</li>
<li><p>The extension class module defines new method types to bind C and
python methods to extension class instances. It would be useful
for these method objects to provide access to function call
information, such as the number and names of arguments and the
number of defaults, by parsing extension function documentation
strings.</p>
</li></ul>
<h2>Applications</h2>
<p> Aside from test and demonstration applications, the extension class
mechanism has been used to provide an extension-based implementation
of the persistence mechanism described in <a href="#1">[1]</a>. We have developed
this further to provide features such as automatic deactivation of
objects not used after some period of time and to provide more
efficient persistent-object cache management.</p>
<p> Acquisition has been heavily used in our recent products.
Synchonized classes have also been used in recent products.</p>
<h2>Summary</h2>
<p> The extension-class mechanism described here provides a way to add
class services to extension types. It allows:</p>
<ul><li><p>Sub-classing extension classes in Python,</p>
</li>
<li><p>Construction of extension class instances by calling extension
classes,</p>
</li>
<li><p>Extension classes to provide meta-data, such as unbound methods
and their documentation string.</p>
</li></ul>
<p> In addition, the extension class module provides a relatively
concise example of the use of mechanisms that were added to Python
to support MESS <a href="#6">[6]</a>, and that were described at the fourth Python
Workshop <a href="#4">[4]</a>. It is hoped that this will spur research in improved
and specialized models for class implementation in Python.</p>
<p> References</p>
<p> <a name="1">[1]</a> Fulton, J., <a href="http://www.digicool.com/papers/Persistence.html">Providing Persistence for World-Wide-Web Applications</a>
Proceedings of the 5th Python Workshop.</p>
<p> <a name="2">[2]</a> Page, R. and Cropper, S., <a href="http://www.digicool.com/papers/DocumentTemplate.html">Document Template</a>
Proceedings of the 5th Python Workshop.</p>
<p> <a name="3">[3]</a> Beaudry, D., <a href="http://www.python.org/workshops/1994-11/BuiltInClasses/BuiltInClasses_1.html">Deriving Built-In Classes in Python</a>
Proceedings of the First International Python Workshop.</p>
<p> <a name="4">[4]</a> Van Rossum, G., <a href="http://www.python.org/workshops/1996-06/notes/thursday.html">Don Beaudry Hack - MESS</a>
presented in the Developer's Future Enhancements session of the
4th Python Workshop. </p>
<p> <a name="5">[5]</a> Fulton, J., <a href="http://www.digicool.com/jim/MetaType.c">Meta-Type Object</a>
This is a small proposal, the text of which is contained in a
sample implementation source file, </p>
<p> <a name="6">[6]</a> Beaudry, D., and Ascher, D., <a href="http://starship.skyport.net/~da/mess/">The Meta-Extension Set</a>.
</p>
Extension Classes, Python Extension Types Become Classes
Jim Fulton, Digital Creations, Inc.
jim@digicool.com
"Copyright (C) 1996-1998, Digital Creations":COPYRIGHT.html.
Abstract
A lightweight mechanism has been developed for making Python
extension types more class-like. Classes can be developed in an
extension language, such as C or C++, and these classes can be
treated like other python classes:
- They can be sub-classed in python,
- They provide access to method documentation strings, and
- They can be used to directly create new instances.
An example class shows how extension classes are implemented and how
they differ from extension types.
Extension classes provide additional extensions to class and
instance semantics, including:
- A protocol for accessing subobjects "in the context of" their
containers. This is used to implement custom method types
and "environmental acquisition":Acquisition.html.
- A protocol for overriding method call semantics. This is used
to implement "synchonized" classes and could be used to
implement argument type checking.
- A protocol for class initialization that supports execution of a
special '__class_init__' method after a class has been
initialized.
Extension classes illustrate how the Python class mechanism can be
extended and may provide a basis for improved or specialized class
models.
Releases
To find out what's changed in this release,
see the "release notes":release.html.
Problem
Currently, Python provides two ways of defining new kinds of objects:
- Python classes
- Extension types
Each approach has it's strengths. Extension types provide much greater
control to the programmer and, generally, better performance. Because
extension types are written in C, the programmer has greater access to
external resources. (Note that Python's use of the term type has
little to do with the notion of type as a formal specification.)
Classes provide a higher level of abstraction and are generally much
easier to develop. Classes provide full inheritance support, while
support for inheritance when developing extension types is very
limited. Classes provide run-time meta-data, such as method documentation
strings, that are useful for documentation and discovery. Classes
act as factories for creating instances, while separate functions
must be provided to create instances of types.
It would be useful to combine the features of the two approaches. It
would be useful to be able to have better support for inheritance for
types, or to be able to subclass from types in Python. It would be
useful to be able to have class-like meta-data support for types and
the ability to construct instances directly from types.
Our software is developed in Python. When necessary, we convert
debugged Python routines and classes to C for improved
performance. In most cases, a small number of methods in a class
is responsible for most of the computation. It should be possible
to convert only these methods to C, while leaving the other method
in Python. A natural way to approach this is to create a base
class in C that contains only the performance-critical aspects of
a class' implementation and mix this base class into a Python
class.
We have need, in a number of projects, for semantics that are
slightly different than the usual class and instance semantics,
yet we don't want to do most of our development in C. For
example, we have developed a persistence mechanism [1] that
redefines '__getattr__' and '__setattr__' to take storage-related
actions when object state is accessed or modified. We want to be
able to take certain actions on *every* attribute reference, but
for python class instances, '__getattr__' is only called when
attribute lookup fails by normal means.
As another example, we would like to have greater control over how
methods are bound. Currently, when accessing a class
instance attribute, the attribute value is bound together with the
instance in a method object *if and only if* the attribute value is a
python function. For some applications, we might also want to be
able to bind extension functions, or other types of callable
objects, such as HTML document templates [2]. Furthermore,
we might want to have greater control over how objects are bound.
For example, we might want to bind instances and callable objects
with special method objects that assure that no more than one thread
accesses the object or method at one time.
We can provide these special semantics in extension types, but we
wish to provide them for classes developed in Python.
Background
At the first Python Workshop, Don Beaudry presented work [3] done
at V.I. Corp to integrate Python with C++ frameworks. This system
provided a number of important features, including:
- Definition of extension types that provide class-like meta-data
and that can be called to create instances.
- Ability to subclass in python from C types.
- Ability to define classes in python who's data are stored as
C structures rather than in dictionaries to better interface to
C and C++ libraries, and for better performance.
- Less dynamic data structures. In particular, the data structure
for a class is declared during class definition.
- Support for enumeration types.
This work was not released, initially.
Shortly after the workshop, changes were made to Python to support
the sub-classing features described in [3]. These changes were not
documented until the fourth Python Workshop [4].
At the third Python workshop, I presented some work I had done on
generating module documentation for extension types. Based on the
discussion at this workshop, I developed a meta-type proposal [5].
This meta-type proposal was for an object that simply stored
meta-information for a type, for the purpose of generating module
documentation.
In the summer of 1996, Don Beaudry released the system described in
[3] under the name MESS [6]. MESS addresses a number of needs but
has a few drawbacks:
- Only single inheritance is supported.
- The mechanisms for defining MESS extension types is very different
from and more complicated than the standard Python type creation
mechanism.
- Defining MESS types requires the use of an extensive C
applications programming interface. This presents problems for
configuring dynamically-loaded extension modules unless the MESS
library is linked into the Python interpreter.
- Because the system tries to do a number of different things, it is
fairly large, about 15,000 lines.
- There is very little documentation, especially for the C
programming interface.
- The system is a work in progress, with a number of outstanding
bugs.
As MESS matures, we expect most of these problems to be addressed.
Extension Classes
To meet short term needs for a C-based persistence mechanism [1], an
extension class module was developed using the mechanism described
in [4] and building on ideas from MESS [6]. The extension class module
recasts extension types as "extension classes" by seeking to
eliminate, or at least reduce semantic differences between types and
classes. The module was designed to meet the following goal:
- Provide class-like behavior for extension types, including
interfaces for meta information and for constructing instances.
- Support sub-classing in Python from extension classes, with support
for multiple inheritance.
- Provide a small hardened implementation that can be used for
current products.
- Provide a mechanism that requires minimal modification to existing
extension types.
- Provide a basis for research on alternative semantics for classes
and inheritance.
**Note:** I use *non-standard* terminology here. By standard
*python* terminology, only standard python classes can be called
classes. ExtensionClass "classes" are technically just "types"
that happen to swim, walk and quack like python classes.
Base extension classes and extension subclasses
Base extension classes are implemented in C. Extension subclasses
are implemented in Python and inherit, directly or indirectly from
one or more base extension classes. An extension subclass may
inherit from base extension classes, extension subclasses, and
ordinary python classes. The usual inheritance order rules
apply. Currently, extension subclasses must conform to the
following two rules:
- The first super class listed in the class statement defining an
extension subclass must be either a base extension class or an
extension subclass. This restriction will be removed in
Python-1.5.
- At most one base extension direct or indirect super class may
define C data members. If an extension subclass inherits from
multiple base extension classes, then all but one must be mix-in
classes that provide extension methods but no data.
Meta Information
Like standard python classes, extension classes have the following
attributes containing meta-data:
'__doc__' -- a documentation string for the class,
'__name__' -- the class name,
'__bases__' -- a sequence of base classes,
'__dict__' -- a class dictionary, and
'__module__' -- the name of the module in which the class was
defined.
The class dictionary provides access to unbound methods and their
documentation strings, including extension methods and special
methods, such as methods that implement sequence and numeric
protocols. Unbound methods can be called with instance first
arguments.
Subclass instance data
Extension subclass instances have instance dictionaries, just
like Python class instances do. When fetching attribute values,
extension class instances will first try to obtain data from the
base extension class data structure, then from the instance
dictionary, then from the class dictionary, and finally from base
classes. When setting attributes, extension classes first attempt
to use extension base class attribute setting operations, and if
these fail, then data are placed in the instance dictionary.
Implementing base extension classes
A base extension class is implemented in much the same way that an
extension type is implemented, except:
- The include file, 'ExtensionClass.h', must be included.
- The type structure is declared to be of type 'PyExtensionClass', rather
than of type 'PyTypeObject'.
- The type structure has an additional member that must be defined
after the documentation string. This extra member is a method chain
('PyMethodChain') containing a linked list of method definition
('PyMethodDef') lists. Method chains can be used to implement
method inheritance in C. Most extensions don't use method chains,
but simply define method lists, which are null-terminated arrays
of method definitions. A macro, 'METHOD_CHAIN' is defined in
'ExtensionClass.h' that converts a method list to a method chain.
(See the example below.)
- Module functions that create new instances must be replaced by
'__init__' methods that initialize, but does not create storage for
instances.
- The extension class must be initialized and exported to the module
with::
PyExtensionClass_Export(d,"name",type);
where 'name' is the module name and 'type' is the extension class
type object.
Attribute lookup
Attribute lookup is performed by calling the base extension class
'getattr' operation for the base extension class that includes C
data, or for the first base extension class, if none of the base
extension classes include C data. 'ExtensionClass.h' defines a
macro 'Py_FindAttrString' that can be used to find an object's
attributes that are stored in the object's instance dictionary or
in the object's class or base classes::
v = Py_FindAttrString(self,name);
where 'name' is a C string containing the attribute name.
In addition, a macro is provided that replaces 'Py_FindMethod'
calls with logic to perform the same sort of lookup that is
provided by 'Py_FindAttrString'.
If an attribute name is contained in a Python string object,
rather than a C string object, then the macro 'Py_FindAttr' should
be used to look up an attribute value.
Linking
The extension class mechanism was designed to be useful with
dynamically linked extension modules. Modules that implement
extension classes do not have to be linked against an extension
class library. The macro 'PyExtensionClass_Export' imports the
'ExtensionClass' module and uses objects imported from this module
to initialize an extension class with necessary behavior.
Example: MultiMapping objects
An "example":MultiMapping.html, is provided that illustrates the
changes needed to convert an existing type to an ExtensionClass.
Implementing base extension class constructors
Some care should be taken when implementing or overriding base
class constructors. When a Python class overrides a base class
constructor and fails to call the base class constructor, a
program using the class may fail, but it will not crash the
interpreter. On the other hand, an extension subclass that
overrides a constructor in an extension base class must call the
extension base class constructor or risk crashing the interpreter.
This is because the base class constructor may set C pointers that,
if not set properly, will cause the interpreter to crash when
accessed. This is the case with the 'MultiMapping' extension base
class shown in the example above.
If no base class constructor is provided, extension class instance
memory will be initialized to 0. It is a good idea to design
extension base classes so that instance methods check for
uninitialized memory and perform initialialization if necessary.
This was not done above to simplify the example.
Overriding methods inherited from Python base classes
A problem occurs when trying to overide methods inherited from
Python base classes. Consider the following example::
from ExtensionClass import Base
class Spam:
def __init__(self, name):
self.name=name
class ECSpam(Base, Spam):
def __init__(self, name, favorite_color):
Spam.__init__(self,name)
self.favorite_color=favorite_color
This implementation will fail when an 'ECSpam' object is
instantiated. The problem is that 'ECSpam.__init__' calls
'Spam.__init__', and 'Spam.__init__' can only be called with a
Python instance (an object of type '"instance"') as the first
argument. The first argument passed to 'Spam.__init__' will be an
'ECSpam' instance (an object of type 'ECSPam').
To overcome this problem, extension classes provide a class method
'inheritedAttribute' that can be used to obtain an inherited
attribute that is suitable for calling with an extension class
instance. Using the 'inheritedAttribute' method, the above
example can be rewritten as::
from ExtensionClass import Base
class Spam:
def __init__(self, name):
self.name=name
class ECSpam(Base, Spam):
def __init__(self, name, favorite_color):
ECSpam.inheritedAttribute('__init__')(self,name)
self.favorite_color=favorite_color
This isn't as pretty but does provide the desired result.
New class and instance semantics
Context Wrapping
It is sometimes useful to be able to wrap up an object together
with a containing object. I call this "context wrapping"
because an object is accessed in the context of the object it is
accessed through.
We have found many applications for this, including:
- User-defined method objects,
- "Acquisition":Acquisition.html, and
- Computed attributes
User-defined method objects
Python classes wrap Python function attributes into methods. When a
class has a function attribute that is accessed as an instance
attribute, a method object is created and returned that contains
references to the original function and instance. When the method
is called, the original function is called with the instance as the
first argument followed by any arguments passed to the method.
Extension classes provide a similar mechanism for attributes that
are Python functions or inherited extension functions. In
addition, if an extension class attribute is an instance of an
extension class that defines an '__of__' method, then when the
attribute is accessed through an instance, it's '__of__' method
will be called to create a bound method.
Consider the following example::
import ExtensionClass
class CustomMethod(ExtensionClass.Base):
def __call__(self,ob):
print 'a %s was called' % ob.__class__.__name__
class wrapper:
def __init__(self,m,o): self.meth, self.ob=m,o
def __call__(self): self.meth(self.ob)
def __of__(self,o): return self.wrapper(self,o)
class bar(ExtensionClass.Base):
hi=CustomMethod()
x=bar()
hi=x.hi()
Note that 'ExtensionClass.Base' is a base extension class that
provides very basic ExtensionClass behavior.
When run, this program outputs: 'a bar was called'.
Computed Attributes
It is not uncommon to wish to expose information via the
attribute interface without affecting implementation data
structures. One can use a custom '__getattr__' method to
implement computed attributes, however, this can be a bit
cumbersome and can interfere with other uses of '__getattr__',
such as for persistence.
The '__of__' protocol provides a convenient way to implement
computed attributes. First, we define a ComputedAttribute
class. a ComputedAttribute is constructed with a function to
be used to compute an attribute, and calls the function when
it's '__of__' method is called:
import ExtensionClass
class ComputedAttribute(ExtensionClass.Base):
def __init__(self, func): self.func=func
def __of__(self, parent): return self.func(parent)
Then we can use this class to create computed attributes. In the
example below, we create a computed attribute, 'radius':
from math import sqrt
class Point(ExtensionClass.Base):
def __init__(self, x, y): self.x, self.y = x, y
radius=ComputedAttribute(lambda self: sqrt(self.x**2+self.y**2))
which we can use just like an ordinary attribute:
p=Point(2,2)
print p.radius
Overriding method calls
Normally, when a method is called, the function wrapped by the
method is called directly by the method. In some cases, it is
useful for user-defined logic to participate in the actual
function call. Extension classes introduce a new protocol that
provides extension classes greater control over how their
methods are called. If an extension class defines a special
method, '__call_method__', then this method will be called to
call the functions (or other callable object) wrapped by the
method. The method. '__call_method__' should provide the same
interface as provided by the Python builtin 'apply' function.
For example, consider the expression: 'x.meth(arg1, arg2)'. The
expression is evaluated by first computing a method object that
wraps 'x' and the attribute of 'x' stored under the name 'meth'.
Assuming that 'x' has a '__call_method__' method defined, then
the '__call_method__' method of 'x' will be called with two
arguments, the attribute of 'x' stored under the name 'meth',
and a tuple containing 'x', 'arg1', and 'arg2'.
To see how this feature may be used, see the Python module,
'Syn.py', which is included in the ExtensionClass distribution.
This module provides a mix-in class that provides Java-like
"synchonized" classes that limit access to their methods to one
thread at a time.
An interesting application of this mechanism would be to
implement interface checking on method calls.
Method attributes
Methods of ExtensionClass instances can have user-defined
attributes, which are stored in their associated instances.
For example::
class C(ExtensionClass.Base):
def f(self):
"Get a secret"
....
c=C()
c.f.__roles__=['Trusted People']
print c.f.__roles__ # outputs ['Trusted People']
print c.f__roles__ # outputs ['Trusted People']
print C.f.__roles__ # fails, unbound method
A bound method attribute is set by setting an attribute in it's
instance with a name consisting of the concatination of the
method's '__name__' attribute and the attribute name.
Attributes cannot be set on unbound methods.
Class initialization
Normal Python class initialization is similar to but subtley
different from instance initialization. An instance '__init__'
function is called on an instance immediately *after* it is
created. An instance '__init__' function can use instance
information, like it's class and can pass the instance to other
functions. On the other hand, the code in class statements is
executed immediately *before* the class is created. This means
that the code in a class statement cannot use class attributes,
like '__bases__', or pass the class to functions.
Extension classes provide a mechanism for specifying code to be
run *after* a class has been created. If a class or one of it's
base classes defines a '__class_init__' method, then this method
will be called just after a class has been created. The one
argument passed to the method will be the class, *not* an
instance of the class.
Useful macros defined in ExtensionClass.h
A number of useful macros are defined in ExtensionClass.h.
These are documented in 'ExtensionClass.h'.
Pickleability
Classes created with ExtensionClass, including extension base
classes are automatically pickleable. The usual gymnastics
necessary to pickle 'non-standard' types are not necessray for
types that have been modified to be extension base classes.
Status
The current release of the extension class module is "1.1",
http://www.digicool.com/releases/ExtensionClass/ExtensionClass-1.1.tar.gz.
The core implementation has less than four thousand lines of code,
including comments. This release requires Python 1.4 or higher.
To find out what's changed in this release, see the
"release notes":release.html.
"Installation instructions":Installation.html, are provided.
Issues
There are a number of issues that came up in the course of this work
and that deserve mention.
- In Python 1.4, the class extension mechanism described in [4] required
that the first superclass in a list of super-classes must be of the
extended class type. This may not be convenient if mix-in
behavior is desired. If a list of base classes starts with a
standard python class, but includes an extension class, then an
error was raised. It would be more useful if, when a list of base
classes contains one or more objects that are not python classes,
the first such object was used to control the extended class
definition. To get around this, the 'ExtensionClass' module exports
a base extension class, 'Base', that can be used as the first base
class in a list of base classes to assure that an extension
subclass is created.
Python 1.5 allows the class extension even if the first non-class
object in the list of base classes is not the first object in
the list. This issue appears to go away in Python 1.5, however,
the restriction that the first non-class object in a list of
base classes must be the first in the list may reappear in later
versions of Python.
- Currently, only one base extension class can define any data in
C. The data layout of subclasses-instances is the same as for the
base class that defines data in C, except that the data structure
is extended to hold an instance dictionary. The data structure
begins with a standard python header, and extension methods expect
the C instance data to occur immediately after the object header. If
two or more base classes defined C data, the methods for the
different base classes would expect their data to be in the same
location. A solution might be to allocate base class instances and
store pointers to these instances in the subclass data structure.
The method binding mechanism would have to be a more complicated
to make sure that methods were bound to the correct base data
structure. Alternatively, the signature of C methods could be
expanded to allow pointers to expected class data to be passed
in addition to object pointers.
- There is currently no support for sub-classing in C, beyond that
provided by method chains.
- Rules for mixed-type arithmetic are different for python class
instances than they are for extension type instances. Python
classes can define right and left versions of numeric binary
operators, or they can define a coercion operator for converting
binary operator operands to a common type. For extension types,
only the latter, coercion-based, approach is supported. The
coercion-based approach does not work well for many data types for
which coercion rules depend on the operator. Because extension
classes are based on extension types, they are currently limited
to the coercion-based approach. It should be possible to
extend the extension class implementation to allow both types of
mixed-type arithmetic control.
- I considered making extension classes immutable, meaning that
class attributes could not be set after class creation. I also
considered making extension subclasses cache inherited
attributes. Both of these are related and attractive for some
applications, however, I decided that it would be better to retain
standard class instance semantics and provide these features as
options at a later time.
- The extension class module defines new method types to bind C and
python methods to extension class instances. It would be useful
for these method objects to provide access to function call
information, such as the number and names of arguments and the
number of defaults, by parsing extension function documentation
strings.
Applications
Aside from test and demonstration applications, the extension class
mechanism has been used to provide an extension-based implementation
of the persistence mechanism described in [1]. We have developed
this further to provide features such as automatic deactivation of
objects not used after some period of time and to provide more
efficient persistent-object cache management.
Acquisition has been heavily used in our recent products.
Synchonized classes have also been used in recent products.
Summary
The extension-class mechanism described here provides a way to add
class services to extension types. It allows:
- Sub-classing extension classes in Python,
- Construction of extension class instances by calling extension
classes,
- Extension classes to provide meta-data, such as unbound methods
and their documentation string.
In addition, the extension class module provides a relatively
concise example of the use of mechanisms that were added to Python
to support MESS [6], and that were described at the fourth Python
Workshop [4]. It is hoped that this will spur research in improved
and specialized models for class implementation in Python.
References
.. [1] Fulton, J., "Providing Persistence for World-Wide-Web Applications",
http://www.digicool.com/papers/Persistence.html,
Proceedings of the 5th Python Workshop.
.. [2] Page, R. and Cropper, S., "Document Template",
http://www.digicool.com/papers/DocumentTemplate.html,
Proceedings of the 5th Python Workshop.
.. [3] Beaudry, D., "Deriving Built-In Classes in Python",
http://www.python.org/workshops/1994-11/BuiltInClasses/BuiltInClasses_1.html,
Proceedings of the First International Python Workshop.
.. [4] Van Rossum, G., "Don Beaudry Hack - MESS",
http://www.python.org/workshops/1996-06/notes/thursday.html,
presented in the Developer's Future Enhancements session of the
4th Python Workshop.
.. [5] Fulton, J., "Meta-Type Object",
http://www.digicool.com/jim/MetaType.c,
This is a small proposal, the text of which is contained in a
sample implementation source file,
.. [6] Beaudry, D., and Ascher, D., "The Meta-Extension Set",
http://starship.skyport.net/~da/mess/.
Installation
"Copyright (C) 1996-1998, Digital Creations":COPYRIGHT.html.
The ExtensionClass distribution now uses the "Universal Unix
Makefile for Python extensions", 'Makefile.pre.in', which was
introduced as part of Python1.4. Copies of this file for Python
1.4 and Python 1.5 are included with this release. See the
instructions in the make file, itself. Note that you will need to
copy or rename the the file for the Python version you're using to
Makefile.pre.in.
Files
ExtensionClass.stx -- This file in structured text format
ExtensionClass.html -- This file in HTML format
Installation -- Installation instructions in structured text
format.
Installation.html -- Installation instructions in HTML
format.
Acquisition.stx -- Acquisition documentation in structured text
format.
Acquisition.html -- Acquisition documentation in HTML
format.
MultiMapping.stx -- The MultiMapping example in structured text
format.
MultiMapping.html -- The MultiMapping example in structured text
format.
release.notes -- Release notes in structured text
format.
release.html -- Release notes in HTML format.
README -- A file that says to read this file.
Makefile.pre.in-1.4 -- The Universal Unix Makefile for Python
extensions. This is the Python 1.4
version. Copy this to Makefile.pre.in
before using it.
Makefile.pre.in-1.5 -- The Universal Unix Makefile for Python
extensions. This is the Python 1.5
version. Copy this to Makefile.pre.in
before using it.
Setup -- a configuration file used by the Universal
Unix Makefile for Python extensions
ExtensionClass.c -- The ExtensionClass source
ExtensionClass.h -- The ExtensionClass header file
Acquisition.c -- The source for the 'Acquisition' module
that provides mix-in classes to support
environmental acquisition
MethodObject.c -- The source for the 'MethodObject' module
that provides a mix-in class for
user-defined method types. To create a
user-defined method type, just create an
extension subclass of
'MethodObject.MethodObject' that has an
'__call__' method.
Missing.c -- The source for the 'Missing' module
that provides a class for objects that
model "missing" or unknown data. Missing
objects have the property that all
mathematical operations yield a missing
value. This is included mainly as an
example (and test) of a numeric extension
base class.
MultiMapping.c -- The source for a slightly enhanced
'MultiMapping' module that is based on the
'MultiMapping' example given in this
paper. If present, document templates [2]
will take advantage of this module to
significantly increase rendering
performance.
Sync.py -- A Python module that provides a
'Synchonized' mix-in class that limits access
to an object's methods to one thread at a
time. This requires the installation of
the ThreadLock module.
ThreadLock.c -- The source for the 'ThreadLock' module that
provides 'ThreadLock' objects. These are
similar to the lock objects provided by
the 'thread' modules. Unlike normal
Python lock objects, 'ThreadLock' objects
can be acquired (and released) more than
once by the same thread.
In addition to the files listed above, several "test" modules are
included. These are modules that I used to test ExtensionClass.
They do not constitute a regression testing suit and I've made
little effort to assure that they actually work, although that
would be a good thing to do if time permits.
<h1>Installation</h1>
<p> <a href="COPYRIGHT.html">Copyright (C) 1996-1998, Digital Creations</a>.</p>
<p> The ExtensionClass distribution now uses the "Universal Unix
Makefile for Python extensions", <code>Makefile.pre.in</code>, which was
introduced as part of Python1.4. Copies of this file for Python
1.4 and Python 1.5 are included with this release. See the
instructions in the make file, itself. Note that you will need to
copy or rename the the file for the Python version you're using to
Makefile.pre.in.</p>
<h1>Files</h1>
<dl><dt> ExtensionClass.stx </dt><dd><p>This file in structured text format</p>
</dd>
<dt> ExtensionClass.html </dt><dd><p>This file in HTML format</p>
</dd>
<dt> Installation </dt><dd><p>Installation instructions in structured text
format. </p>
</dd>
<dt> Installation.html </dt><dd><p>Installation instructions in HTML
format. </p>
</dd>
<dt> Acquisition.stx </dt><dd><p>Acquisition documentation in structured text
format. </p>
</dd>
<dt> Acquisition.html </dt><dd><p>Acquisition documentation in HTML
format. </p>
</dd>
<dt> MultiMapping.stx </dt><dd><p>The MultiMapping example in structured text
format. </p>
</dd>
<dt> MultiMapping.html </dt><dd><p>The MultiMapping example in structured text
format. </p>
</dd>
<dt> release.notes </dt><dd><p>Release notes in structured text
format. </p>
</dd>
<dt> release.html </dt><dd><p>Release notes in HTML format. </p>
</dd>
<dt> README </dt><dd><p>A file that says to read this file.</p>
</dd>
<dt> Makefile.pre.in-1.4 </dt><dd><p>The Universal Unix Makefile for Python
extensions. This is the Python 1.4
version. Copy this to Makefile.pre.in
before using it.</p>
</dd>
<dt> Makefile.pre.in-1.5 </dt><dd><p>The Universal Unix Makefile for Python
extensions. This is the Python 1.5
version. Copy this to Makefile.pre.in
before using it.</p>
</dd>
<dt> Setup </dt><dd><p>a configuration file used by the Universal
Unix Makefile for Python extensions </p>
</dd>
<dt> ExtensionClass.c </dt><dd><p>The ExtensionClass source</p>
</dd>
<dt> ExtensionClass.h </dt><dd><p>The ExtensionClass header file</p>
</dd>
<dt> Acquisition.c </dt><dd><p>The source for the <code>Acquisition</code> module
that provides mix-in classes to support
environmental acquisition</p>
</dd>
<dt> MethodObject.c </dt><dd><p>The source for the <code>MethodObject</code> module
that provides a mix-in class for
user-defined method types. To create a
user-defined method type, just create an
extension subclass of
<code>MethodObject.MethodObject</code> that has an
<code>__call__</code> method.</p>
</dd>
<dt> Missing.c </dt><dd><p>The source for the <code>Missing</code> module
that provides a class for objects that
model "missing" or unknown data. Missing
objects have the property that all
mathematical operations yield a missing
value. This is included mainly as an
example (and test) of a numeric extension
base class.</p>
</dd>
<dt> MultiMapping.c </dt><dd><p>The source for a slightly enhanced
<code>MultiMapping</code> module that is based on the
<code>MultiMapping</code> example given in this
paper. If present, document templates <a href="#2">[2]</a>
will take advantage of this module to
significantly increase rendering
performance. </p>
</dd>
<dt> Sync.py </dt><dd><p>A Python module that provides a
<code>Synchonized</code> mix-in class that limits access
to an object's methods to one thread at a
time. This requires the installation of
the ThreadLock module.</p>
</dd>
<dt> ThreadLock.c </dt><dd><p>The source for the <code>ThreadLock</code> module that
provides <code>ThreadLock</code> objects. These are
similar to the lock objects provided by
the <code>thread</code> modules. Unlike normal
Python lock objects, <code>ThreadLock</code> objects
can be acquired (and released) more than
once by the same thread.</p>
</dd></dl>
<p> In addition to the files listed above, several "test" modules are
included. These are modules that I used to test ExtensionClass.
They do not constitute a regression testing suit and I've made
little effort to assure that they actually work, although that
would be a good thing to do if time permits.
</p>
<h1>Example: MultiMapping objects</h1>
<p> <a href="COPYRIGHT.html">Copyright (C) 1996-1998, Digital Creations</a>.</p>
<p> As an example, consider an extension class that implements a
"MultiMapping". A multi-mapping is an object that encapsulates 0
or more mapping objects. When an attempt is made to lookup an
object, the encapsulated mapping objects are searched until an
object is found.</p>
<p> Consider an implementation of a MultiMapping extension type,
without use of the extension class mechanism:</p>
<PRE>
#include &quot;Python.h&quot;
#define UNLESS(E) if(!(E))
typedef struct {
PyObject_HEAD
PyObject *data;
} MMobject;
staticforward PyTypeObject MMtype;
static PyObject *
MM_push(MMobject *self, PyObject *args){
PyObject *src;
UNLESS(PyArg_ParseTuple(args, &quot;O&quot;, &amp;src)) return NULL;
UNLESS(-1 != PyList_Append(self-&gt;data,src)) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
MM_pop(MMobject *self, PyObject *args){
long l;
PyObject *r;
static PyObject *emptyList=0;
UNLESS(emptyList) UNLESS(emptyList=PyList_New(0)) return NULL;
UNLESS(PyArg_ParseTuple(args, &quot;&quot;)) return NULL;
UNLESS(-1 != (l=PyList_Size(self-&gt;data))) return NULL;
l--;
UNLESS(r=PySequence_GetItem(self-&gt;data,l)) return NULL;
UNLESS(-1 != PyList_SetSlice(self-&gt;data,l,l+1,emptyList)) goto err;
return r;
err:
Py_DECREF(r);
return NULL;
}
static struct PyMethodDef MM_methods[] = {
{&quot;push&quot;, (PyCFunction) MM_push, 1,
&quot;push(mapping_object) -- Add a data source&quot;},
{&quot;pop&quot;, (PyCFunction) MM_pop, 1,
&quot;pop() -- Remove and return the last data source added&quot;},
{NULL, NULL} /* sentinel */
};
static PyObject *
newMMobject(PyObject *ignored, PyObject *args){
MMobject *self;
UNLESS(PyArg_ParseTuple(args, &quot;&quot;)) return NULL;
UNLESS(self = PyObject_NEW(MMobject, &amp;MMtype)) return NULL;
UNLESS(self-&gt;data=PyList_New(0)) goto err;
return (PyObject *)self;
err:
Py_DECREF(self);
return NULL;
}
static void
MM_dealloc(MMobject *self){
Py_XDECREF(self-&gt;data);
PyMem_DEL(self);
}
static PyObject *
MM_getattr(MMobject *self, char *name){
return Py_FindMethod(MM_methods, (PyObject *)self, name);
}
static int
MM_length(MMobject *self){
long l=0, el, i;
PyObject *e=0;
UNLESS(-1 != (i=PyList_Size(self-&gt;data))) return -1;
while(--i &gt;= 0)
{
e=PyList_GetItem(self-&gt;data,i);
UNLESS(-1 != (el=PyObject_Length(e))) return -1;
l+=el;
}
return l;
}
static PyObject *
MM_subscript(MMobject *self, PyObject *key){
long i;
PyObject *e;
UNLESS(-1 != (i=PyList_Size(self-&gt;data))) return NULL;
while(--i &gt;= 0)
{
e=PyList_GetItem(self-&gt;data,i);
if(e=PyObject_GetItem(e,key)) return e;
PyErr_Clear();
}
PyErr_SetObject(PyExc_KeyError,key);
return NULL;
}
static PyMappingMethods MM_as_mapping = {
(inquiry)MM_length, /*mp_length*/
(binaryfunc)MM_subscript, /*mp_subscript*/
(objobjargproc)NULL, /*mp_ass_subscript*/
};
/* -------------------------------------------------------- */
static char MMtype__doc__[] =
&quot;MultiMapping -- Combine multiple mapping objects for lookup&quot;
;
static PyTypeObject MMtype = {
PyObject_HEAD_INIT(&amp;PyType_Type)
0, /*ob_size*/
&quot;MultMapping&quot;, /*tp_name*/
sizeof(MMobject), /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
(destructor)MM_dealloc, /*tp_dealloc*/
(printfunc)0, /*tp_print*/
(getattrfunc)MM_getattr, /*tp_getattr*/
(setattrfunc)0, /*tp_setattr*/
(cmpfunc)0, /*tp_compare*/
(reprfunc)0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
&amp;MM_as_mapping, /*tp_as_mapping*/
(hashfunc)0, /*tp_hash*/
(ternaryfunc)0, /*tp_call*/
(reprfunc)0, /*tp_str*/
/* Space for future expansion */
0L,0L,0L,0L,
MMtype__doc__ /* Documentation string */
};
static struct PyMethodDef MultiMapping_methods[] = {
{&quot;MultiMapping&quot;, (PyCFunction)newMMobject, 1,
&quot;MultiMapping() -- Create a new empty multi-mapping&quot;},
{NULL, NULL} /* sentinel */
};
void
initMultiMapping(){
PyObject *m;
m = Py_InitModule4(
&quot;MultiMapping&quot;, MultiMapping_methods,
&quot;MultiMapping -- Wrap multiple mapping objects for lookup&quot;,
(PyObject*)NULL,PYTHON_API_VERSION);
if (PyErr_Occurred())
Py_FatalError(&quot;can't initialize module MultiMapping&quot;);
}
</PRE>
<p> This module defines an extension type, <code>MultiMapping</code>, and exports a
module function, <code>MultiMapping</code>, that creates <code>MultiMapping</code>
Instances. The type provides two methods, <code>push</code>, and <code>pop</code>, for
adding and removing mapping objects to the multi-mapping.
The type provides mapping behavior, implementing mapping length
and subscript operators but not mapping a subscript assignment
operator.</p>
<p> Now consider an extension class implementation of MultiMapping
objects:</p>
<PRE>
#include &quot;Python.h&quot;
#include &quot;ExtensionClass.h&quot;
#define UNLESS(E) if(!(E))
typedef struct {
PyObject_HEAD
PyObject *data;
} MMobject;
staticforward PyExtensionClass MMtype;
static PyObject *
MM_push(self, args)
MMobject *self;
PyObject *args;
{
PyObject *src;
UNLESS(PyArg_ParseTuple(args, &quot;O&quot;, &amp;src)) return NULL;
UNLESS(-1 != PyList_Append(self-&gt;data,src)) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
MM_pop(self, args)
MMobject *self;
PyObject *args;
{
long l;
PyObject *r;
static PyObject *emptyList=0;
UNLESS(emptyList) UNLESS(emptyList=PyList_New(0)) return NULL;
UNLESS(PyArg_ParseTuple(args, &quot;&quot;)) return NULL;
UNLESS(-1 != (l=PyList_Size(self-&gt;data))) return NULL;
l--;
UNLESS(r=PySequence_GetItem(self-&gt;data,l)) return NULL;
UNLESS(-1 != PyList_SetSlice(self-&gt;data,l,l+1,emptyList)) goto err;
return r;
err:
Py_DECREF(r);
return NULL;
}
static PyObject *
MM__init__(self, args)
MMobject *self;
PyObject *args;
{
UNLESS(PyArg_ParseTuple(args, &quot;&quot;)) return NULL;
UNLESS(self-&gt;data=PyList_New(0)) goto err;
Py_INCREF(Py_None);
return Py_None;
err:
Py_DECREF(self);
return NULL;
}
static struct PyMethodDef MM_methods[] = {
{&quot;__init__&quot;, (PyCFunction)MM__init__, 1,
&quot;__init__() -- Create a new empty multi-mapping&quot;},
{&quot;push&quot;, (PyCFunction) MM_push, 1,
&quot;push(mapping_object) -- Add a data source&quot;},
{&quot;pop&quot;, (PyCFunction) MM_pop, 1,
&quot;pop() -- Remove and return the last data source added&quot;},
{NULL, NULL} /* sentinel */
};
static void
MM_dealloc(self)
MMobject *self;
{
Py_XDECREF(self-&gt;data);
PyMem_DEL(self);
}
static PyObject *
MM_getattr(self, name)
MMobject *self;
char *name;
{
return Py_FindMethod(MM_methods, (PyObject *)self, name);
}
static int
MM_length(self)
MMobject *self;
{
long l=0, el, i;
PyObject *e=0;
UNLESS(-1 != (i=PyList_Size(self-&gt;data))) return -1;
while(--i &gt;= 0)
{
e=PyList_GetItem(self-&gt;data,i);
UNLESS(-1 != (el=PyObject_Length(e))) return -1;
l+=el;
}
return l;
}
static PyObject *
MM_subscript(self, key)
MMobject *self;
PyObject *key;
{
long i;
PyObject *e;
UNLESS(-1 != (i=PyList_Size(self-&gt;data))) return NULL;
while(--i &gt;= 0)
{
e=PyList_GetItem(self-&gt;data,i);
if(e=PyObject_GetItem(e,key)) return e;
PyErr_Clear();
}
PyErr_SetObject(PyExc_KeyError,key);
return NULL;
}
static PyMappingMethods MM_as_mapping = {
(inquiry)MM_length, /*mp_length*/
(binaryfunc)MM_subscript, /*mp_subscript*/
(objobjargproc)NULL, /*mp_ass_subscript*/
};
/* -------------------------------------------------------- */
static char MMtype__doc__[] =
&quot;MultiMapping -- Combine multiple mapping objects for lookup&quot;
;
static PyExtensionClass MMtype = {
PyObject_HEAD_INIT(&amp;PyType_Type)
0, /*ob_size*/
&quot;MultMapping&quot;, /*tp_name*/
sizeof(MMobject), /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
(destructor)MM_dealloc, /*tp_dealloc*/
(printfunc)0, /*tp_print*/
(getattrfunc)MM_getattr, /*tp_getattr*/
(setattrfunc)0, /*tp_setattr*/
(cmpfunc)0, /*tp_compare*/
(reprfunc)0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
&amp;MM_as_mapping, /*tp_as_mapping*/
(hashfunc)0, /*tp_hash*/
(ternaryfunc)0, /*tp_call*/
(reprfunc)0, /*tp_str*/
/* Space for future expansion */
0L,0L,0L,0L,
MMtype__doc__, /* Documentation string */
METHOD_CHAIN(MM_methods)
};
static struct PyMethodDef MultiMapping_methods[] = {
{NULL, NULL} /* sentinel */
};
void
initMultiMapping()
{
PyObject *m, *d;
m = Py_InitModule4(
&quot;MultiMapping&quot;, MultiMapping_methods,
&quot;MultiMapping -- Wrap multiple mapping objects for lookup&quot;,
(PyObject*)NULL,PYTHON_API_VERSION);
d = PyModule_GetDict(m);
PyExtensionClass_Export(d,&quot;MultiMapping&quot;,MMtype);
if (PyErr_Occurred())
Py_FatalError(&quot;can't initialize module MultiMapping&quot;);
}
</PRE>
<p> This version includes <code>ExtensionClass.h</code>. The two declarations of
<code>MMtype</code> have been changed from <code>PyTypeObject</code> to <code>PyExtensionClass</code>.
The <code>METHOD_CHAIN</code> macro has been used to add methods to the end of
the definition for <code>MMtype</code>. The module function, newMMobject has
been replaced by the <code>MMtype</code> method, <code>MM__init__</code>. Note that this
method does not create or return a new object. Finally, the lines:</p>
<PRE>
d = PyModule_GetDict(m);
PyExtensionClass_Export(d,&quot;MultiMapping&quot;,MMtype);
</PRE>
<p> Have been added to both initialize the extension class and to export
it in the module dictionary.</p>
<p> To use this module, compile, link, and import it as with any other
extension module. The following python code illustrates the
module's use:</p>
<PRE>
from MultiMapping import MultiMapping
m=MultiMapping()
m.push({'spam':1, 'eggs':2})
m.push({'spam':3, 'ham':4})
m['spam'] # returns 3
m['ham'] # returns 4
m['foo'] # raises a key error
</PRE>
<p> Creating the <code>MultiMapping</code> object took three steps, one to create
an empty <code>MultiMapping</code>, and two to add mapping objects to it. We
might wish to simplify the process of creating MultiMapping
objects by providing a constructor that takes source mapping
objects as parameters. We can do this by sub-classing MultiMapping
in Python:</p>
<PRE>
from MultiMapping import MultiMapping
class ExtendedMultiMapping(MultiMapping):
def __init__(self,*data):
MultiMapping.__init__(self)
for d in data: self.push(d)
m=ExtendedMultiMapping({'spam':1, 'eggs':2}, {'spam':3, 'ham':4})
m['spam'] # returns 3
m['ham'] # returns 4
m['foo'] # raises a key error
</PRE>
<p> Note that the source file included in the ExtensionClass
distribution has numerous enhancements beyond the version shown in
this document.
</p>
Example: MultiMapping objects
"Copyright (C) 1996-1998, Digital Creations":COPYRIGHT.html.
As an example, consider an extension class that implements a
"MultiMapping". A multi-mapping is an object that encapsulates 0
or more mapping objects. When an attempt is made to lookup an
object, the encapsulated mapping objects are searched until an
object is found.
Consider an implementation of a MultiMapping extension type,
without use of the extension class mechanism::
#include "Python.h"
#define UNLESS(E) if(!(E))
typedef struct {
PyObject_HEAD
PyObject *data;
} MMobject;
staticforward PyTypeObject MMtype;
static PyObject *
MM_push(MMobject *self, PyObject *args){
PyObject *src;
UNLESS(PyArg_ParseTuple(args, "O", &src)) return NULL;
UNLESS(-1 != PyList_Append(self->data,src)) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
MM_pop(MMobject *self, PyObject *args){
long l;
PyObject *r;
static PyObject *emptyList=0;
UNLESS(emptyList) UNLESS(emptyList=PyList_New(0)) return NULL;
UNLESS(PyArg_ParseTuple(args, "")) return NULL;
UNLESS(-1 != (l=PyList_Size(self->data))) return NULL;
l--;
UNLESS(r=PySequence_GetItem(self->data,l)) return NULL;
UNLESS(-1 != PyList_SetSlice(self->data,l,l+1,emptyList)) goto err;
return r;
err:
Py_DECREF(r);
return NULL;
}
static struct PyMethodDef MM_methods[] = {
{"push", (PyCFunction) MM_push, 1,
"push(mapping_object) -- Add a data source"},
{"pop", (PyCFunction) MM_pop, 1,
"pop() -- Remove and return the last data source added"},
{NULL, NULL} /* sentinel */
};
static PyObject *
newMMobject(PyObject *ignored, PyObject *args){
MMobject *self;
UNLESS(PyArg_ParseTuple(args, "")) return NULL;
UNLESS(self = PyObject_NEW(MMobject, &MMtype)) return NULL;
UNLESS(self->data=PyList_New(0)) goto err;
return (PyObject *)self;
err:
Py_DECREF(self);
return NULL;
}
static void
MM_dealloc(MMobject *self){
Py_XDECREF(self->data);
PyMem_DEL(self);
}
static PyObject *
MM_getattr(MMobject *self, char *name){
return Py_FindMethod(MM_methods, (PyObject *)self, name);
}
static int
MM_length(MMobject *self){
long l=0, el, i;
PyObject *e=0;
UNLESS(-1 != (i=PyList_Size(self->data))) return -1;
while(--i >= 0)
{
e=PyList_GetItem(self->data,i);
UNLESS(-1 != (el=PyObject_Length(e))) return -1;
l+=el;
}
return l;
}
static PyObject *
MM_subscript(MMobject *self, PyObject *key){
long i;
PyObject *e;
UNLESS(-1 != (i=PyList_Size(self->data))) return NULL;
while(--i >= 0)
{
e=PyList_GetItem(self->data,i);
if(e=PyObject_GetItem(e,key)) return e;
PyErr_Clear();
}
PyErr_SetObject(PyExc_KeyError,key);
return NULL;
}
static PyMappingMethods MM_as_mapping = {
(inquiry)MM_length, /*mp_length*/
(binaryfunc)MM_subscript, /*mp_subscript*/
(objobjargproc)NULL, /*mp_ass_subscript*/
};
/* -------------------------------------------------------- */
static char MMtype__doc__[] =
"MultiMapping -- Combine multiple mapping objects for lookup"
;
static PyTypeObject MMtype = {
PyObject_HEAD_INIT(&PyType_Type)
0, /*ob_size*/
"MultMapping", /*tp_name*/
sizeof(MMobject), /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
(destructor)MM_dealloc, /*tp_dealloc*/
(printfunc)0, /*tp_print*/
(getattrfunc)MM_getattr, /*tp_getattr*/
(setattrfunc)0, /*tp_setattr*/
(cmpfunc)0, /*tp_compare*/
(reprfunc)0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
&MM_as_mapping, /*tp_as_mapping*/
(hashfunc)0, /*tp_hash*/
(ternaryfunc)0, /*tp_call*/
(reprfunc)0, /*tp_str*/
/* Space for future expansion */
0L,0L,0L,0L,
MMtype__doc__ /* Documentation string */
};
static struct PyMethodDef MultiMapping_methods[] = {
{"MultiMapping", (PyCFunction)newMMobject, 1,
"MultiMapping() -- Create a new empty multi-mapping"},
{NULL, NULL} /* sentinel */
};
void
initMultiMapping(){
PyObject *m;
m = Py_InitModule4(
"MultiMapping", MultiMapping_methods,
"MultiMapping -- Wrap multiple mapping objects for lookup",
(PyObject*)NULL,PYTHON_API_VERSION);
if (PyErr_Occurred())
Py_FatalError("can't initialize module MultiMapping");
}
This module defines an extension type, 'MultiMapping', and exports a
module function, 'MultiMapping', that creates 'MultiMapping'
Instances. The type provides two methods, 'push', and 'pop', for
adding and removing mapping objects to the multi-mapping.
The type provides mapping behavior, implementing mapping length
and subscript operators but not mapping a subscript assignment
operator.
Now consider an extension class implementation of MultiMapping
objects::
#include "Python.h"
#include "ExtensionClass.h"
#define UNLESS(E) if(!(E))
typedef struct {
PyObject_HEAD
PyObject *data;
} MMobject;
staticforward PyExtensionClass MMtype;
static PyObject *
MM_push(self, args)
MMobject *self;
PyObject *args;
{
PyObject *src;
UNLESS(PyArg_ParseTuple(args, "O", &src)) return NULL;
UNLESS(-1 != PyList_Append(self->data,src)) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
MM_pop(self, args)
MMobject *self;
PyObject *args;
{
long l;
PyObject *r;
static PyObject *emptyList=0;
UNLESS(emptyList) UNLESS(emptyList=PyList_New(0)) return NULL;
UNLESS(PyArg_ParseTuple(args, "")) return NULL;
UNLESS(-1 != (l=PyList_Size(self->data))) return NULL;
l--;
UNLESS(r=PySequence_GetItem(self->data,l)) return NULL;
UNLESS(-1 != PyList_SetSlice(self->data,l,l+1,emptyList)) goto err;
return r;
err:
Py_DECREF(r);
return NULL;
}
static PyObject *
MM__init__(self, args)
MMobject *self;
PyObject *args;
{
UNLESS(PyArg_ParseTuple(args, "")) return NULL;
UNLESS(self->data=PyList_New(0)) goto err;
Py_INCREF(Py_None);
return Py_None;
err:
Py_DECREF(self);
return NULL;
}
static struct PyMethodDef MM_methods[] = {
{"__init__", (PyCFunction)MM__init__, 1,
"__init__() -- Create a new empty multi-mapping"},
{"push", (PyCFunction) MM_push, 1,
"push(mapping_object) -- Add a data source"},
{"pop", (PyCFunction) MM_pop, 1,
"pop() -- Remove and return the last data source added"},
{NULL, NULL} /* sentinel */
};
static void
MM_dealloc(self)
MMobject *self;
{
Py_XDECREF(self->data);
PyMem_DEL(self);
}
static PyObject *
MM_getattr(self, name)
MMobject *self;
char *name;
{
return Py_FindMethod(MM_methods, (PyObject *)self, name);
}
static int
MM_length(self)
MMobject *self;
{
long l=0, el, i;
PyObject *e=0;
UNLESS(-1 != (i=PyList_Size(self->data))) return -1;
while(--i >= 0)
{
e=PyList_GetItem(self->data,i);
UNLESS(-1 != (el=PyObject_Length(e))) return -1;
l+=el;
}
return l;
}
static PyObject *
MM_subscript(self, key)
MMobject *self;
PyObject *key;
{
long i;
PyObject *e;
UNLESS(-1 != (i=PyList_Size(self->data))) return NULL;
while(--i >= 0)
{
e=PyList_GetItem(self->data,i);
if(e=PyObject_GetItem(e,key)) return e;
PyErr_Clear();
}
PyErr_SetObject(PyExc_KeyError,key);
return NULL;
}
static PyMappingMethods MM_as_mapping = {
(inquiry)MM_length, /*mp_length*/
(binaryfunc)MM_subscript, /*mp_subscript*/
(objobjargproc)NULL, /*mp_ass_subscript*/
};
/* -------------------------------------------------------- */
static char MMtype__doc__[] =
"MultiMapping -- Combine multiple mapping objects for lookup"
;
static PyExtensionClass MMtype = {
PyObject_HEAD_INIT(&PyType_Type)
0, /*ob_size*/
"MultMapping", /*tp_name*/
sizeof(MMobject), /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
(destructor)MM_dealloc, /*tp_dealloc*/
(printfunc)0, /*tp_print*/
(getattrfunc)MM_getattr, /*tp_getattr*/
(setattrfunc)0, /*tp_setattr*/
(cmpfunc)0, /*tp_compare*/
(reprfunc)0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
&MM_as_mapping, /*tp_as_mapping*/
(hashfunc)0, /*tp_hash*/
(ternaryfunc)0, /*tp_call*/
(reprfunc)0, /*tp_str*/
/* Space for future expansion */
0L,0L,0L,0L,
MMtype__doc__, /* Documentation string */
METHOD_CHAIN(MM_methods)
};
static struct PyMethodDef MultiMapping_methods[] = {
{NULL, NULL} /* sentinel */
};
void
initMultiMapping()
{
PyObject *m, *d;
m = Py_InitModule4(
"MultiMapping", MultiMapping_methods,
"MultiMapping -- Wrap multiple mapping objects for lookup",
(PyObject*)NULL,PYTHON_API_VERSION);
d = PyModule_GetDict(m);
PyExtensionClass_Export(d,"MultiMapping",MMtype);
if (PyErr_Occurred())
Py_FatalError("can't initialize module MultiMapping");
}
This version includes 'ExtensionClass.h'. The two declarations of
'MMtype' have been changed from 'PyTypeObject' to 'PyExtensionClass'.
The 'METHOD_CHAIN' macro has been used to add methods to the end of
the definition for 'MMtype'. The module function, newMMobject has
been replaced by the 'MMtype' method, 'MM__init__'. Note that this
method does not create or return a new object. Finally, the lines::
d = PyModule_GetDict(m);
PyExtensionClass_Export(d,"MultiMapping",MMtype);
Have been added to both initialize the extension class and to export
it in the module dictionary.
To use this module, compile, link, and import it as with any other
extension module. The following python code illustrates the
module's use::
from MultiMapping import MultiMapping
m=MultiMapping()
m.push({'spam':1, 'eggs':2})
m.push({'spam':3, 'ham':4})
m['spam'] # returns 3
m['ham'] # returns 4
m['foo'] # raises a key error
Creating the 'MultiMapping' object took three steps, one to create
an empty 'MultiMapping', and two to add mapping objects to it. We
might wish to simplify the process of creating MultiMapping
objects by providing a constructor that takes source mapping
objects as parameters. We can do this by sub-classing MultiMapping
in Python::
from MultiMapping import MultiMapping
class ExtendedMultiMapping(MultiMapping):
def __init__(self,*data):
MultiMapping.__init__(self)
for d in data: self.push(d)
m=ExtendedMultiMapping({'spam':1, 'eggs':2}, {'spam':3, 'ham':4})
m['spam'] # returns 3
m['ham'] # returns 4
m['foo'] # raises a key error
Note that the source file included in the ExtensionClass
distribution has numerous enhancements beyond the version shown in
this document.
<p>
Extension Class</p>
<p> <a href="COPYRIGHT.html">Copyright (C) 1996-1998, Digital Creations</a>.</p>
<p> A lightweight mechanism has been developed for making Python
extension types more class-like. Classes can be developed in an
extension language, such as C or C++, and these classes can be
treated like other python classes:</p>
<ul><li><p>They can be sub-classed in python,</p>
</li>
<li><p>They provide access to method documentation strings, and</p>
</li>
<li><p>They can be used to directly create new instances.</p>
</li></ul>
<p> Extension classes provide additional extensions to class and
instance semantics, including:</p>
<ul><li><p>A protocol for accessing subobjects "in the context of" their
containers. This is used to implement custom method types
and <a href="Acquisition.html">environmental acquisition</a>.</p>
</li>
<li><p>A protocol for overriding method call semantics. This is used
to implement "synchonized" classes and could be used to
implement argument type checking.</p>
</li>
<li><p>A protocol for class initialization that supports execution of a
special <code>__class_init__</code> method after a class has been
initialized.</p>
</li></ul>
<p> Extension classes illustrate how the Python class mechanism can be
extended and may provide a basis for improved or specialized class
models. </p>
<h1>Releases</h1>
<p> The current release is <a href="ExtensionClass-1.2.tar.gz">1.2</a>
To find out what's changed in this release,
see the <a href="release.html">release notes</a>.</p>
<p> Documentation is available <a href="ExtensionClass.html">on-line</a>.</p>
<h1>Windows Binaries</h1>
<p> A win32 binary release, <a href="ec12.zip">ec12.zip</a> is available. This
release includes all of the ExtensionClass modules built as
Windows extension modules (.pyd) files. These were built for
Python 1.5.1 using Microsoft Visual C++ 5.0 in "Release" mode.</p>
<p>
<TABLE BORDER=1 CELLPADDING=2>
</TABLE></p>
Extension Class
"Copyright (C) 1996-1998, Digital Creations":COPYRIGHT.html.
A lightweight mechanism has been developed for making Python
extension types more class-like. Classes can be developed in an
extension language, such as C or C++, and these classes can be
treated like other python classes:
- They can be sub-classed in python,
- They provide access to method documentation strings, and
- They can be used to directly create new instances.
Extension classes provide additional extensions to class and
instance semantics, including:
- A protocol for accessing subobjects "in the context of" their
containers. This is used to implement custom method types
and "environmental acquisition":Acquisition.html.
- A protocol for overriding method call semantics. This is used
to implement "synchonized" classes and could be used to
implement argument type checking.
- A protocol for class initialization that supports execution of a
special '__class_init__' method after a class has been
initialized.
Extension classes illustrate how the Python class mechanism can be
extended and may provide a basis for improved or specialized class
models.
Releases
The current release is "1.2":ExtensionClass-1.2.tar.gz,
To find out what's changed in this release,
see the "release notes":release.html.
Documentation is available "on-line":ExtensionClass.html.
Windows Binaries
A win32 binary release, "ec12.zip":ec12.zip, is available. This
release includes all of the ExtensionClass modules built as
Windows extension modules (.pyd) files. These were built for
Python 1.5.1 using Microsoft Visual C++ 5.0 in "Release" mode.
Release Notes
1.2
This release provides some important bug fixes, some new features,
and a new copyright.
New functionality:
- One or more mapping objects can be passed to the MultiMapping
constructor.
- MultiMapping objects implement the has_key and get methods as
defined for Python 1.5 dictionaries.
Bugs fixed:
- When support was added for passing acquisition wrappers to
methods of data-less C mix-in classes, C-based __call_method__
hooks were broken. The most notable example of this was the
breaking of the Synchronized class.
- Calling C-base-class special methods from overriding methods,
as in::
class LowerMultiMapping(MultiMapping):
def __getitem__(self, key):
return MultiMapping.__getitem__(self, lower(key))
caused infinite loops.
- A typo in the Acquisition probably caused __delitem__
calls on wrapped mapping objects to fail.
1.1
New functionality:
- Changed the way that methods in pure mix-in classes are
constructed. Now methods are wrapped in such a way that
tricky wrapper objects (like Acquisition wrappers) can
bind them to wrapped objects.
- An "is subclass" test is provided via the macros
'ExtensionClassSubclass_Check', and
'ExtensionClassSubclassInstance_Check', which are
documented in 'ExtensionClass.h'.
- Methods and Acquisition wrappers use free list to improve
allocation and deallocation performance.
- Bound methods have attributes who's values are stored in
their instances.
- Added '__module__' attribute to ExtensionClasses to be
consistent with Python 1.5 classes and to work correctly
with 1.5 pickling.
- Added the '__basic__' new class method to allow
ExtensionClass instances to be unpickled without calling
their constructors.
- Acquired acquiring objects can nor acquire from the object
they were accessed in, in addition to the object they were
acquired from.
- Added new 'Acquisition' variable, 'Acquired', to support
"Controlled Acquisition'.
- Added a new 'aq_acquire' method for objects that subclass
'Acquisition.Implicit' or 'Acquisition.Explicit'. This
supports explicit acquisition and provides an option
filter function to support "Filtered Acquisiition".
The 'acquire' method available in earlier releases is still
available, but is deprecated.
Bugs fixed:
- There were some incorrect C-level error return values.
- A bug in handling method chains caused "C inheritence"
to fail. This only affected extension types that
inherited from extension types using method chains, *not*
extension subclasses defined in Python inheriting from
extension base classes defined in C.
- Expressions like 'not foo' or statements like::
if foo:
...
often failed for non-collection types because of an
incorrect attempt to take the 'len' of an object.
- Comparisons of objects with different classes didn't work
correctly.
- Instances provided access to their class '__bases__'
attribute.
1.0.2
Bugs fixed:
- Fixed bug in handling subclasses of Sequence objects.
- Fixed comparison bug in Missing objects.
1.0.1
Added functionality to and fixed bug in Missing module
- Fixed horible reference-counting bug
- Changed so that 'Missing.Value.spam(a1,a2,whatever)'
returns 'Missing.Value' for any method name (except
'__reduce__') and any arguments.
- Changed so that missing values are picklable. Note that
the special global, Missing.Value, is pickled in a
slightly more efficient manner than other missing values.
1.0
First non-beta release
This release is the result of a major rewrite and "hardening"
effort to increase performance and reliability. This version
is being used in several Digital Creations products, so if
parts are broken, we probably don't use them. :-)
This release also contains several new features and example
modules, including:
- Acquisition,
- Custom method calls,
- Class initialization protocol,
- A class method that makes it possible to explicitly call
Python base-class methods.
- A sample application of custom method calls that provides
Java-like synchronized classes that prevent more than one
thread from accessing an object's methods at one time.
Note that there is one known incompatibility with previous
releases. In previouse releases, the method used to support
context wrapping was named '__bind_to_object__'. The name of
this method was changed to '__of__' in this release and I do
not expect this name to change in the future.
<h1>Release Notes</h1>
<h2>1.2</h2>
<p> This release provides some important bug fixes, some new features,
and a new copyright.</p>
<p> New functionality:</p>
<ul><li><p>One or more mapping objects can be passed to the MultiMapping
constructor.</p>
</li>
<li><p>MultiMapping objects implement the has_key and get methods as
defined for Python 1.5 dictionaries.</p>
</li></ul>
<p> Bugs fixed:</p>
<ul><li><p>When support was added for passing acquisition wrappers to
methods of data-less C mix-in classes, C-based __call_method__
hooks were broken. The most notable example of this was the
breaking of the Synchronized class.</p>
</li>
<li><p>Calling C-base-class special methods from overriding methods,
as in::</p>
<PRE>
class LowerMultiMapping(MultiMapping):
def __getitem__(self, key):
return MultiMapping.__getitem__(self, lower(key))
caused infinite loops.
</PRE>
</li>
<li><p>A typo in the Acquisition probably caused __delitem__
calls on wrapped mapping objects to fail.</p>
</li></ul>
<h2>1.1</h2>
<p> New functionality:</p>
<ul><li><p>Changed the way that methods in pure mix-in classes are
constructed. Now methods are wrapped in such a way that
tricky wrapper objects (like Acquisition wrappers) can
bind them to wrapped objects.</p>
</li>
<li><p>An "is subclass" test is provided via the macros
<code>ExtensionClassSubclass_Check</code>, and
<code>ExtensionClassSubclassInstance_Check</code>, which are
documented in <code>ExtensionClass.h</code>.</p>
</li>
<li><p>Methods and Acquisition wrappers use free list to improve
allocation and deallocation performance.</p>
</li>
<li><p>Bound methods have attributes who's values are stored in
their instances.</p>
</li>
<li><p>Added <code>__module__</code> attribute to ExtensionClasses to be
consistent with Python 1.5 classes and to work correctly
with 1.5 pickling.</p>
</li>
<li><p>Added the <code>__basic__</code> new class method to allow
ExtensionClass instances to be unpickled without calling
their constructors.</p>
</li>
<li><p>Acquired acquiring objects can nor acquire from the object
they were accessed in, in addition to the object they were
acquired from.</p>
</li>
<li><p>Added new <code>Acquisition</code> variable, <code>Acquired</code>, to support
"Controlled Acquisition'.</p>
</li>
<li><p>Added a new <code>aq_acquire</code> method for objects that subclass
<code>Acquisition.Implicit</code> or <code>Acquisition.Explicit</code>. This
supports explicit acquisition and provides an option
filter function to support "Filtered Acquisiition".</p>
<p> The <code>acquire</code> method available in earlier releases is still
available, but is deprecated.</p>
</li></ul>
<p> Bugs fixed:</p>
<ul><li><p>There were some incorrect C-level error return values.</p>
</li>
<li><p>A bug in handling method chains caused "C inheritence"
to fail. This only affected extension types that
inherited from extension types using method chains, <em>not</em>
extension subclasses defined in Python inheriting from
extension base classes defined in C.</p>
</li>
<li><p>Expressions like <code>not foo</code> or statements like::</p>
<PRE>
if foo:
...
often failed for non-collection types because of an
incorrect attempt to take the 'len' of an object.
</PRE>
</li>
<li><p>Comparisons of objects with different classes didn't work
correctly.</p>
</li>
<li><p>Instances provided access to their class <code>__bases__</code>
attribute.</p>
</li></ul>
<h2>1.0.2</h2>
<p> Bugs fixed:</p>
<ul><li><p>Fixed bug in handling subclasses of Sequence objects.</p>
</li>
<li><p>Fixed comparison bug in Missing objects.</p>
</li></ul>
<h2>1.0.1</h2>
<p> Added functionality to and fixed bug in Missing module</p>
<ul><li><p>Fixed horible reference-counting bug</p>
</li>
<li><p>Changed so that <code>Missing.Value.spam(a1,a2,whatever)</code>
returns <code>Missing.Value</code> for any method name (except
<code>__reduce__</code>) and any arguments.</p>
</li>
<li><p>Changed so that missing values are picklable. Note that
the special global, Missing.Value, is pickled in a
slightly more efficient manner than other missing values.</p>
</li></ul>
<h2>1.0</h2>
<p> First non-beta release</p>
<p> This release is the result of a major rewrite and "hardening"
effort to increase performance and reliability. This version
is being used in several Digital Creations products, so if
parts are broken, we probably don't use them. :-)</p>
<p> This release also contains several new features and example
modules, including:</p>
<ul><li><p>Acquisition,</p>
</li>
<li><p>Custom method calls,</p>
</li>
<li><p>Class initialization protocol,</p>
</li>
<li><p>A class method that makes it possible to explicitly call
Python base-class methods.</p>
</li>
<li><p>A sample application of custom method calls that provides
Java-like synchronized classes that prevent more than one
thread from accessing an object's methods at one time.</p>
</li></ul>
<p> Note that there is one known incompatibility with previous
releases. In previouse releases, the method used to support
context wrapping was named <code>__bind_to_object__</code>. The name of
this method was changed to <code>__of__</code> in this release and I do
not expect this name to change in the future.
</p>
/*****************************************************************************
Copyright (c) 1996-2002 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
FOR A PARTICULAR PURPOSE
****************************************************************************/
#ifndef __ACQUISITION_H_
#define __ACQUISITION_H_
typedef struct {
PyObject *(*AQ_Acquire) (PyObject *obj, PyObject *name, PyObject *filter,
PyObject *extra, int explicit, PyObject *deflt,
int containment);
PyObject *(*AQ_Get) (PyObject *obj, PyObject *name, PyObject *deflt,
int containment);
int (*AQ_IsWrapper) (PyObject *obj);
PyObject *(*AQ_Base) (PyObject *obj);
PyObject *(*AQ_Parent) (PyObject *obj);
PyObject *(*AQ_Self) (PyObject *obj);
PyObject *(*AQ_Inner) (PyObject *obj);
PyObject *(*AQ_Chain) (PyObject *obj, int containment);
} ACQUISITIONCAPI;
#ifndef _IN_ACQUISITION_C
#define aq_Acquire(obj, name, filter, extra, explicit, deflt, containment ) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Acquire(obj, name, filter, extra, explicit, deflt, containment)))
#define aq_acquire(obj, name) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Acquire(obj, name, NULL, NULL, 1, NULL, 0)))
#define aq_get(obj, name, deflt, containment) (AcquistionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Get(obj, name, deflt, containment)))
#define aq_isWrapper(obj) (AcquisitionCAPI == NULL ? -1 : (AcquisitionCAPI->AQ_IsWrapper(obj)))
#define aq_base(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Base(obj)))
#define aq_parent(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Parent(obj)))
#define aq_self(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Self(obj)))
#define aq_inner(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Inner(obj)))
#define aq_chain(obj, containment) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_CHain(obj, containment)))
static ACQUISITIONCAPI *AcquisitionCAPI = NULL;
#define aq_init() { \
PyObject *module; \
PyObject *api; \
if (! (module = PyImport_ImportModule("Acquisition"))) return; \
if (! (api = PyObject_GetAttrString(module,"AcquisitionCAPI"))) return; \
Py_DECREF(module); \
AcquisitionCAPI = PyCObject_AsVoidPtr(api); \
Py_DECREF(api); \
}
#endif
#endif
/*****************************************************************************
Copyright (c) 1996-2002 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
FOR A PARTICULAR PURPOSE
****************************************************************************/
#include "ExtensionClass.h"
#define UNLESS(E) if(!(E))
#define OBJECT(O) ((PyObject*)(O))
typedef struct {
PyObject_HEAD
PyObject *callable;
int level;
} CA;
static PyObject *
CA__init__(CA *self, PyObject *args)
{
PyObject *callable;
int level=0;
UNLESS(PyArg_ParseTuple(args,"O|i",&callable, &level)) return NULL;
if (level > 0)
{
callable=PyObject_CallFunction(OBJECT(self->ob_type), "Oi",
callable, self->level-1);
UNLESS (callable) return NULL;
self->level=level;
}
else
{
Py_INCREF(callable);
self->level=0;
}
self->callable=callable;
Py_INCREF(Py_None);
return Py_None;
}
static void
CA_dealloc(CA *self)
{
Py_DECREF(self->callable);
Py_DECREF(self->ob_type);
PyMem_DEL(self);
}
static PyObject *
CA_of(CA *self, PyObject *args)
{
if (self->level > 0)
{
Py_INCREF(self->callable);
return self->callable;
}
if (PyString_Check(self->callable))
{
/* Special case string as simple alias. */
PyObject *o;
UNLESS (PyArg_ParseTuple(args,"O", &o)) return NULL;
return PyObject_GetAttr(o, self->callable);
}
return PyObject_CallObject(self->callable, args);
}
static struct PyMethodDef CA_methods[] = {
{"__init__",(PyCFunction)CA__init__, METH_VARARGS, ""},
{"__of__", (PyCFunction)CA_of, METH_VARARGS, ""},
{NULL, NULL} /* sentinel */
};
static PyExtensionClass ComputedAttributeType = {
PyObject_HEAD_INIT(NULL) 0,
"ComputedAttribute", sizeof(CA),
0,
(destructor)CA_dealloc,
0,0,0,0,0, 0,0,0, 0,0,0,0,0, 0,0,
"ComputedAttribute(callable) -- Create a computed attribute",
METHOD_CHAIN(CA_methods),
EXTENSIONCLASS_BINDABLE_FLAG
};
static struct PyMethodDef methods[] = {
{NULL, NULL}
};
void
initComputedAttribute(void)
{
PyObject *m, *d;
UNLESS(ExtensionClassImported) return;
ComputedAttributeType.tp_getattro=
(getattrofunc)PyExtensionClassCAPI->getattro;
/* Create the module and add the functions */
m = Py_InitModule4("ComputedAttribute", methods,
"Provide Computed Attributes\n\n"
"$Id: ComputedAttribute.c,v 1.8 2002/11/13 17:45:53 jeremy Exp $\n",
OBJECT(NULL),PYTHON_API_VERSION);
d = PyModule_GetDict(m);
PyExtensionClass_Export(d,"ComputedAttribute",ComputedAttributeType);
}
/*****************************************************************************
Copyright (c) 1996-2002 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
FOR A PARTICULAR PURPOSE
****************************************************************************/
/*
$Id: ExtensionClass.h,v 1.18 2002/08/26 13:33:01 Brian Exp $
Extension Class Definitions
Implementing base extension classes
A base extension class is implemented in much the same way that an
extension type is implemented, except:
- The include file, 'ExtensionClass.h', must be included.
- The type structure is declared to be of type
'PyExtensionClass', rather than of type 'PyTypeObject'.
- The type structure has an additional member that must be defined
after the documentation string. This extra member is a method chain
('PyMethodChain') containing a linked list of method definition
('PyMethodDef') lists. Method chains can be used to implement
method inheritance in C. Most extensions don't use method chains,
but simply define method lists, which are null-terminated arrays
of method definitions. A macro, 'METHOD_CHAIN' is defined in
'ExtensionClass.h' that converts a method list to a method chain.
(See the example below.)
- Module functions that create new instances must be replaced by an
'__init__' method that initializes, but does not create storage for
instances.
- The extension class must be initialized and exported to the module
with::
PyExtensionClass_Export(d,"name",type);
where 'name' is the module name and 'type' is the extension class
type object.
Attribute lookup
Attribute lookup is performed by calling the base extension class
'getattr' operation for the base extension class that includes C
data, or for the first base extension class, if none of the base
extension classes include C data. 'ExtensionClass.h' defines a
macro 'Py_FindAttrString' that can be used to find an object's
attributes that are stored in the object's instance dictionary or
in the object's class or base classes::
v = Py_FindAttrString(self,name);
In addition, a macro is provided that replaces 'Py_FindMethod'
calls with logic to perform the same sort of lookup that is
provided by 'Py_FindAttrString'.
Linking
The extension class mechanism was designed to be useful with
dynamically linked extension modules. Modules that implement
extension classes do not have to be linked against an extension
class library. The macro 'PyExtensionClass_Export' imports the
'ExtensionClass' module and uses objects imported from this module
to initialize an extension class with necessary behavior.
If you have questions regarding this software,
contact:
If you have questions regarding this software,
contact:
Zope Corporation
info@zope.com
(540) 371-6909
*/
#ifndef EXTENSIONCLASS_H
#define EXTENSIONCLASS_H
#include "Python.h"
#include "import.h"
/* Declarations for objects of type ExtensionClass */
typedef struct {
PyObject_VAR_HEAD
char *tp_name; /* For printing */
int tp_basicsize, tp_itemsize; /* For allocation */
/* Methods to implement standard operations */
destructor tp_dealloc;
printfunc tp_print;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
cmpfunc tp_compare;
reprfunc tp_repr;
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
/* More standard operations (at end for binary compatibility) */
hashfunc tp_hash;
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
setattrofunc tp_setattro;
/* Space for future expansion */
long tp_xxx3;
long tp_xxx4;
char *tp_doc; /* Documentation string */
#ifdef COUNT_ALLOCS
/* these must be last */
int tp_alloc;
int tp_free;
int tp_maxalloc;
struct _typeobject *tp_next;
#endif
/* Here's the juicy stuff: */
/* Put your method chain here. If you just have a method
list, you can use the METHON_CHAIN macro to make a chain.
*/
PyMethodChain methods;
/* You may set certain flags here. */
long class_flags;
/* The following flags are used by ExtensionClass */
#define EXTENSIONCLASS_DYNAMIC_FLAG 1 << 0
#define EXTENSIONCLASS_BINDABLE_FLAG 1 << 2
#define EXTENSIONCLASS_METHODHOOK_FLAG 1 << 3
#define EXTENSIONCLASS_INSTDICT_FLAG 1 << 4
#define EXTENSIONCLASS_NOINSTDICT_FLAG 1 << 5
#define EXTENSIONCLASS_BASICNEW_FLAG 1 << 6
#define EXTENSIONCLASS_PYTHONICATTR_FLAG 1 << 7
#define EXTENSIONCLASS_USERGETATTR_FLAG 1 << 8
#define EXTENSIONCLASS_USERSETATTR_FLAG 1 << 9
#define EXTENSIONCLASS_USERDELATTR_FLAG 1 << 10
/* The following flags are for use by extension class developers. */
#define EXTENSIONCLASS_USER_FLAG1 1 << 16
#define EXTENSIONCLASS_USER_FLAG2 1 << 17
#define EXTENSIONCLASS_USER_FLAG3 1 << 18
#define EXTENSIONCLASS_USER_FLAG4 1 << 19
#define EXTENSIONCLASS_USER_FLAG5 1 << 20
#define EXTENSIONCLASS_USER_FLAG6 1 << 21
#define EXTENSIONCLASS_USER_FLAG7 1 << 22
#define EXTENSIONCLASS_USER_FLAG8 1 << 23
#define EXTENSIONCLASS_USER_FLAG9 1 << 24
#define EXTENSIONCLASS_USER_FLAG10 1 << 25
#define EXTENSIONCLASS_USER_FLAG11 1 << 26
#define EXTENSIONCLASS_USER_FLAG12 1 << 27
#define EXTENSIONCLASS_USER_FLAG13 1 << 28
#define EXTENSIONCLASS_USER_FLAG14 1 << 29
#define EXTENSIONCLASS_USER_FLAG15 1 << 30
#define EXTENSIONCLASS_USER_FLAG16 1 << 31
/* This is the class dictionary, which is normally created for you.
If you wish, you can provide your own class dictionary object.
If you do provide your own class dictionary, it *must* be
a mapping object. If the object given is also an extension
instance, then sub-class instance dictionaries will be created
by calling the class dictionary's class with zero argumemts.
Otherwise, subclass dictionaries will be of the default type.
*/
PyObject *class_dictionary;
/* You should not set the remaining members. */
PyObject *bases;
PyObject *reserved;
} PyExtensionClass;
/* Following are macros that are needed or useful for defining extension
classes:
*/
/* This macro redefines Py_FindMethod to do attribute for an attribute
name given by a C string lookup using extension class meta-data.
This is used by older getattr implementations.
This macro is used in base class implementations of tp_getattr to
lookup methods or attributes that are not managed by the base type
directly. The macro is generally used to search for attributes
after other attribute searches have failed.
Note that in Python 1.4, a getattr operation may be provided that
uses an object argument. Classes that support this new operation
should use Py_FindAttr.
*/
#define Py_FindMethod(M,SELF,NAME) \
(PyExtensionClassCAPI->getattrs((SELF),(NAME)))
/* Do method or attribute lookup for an attribute name given by a C
string using extension class meta-data.
This macro is used in base class implementations of tp_getattro to
lookup methods or attributes that are not managed by the base type
directly. The macro is generally used to search for attributes
after other attribute searches have failed.
Note that in Python 1.4, a getattr operation may be provided that
uses an object argument. Classes that support this new operation
should use Py_FindAttr.
*/
#define Py_FindAttrString(SELF,NAME) \
(PyExtensionClassCAPI->getattrs((SELF),(NAME)))
/* Do method or attribute lookup using extension class meta-data.
This macro is used in base class implementations of tp_getattr to
lookup methods or attributes that are not managed by the base type
directly. The macro is generally used to search for attributes
after other attribute searches have failed. */
#define Py_FindAttr(SELF,NAME) (PyExtensionClassCAPI->getattro((SELF),(NAME)))
/* Do method or attribute assignment for an attribute name given by a
C string using extension class meta-data.
This macro is used in base class implementations of tp_setattr to
set attributes that are not managed by the base type directly. The
macro is generally used to assign attributes after other attribute
attempts to assign attributes have failed.
Note that in Python 1.4, a setattr operation may be provided that
uses an object argument. Classes that support this new operation
should use PyEC_SetAttr.
*/
#define PyEC_SetAttrString(SELF,NAME,V) \
(PyExtensionClassCAPI->setattrs((SELF),(NAME),(V)))
/* Do attribute assignment for an attribute.
This macro is used in base class implementations of tp_setattro to
set attributes that are not managed by the base type directly. The
macro is generally used to assign attributes after other attribute
attempts to assign attributes have failed.
*/
#define PyEC_SetAttr(SELF,NAME,V) \
(PyExtensionClassCAPI->setattro((SELF),(NAME),(V)))
/* Import the ExtensionClass CAPI */
#define ExtensionClassImported \
(PyExtensionClassCAPI=PyCObject_Import("ExtensionClass","CAPI"))
/* Make sure the C interface has been imported and import it if necessary.
This can be used in an if.
*/
#define MakeSureExtensionClassImported \
(PyExtensionClassCAPI || \
(PyExtensionClassCAPI=PyCObject_Import("ExtensionClass","CAPI")))
/* Export an Extension Base class in a given module dictionary with a
given name and ExtensionClass structure.
*/
#define PyExtensionClass_Export(D,N,T) \
if(PyExtensionClassCAPI || \
(PyExtensionClassCAPI= (struct ExtensionClassCAPIstruct*) \
PyCObject_Import("ExtensionClass","CAPI"))) \
{ PyExtensionClassCAPI->Export(D,N,&T); }
/* Convert a method list to a method chain. */
#define METHOD_CHAIN(DEF) { DEF, NULL }
/* The following macro checks whether a type is an extension class: */
#define PyExtensionClass_Check(TYPE) \
((PyObject*)(TYPE)->ob_type==PyExtensionClassCAPI->ExtensionClassType)
/* The following macro checks whether an instance is an extension instance: */
#define PyExtensionInstance_Check(INST) \
((PyObject*)(INST)->ob_type->ob_type== \
PyExtensionClassCAPI->ExtensionClassType)
/* The following macro checks for errors and prints out an error
message that is more informative than the one given by Python when
an extension module initialization fails.
*/
#define CHECK_FOR_ERRORS(MESS) \
if(PyErr_Occurred()) { \
PyObject *__sys_exc_type, *__sys_exc_value, *__sys_exc_traceback; \
PyErr_Fetch( &__sys_exc_type, &__sys_exc_value, \
&__sys_exc_traceback); \
fprintf(stderr, # MESS ":\n\t"); \
PyObject_Print(__sys_exc_type, stderr,0); \
fprintf(stderr,", "); \
PyObject_Print(__sys_exc_value, stderr,0); \
fprintf(stderr,"\n"); \
fflush(stderr); \
Py_FatalError(# MESS); \
}
/* The following macro can be used to define an extension base class
that only provides method and that is used as a pure mix-in class. */
#define PURE_MIXIN_CLASS(NAME,DOC,METHODS) \
static PyExtensionClass NAME ## Type = { PyObject_HEAD_INIT(NULL) \
0, # NAME, sizeof(PyPureMixinObject), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
0, 0, 0, 0, 0, 0, 0, DOC, {METHODS, NULL}, \
EXTENSIONCLASS_BASICNEW_FLAG}
/* The following macros provide limited access to extension-class
method facilities. */
/* Test for an ExtensionClass method: */
#define PyECMethod_Check(O) \
((PyObject*)(O)->ob_type==PyExtensionClassCAPI->MethodType)
/* Create a method object that wraps a callable object and an
instance. Note that if the callable object is an extension class
method, then the new method will wrap the callable object that is
wrapped by the extension class method. Also note that if the
callable object is an extension class method with a reference
count of 1, then the callable object will be rebound to the
instance and returned with an incremented reference count.
*/
#define PyECMethod_New(CALLABLE,INST) \
(PyExtensionClassCAPI->Method_New(CALLABLE,INST))
/* Return the instance that is bound by an extension class method. */
#define PyECMethod_Self(M) (((PyECMethodObject*)M)->self)
/* Check whether an object has an __of__ method for returning itself
in the context of it's container. */
#define has__of__(O) \
((O)->ob_type->ob_type == \
(PyTypeObject*)PyExtensionClassCAPI->ExtensionClassType && \
(((PyExtensionClass*)((O)->ob_type))->class_flags & \
EXTENSIONCLASS_BINDABLE_FLAG))
/* The following macros are used to check whether an instance
or a class' instanses have instance dictionaries: */
#define HasInstDict(O) \
((((PyExtensionClass*)((O)->ob_type))->class_flags & \
EXTENSIONCLASS_INSTDICT_FLAG))
#define ClassHasInstDict(O) (O->class_flags & EXTENSIONCLASS_INSTDICT_FLAG)
/* Get an object's instance dictionary. Use with caution */
#define INSTANCE_DICT(inst) \
*(((PyObject**)inst) + (inst->ob_type->tp_basicsize/sizeof(PyObject*) - 1))
/* Test whether an ExtensionClass, S, is a subclass of ExtensionClass C. */
#define ExtensionClassSubclass_Check(S,C) ( \
((PyObject*)(S)->ob_type==PyExtensionClassCAPI->ExtensionClassType) && \
((PyObject*)(C)->ob_type==PyExtensionClassCAPI->ExtensionClassType) && \
(PyExtensionClassCAPI->issubclass((PyExtensionClass *)(S), \
(PyExtensionClass *)(C))))
/* Test whether an ExtensionClass instance , I, is a subclass of
ExtensionClass C. */
#define ExtensionClassSubclassInstance_Check(I,C) ( \
((PyObject*)(I)->ob_type->ob_type== \
PyExtensionClassCAPI->ExtensionClassType) && \
((PyObject*)(C)->ob_type==PyExtensionClassCAPI->ExtensionClassType) && \
(PyExtensionClassCAPI->issubclass((PyExtensionClass *)((I)->ob_type), \
(PyExtensionClass *)(C))))
/* This let's you define built-in class methods. */
#define METH_CLASS_METHOD (2 << 17)
/*****************************************************************************
WARNING: EVERYTHING BELOW HERE IS PRIVATE TO THE EXTENSION CLASS INTERFACE
IMPLEMENTATION AND IS SUBJECT TO CHANGE !!!
*****************************************************************************/
static struct ExtensionClassCAPIstruct {
int (*Export)(PyObject *dict, char *name, PyExtensionClass *ob_type);
PyObject *(*getattrs)(PyObject *, char *);
PyObject *(*getattro)(PyObject *, PyObject *);
int (*setattrs)(PyObject *, char *, PyObject *);
int (*setattro)(PyObject *, PyObject *, PyObject *);
PyObject *ExtensionClassType;
PyObject *MethodType;
PyObject *(*Method_New)(PyObject *callable, PyObject *inst);
int (*issubclass)(PyExtensionClass *sub, PyExtensionClass *type);
} *PyExtensionClassCAPI = NULL;
typedef struct { PyObject_HEAD } PyPureMixinObject;
typedef struct {
PyObject_HEAD
PyTypeObject *type;
PyObject *self;
PyObject *meth;
} PyECMethodObject; /* AKA PMethod */
/* The following is to avoid whining from 1.5 :-) */
#define PyCObject_Import PyCObject_Import14
static void *
PyCObject_Import14(char *module_name, char *name)
{
PyObject *m, *c;
void *r=NULL;
if((m=PyImport_ImportModule(module_name)))
{
if((c=PyObject_GetAttrString(m,name)))
{
r=PyCObject_AsVoidPtr(c);
Py_DECREF(c);
}
Py_DECREF(m);
}
return r;
}
#endif
/*****************************************************************************
Copyright (c) 1996-2002 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
FOR A PARTICULAR PURPOSE
****************************************************************************/
#include "ExtensionClass.h"
static PyObject *
of(PyObject *self, PyObject *args)
{
PyObject *inst;
if(PyArg_Parse(args,"O",&inst)) return PyECMethod_New(self,inst);
else return NULL;
}
struct PyMethodDef Method_methods[] = {
{"__of__",(PyCFunction)of,0,""},
{NULL, NULL} /* sentinel */
};
static struct PyMethodDef methods[] = {{NULL, NULL}};
void
initMethodObject(void)
{
PyObject *m, *d;
PURE_MIXIN_CLASS(Method,
"Base class for objects that want to be treated as methods\n"
"\n"
"The method class provides a method, __of__, that\n"
"binds an object to an instance. If a method is a subobject\n"
"of an extension-class instance, the the method will be bound\n"
"to the instance and when the resulting object is called, it\n"
"will call the method and pass the instance in addition to\n"
"other arguments. It is the responsibility of Method objects\n"
"to implement (or inherit) a __call__ method.\n",
Method_methods);
/* Create the module and add the functions */
m = Py_InitModule4("MethodObject", methods,
"Method-object mix-in class module\n\n"
"$Id: MethodObject.c,v 1.9 2002/11/13 17:45:53 jeremy Exp $\n",
(PyObject*)NULL,PYTHON_API_VERSION);
d = PyModule_GetDict(m);
PyExtensionClass_Export(d,"Method",MethodType);
}
/*****************************************************************************
Copyright (c) 1996-2002 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
FOR A PARTICULAR PURPOSE
****************************************************************************/
static char Missing_module_documentation[] =
""
"\n$Id: Missing.c,v 1.20 2003/05/09 21:50:36 jeremy Exp $"
;
#include "ExtensionClass.h"
/* Declarations for objects of type Missing */
typedef struct {
PyObject_HEAD
} Missing;
static PyObject *vname=0, *Missing_dot_Value=0, *empty_string=0, *reduce=0;
static PyObject *theValue, *notMissing;
static void
Missing_dealloc(Missing *self)
{
Py_DECREF(self->ob_type);
PyMem_DEL(self);
}
static PyObject *
Missing_repr(Missing *self)
{
Py_INCREF(Missing_dot_Value);
return Missing_dot_Value;
}
static PyObject *
Missing_str(Missing *self)
{
Py_INCREF(empty_string);
return empty_string;
}
/* Code to access Missing objects as numbers.
We must guarantee that notMissing is never returned to Python code,
because it would violate the guarantee that all Python-accessible
Missing values are equal to each other.
*/
static PyObject *
Missing_bin(PyObject *v, PyObject *w)
{
if (v == notMissing)
v = w;
assert(v != notMissing);
Py_INCREF(v);
return v;
}
static PyObject *
Missing_pow(PyObject *v, PyObject *w, PyObject *z)
{
if (v == notMissing)
v = w;
assert(v != notMissing);
Py_INCREF(v);
return v;
}
static PyObject *
Missing_un(PyObject *v)
{
Py_INCREF(v);
return v;
}
static int
Missing_nonzero(PyObject *v)
{
return 0;
}
/* Always return the distinguished notMissing object as the result
of the coercion. The notMissing object does not compare equal
to other Missing objects.
*/
static int
Missing_coerce(PyObject **pv, PyObject **pw)
{
Py_INCREF(*pv);
Py_INCREF(notMissing);
*pw = notMissing;
return 0;
}
static PyNumberMethods Missing_as_number = {
(binaryfunc)Missing_bin, /*nb_add*/
(binaryfunc)Missing_bin, /*nb_subtract*/
(binaryfunc)Missing_bin, /*nb_multiply*/
(binaryfunc)Missing_bin, /*nb_divide*/
(binaryfunc)Missing_bin, /*nb_remainder*/
(binaryfunc)Missing_bin, /*nb_divmod*/
(ternaryfunc)Missing_pow, /*nb_power*/
(unaryfunc)Missing_un, /*nb_negative*/
(unaryfunc)Missing_un, /*nb_positive*/
(unaryfunc)Missing_un, /*nb_absolute*/
(inquiry)Missing_nonzero, /*nb_nonzero*/
(unaryfunc)Missing_un, /*nb_invert*/
(binaryfunc)Missing_bin, /*nb_lshift*/
(binaryfunc)Missing_bin, /*nb_rshift*/
(binaryfunc)Missing_bin, /*nb_and*/
(binaryfunc)Missing_bin, /*nb_xor*/
(binaryfunc)Missing_bin, /*nb_or*/
(coercion)Missing_coerce, /*nb_coerce*/
0, /*nb_int*/
0, /*nb_long*/
0, /*nb_float*/
0, /*nb_oct*/
0, /*nb_hex*/
#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION != 1
0, /* nb_inplace_add */
0, /* nb_inplace_subtract */
0, /* nb_inplace_multiply */
0, /* nb_inplace_divide */
0, /* nb_inplace_remainder */
0, /* nb_inplace_power */
0, /* nb_inplace_lshift */
0, /* nb_inplace_rshift */
0, /* nb_inplace_and */
0, /* nb_inplace_xor */
0, /* nb_inplace_or */
Missing_bin, /* nb_floor_divide */
Missing_bin, /* nb_true_divide */
0, /* nb_inplace_floor_divide */
0, /* nb_inplace_true_divide */
#endif
};
/* ------------------------------------------------------- */
static PyObject *
Missing_reduce(PyObject *self, PyObject *args, PyObject *kw)
{
if(self==theValue)
{
Py_INCREF(vname);
return vname;
}
return Py_BuildValue("O()",self->ob_type);
}
static struct PyMethodDef reduce_ml[] = {
{"__reduce__", (PyCFunction)Missing_reduce, 1,
"Return a missing value reduced to standard python objects"
}
};
static PyObject *
Missing_getattr(PyObject *self, PyObject *name)
{
char *c, *legal;
if(!(c=PyString_AsString(name))) return NULL;
legal=c;
if (strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
*legal) != NULL)
{
for (legal++; *legal; legal++)
if (strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_",
*legal) == NULL)
{
legal=NULL;
break;
}
}
else legal=NULL;
if(! legal)
{
if(strcmp(c,"__reduce__")==0)
{
if(self==theValue)
{
Py_INCREF(reduce);
return reduce;
}
return PyCFunction_New(reduce_ml, self);
}
PyErr_SetObject(PyExc_AttributeError, name);
return NULL;
}
Py_INCREF(self);
return self;
}
static PyObject *
Missing_call(PyObject *self, PyObject *args, PyObject *kw)
{
Py_INCREF(self);
return self;
}
/* All Missing objects are equal to each other, except for the
special notMissing object. It is returned by coerce to
indicate that Missing is being compare to something else.
*/
static int
Missing_cmp(PyObject *m1, PyObject *m2)
{
if (m1 == notMissing)
return -1;
return (m2 == notMissing);
}
static PyExtensionClass MissingType = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"Missing", /*tp_name*/
sizeof(Missing), /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
(destructor)Missing_dealloc, /*tp_dealloc*/
(printfunc)0, /*tp_print*/
(getattrfunc)0, /*obsolete tp_getattr*/
(setattrfunc)0, /*obsolete tp_setattr*/
Missing_cmp, /*tp_compare*/
(reprfunc)Missing_repr, /*tp_repr*/
&Missing_as_number, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
(hashfunc)0, /*tp_hash*/
(ternaryfunc)Missing_call, /*tp_call*/
(reprfunc)Missing_str, /*tp_str*/
(getattrofunc)Missing_getattr, /*tp_getattro*/
(setattrofunc)0, /*tp_setattro*/
/* Space for future expansion */
0L,0L,
"Represent totally unknown quantities\n"
"\n"
"Missing values are used to represent numeric quantities that are\n"
"unknown. They support all mathematical operations except\n"
"conversions by returning themselves.\n",
METHOD_CHAIN(NULL)
};
/* End of code for Missing objects */
/* -------------------------------------------------------- */
/* List of methods defined in the module */
static struct PyMethodDef Module_Level__methods[] = {
{NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */
};
void
initMissing(void)
{
PyObject *m, *d;
if(! ((vname=PyString_FromString("V"))
&& (Missing_dot_Value=PyString_FromString("Missing.Value"))
&& (empty_string=PyString_FromString(""))
)) return;
/* Create the module and add the functions */
m = Py_InitModule4("Missing", Module_Level__methods,
Missing_module_documentation,
(PyObject*)NULL,PYTHON_API_VERSION);
/* Add some symbolic constants to the module */
d = PyModule_GetDict(m);
PyExtensionClass_Export(d,"Missing",MissingType);
theValue = PyObject_CallObject((PyObject*)&MissingType, NULL);
notMissing = PyObject_CallObject((PyObject*)&MissingType, NULL);
reduce=PyCFunction_New(reduce_ml, theValue);
PyDict_SetItemString(d, "Value", theValue);
PyDict_SetItemString(d, "V", theValue);
PyDict_SetItemString(d, "MV", theValue);
}
/*****************************************************************************
Copyright (c) 1996-2002 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
FOR A PARTICULAR PURPOSE
****************************************************************************/
#include "ExtensionClass.h"
#define UNLESS(E) if(!(E))
typedef struct {
PyObject_HEAD
PyObject *data;
} MMobject;
staticforward PyExtensionClass MMtype;
static PyObject *
MM_push(MMobject *self, PyObject *args)
{
PyObject *src;
UNLESS(PyArg_ParseTuple(args, "O", &src)) return NULL;
UNLESS(-1 != PyList_Append(self->data,src)) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
MM_pop(MMobject *self, PyObject *args)
{
int i=1, l;
PyObject *r;
if(args) UNLESS(PyArg_ParseTuple(args, "|i", &i)) return NULL;
if((l=PyList_Size(self->data)) < 0) return NULL;
i=l-i;
UNLESS(r=PySequence_GetItem(self->data,l-1)) return NULL;
if(PyList_SetSlice(self->data,i,l,NULL) < 0) goto err;
return r;
err:
Py_DECREF(r);
return NULL;
}
static PyObject *
MM__init__(MMobject *self, PyObject *args)
{
UNLESS(self->data=PyList_New(0)) return NULL;
if (args)
{
int l, i;
if ((l=PyTuple_Size(args)) < 0) return NULL;
for (i=0; i < l; i++)
if (PyList_Append(self->data, PyTuple_GET_ITEM(args, i)) < 0)
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
MM_subscript(MMobject *self, PyObject *key)
{
long i;
PyObject *e;
UNLESS(-1 != (i=PyList_Size(self->data))) return NULL;
while(--i >= 0)
{
e=PyList_GetItem(self->data,i);
if((e=PyObject_GetItem(e,key))) return e;
PyErr_Clear();
}
PyErr_SetObject(PyExc_KeyError,key);
return NULL;
}
static PyObject *
MM_has_key(MMobject *self, PyObject *args)
{
PyObject *key;
UNLESS(PyArg_ParseTuple(args,"O",&key)) return NULL;
if((key=MM_subscript(self, key)))
{
Py_DECREF(key);
return PyInt_FromLong(1);
}
PyErr_Clear();
return PyInt_FromLong(0);
}
static PyObject *
MM_get(MMobject *self, PyObject *args)
{
PyObject *key, *d=Py_None;
UNLESS(PyArg_ParseTuple(args,"O|O",&key,&d)) return NULL;
if((key=MM_subscript(self, key))) return key;
PyErr_Clear();
Py_INCREF(d);
return d;
}
static struct PyMethodDef MM_methods[] = {
{"__init__", (PyCFunction)MM__init__, METH_VARARGS,
"__init__([m1, m2, ...]) -- Create a new empty multi-mapping"},
{"get", (PyCFunction) MM_get, METH_VARARGS,
"get(key,[default]) -- Return a value for the given key or a default"},
{"has_key", (PyCFunction) MM_has_key, METH_VARARGS,
"has_key(key) -- Return 1 if the mapping has the key, and 0 otherwise"},
{"push", (PyCFunction) MM_push, METH_VARARGS,
"push(mapping_object) -- Add a data source"},
{"pop", (PyCFunction) MM_pop, METH_VARARGS,
"pop([n]) -- Remove and return the last data source added"},
{NULL, NULL} /* sentinel */
};
static void
MM_dealloc(MMobject *self)
{
Py_XDECREF(self->data);
Py_DECREF(self->ob_type);
PyMem_DEL(self);
}
static PyObject *
MM_getattr(MMobject *self, char *name)
{
return Py_FindMethod(MM_methods, (PyObject *)self, name);
}
static int
MM_length(MMobject *self)
{
long l=0, el, i;
PyObject *e=0;
UNLESS(-1 != (i=PyList_Size(self->data))) return -1;
while(--i >= 0)
{
e=PyList_GetItem(self->data,i);
UNLESS(-1 != (el=PyObject_Length(e))) return -1;
l+=el;
}
return l;
}
static PyMappingMethods MM_as_mapping = {
(inquiry)MM_length, /*mp_length*/
(binaryfunc)MM_subscript, /*mp_subscript*/
(objobjargproc)NULL, /*mp_ass_subscript*/
};
/* -------------------------------------------------------- */
static char MMtype__doc__[] =
"MultiMapping -- Combine multiple mapping objects for lookup"
;
static PyExtensionClass MMtype = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"MultiMapping", /*tp_name*/
sizeof(MMobject), /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
(destructor)MM_dealloc, /*tp_dealloc*/
(printfunc)0, /*tp_print*/
(getattrfunc)MM_getattr, /*tp_getattr*/
(setattrfunc)0, /*tp_setattr*/
(cmpfunc)0, /*tp_compare*/
(reprfunc)0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
&MM_as_mapping, /*tp_as_mapping*/
(hashfunc)0, /*tp_hash*/
(ternaryfunc)0, /*tp_call*/
(reprfunc)0, /*tp_str*/
/* Space for future expansion */
0L,0L,0L,0L,
MMtype__doc__, /* Documentation string */
METHOD_CHAIN(MM_methods)
};
static struct PyMethodDef MultiMapping_methods[] = {
{NULL, NULL} /* sentinel */
};
void
initMultiMapping(void)
{
PyObject *m, *d;
m = Py_InitModule4(
"MultiMapping", MultiMapping_methods,
"MultiMapping -- Wrap multiple mapping objects for lookup"
"\n\n"
"$Id: MultiMapping.c,v 1.12 2002/11/12 20:06:22 jeremy Exp $\n",
(PyObject*)NULL,PYTHON_API_VERSION);
d = PyModule_GetDict(m);
PyExtensionClass_Export(d,"MultiMapping",MMtype);
if (PyErr_Occurred()) Py_FatalError("can't initialize module MultiMapping");
}
/*****************************************************************************
Copyright (c) 1996-2002 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
FOR A PARTICULAR PURPOSE
****************************************************************************/
static char Record_module_documentation[] =
""
"\n$Id: Record.c,v 1.18 2002/11/13 17:45:53 jeremy Exp $"
;
#ifdef PERSISTENCE
#include "cPersistence.h"
#else
#include "ExtensionClass.h"
#endif
/* ----------------------------------------------------- */
static void PyVar_Assign(PyObject **v, PyObject *e) { Py_XDECREF(*v); *v=e;}
#define ASSIGN(V,E) PyVar_Assign(&(V),(E))
#define UNLESS(E) if(!(E))
#define UNLESS_ASSIGN(V,E) ASSIGN(V,E); UNLESS(V)
#define OBJECT(O) ((PyObject*)(O))
static PyObject *py___record_schema__;
/* Declarations for objects of type Record */
typedef struct {
#ifdef PERSISTENCE
cPersistent_HEAD
#else
PyObject_HEAD
#endif
PyObject **data;
PyObject *schema;
} Record;
staticforward PyExtensionClass RecordType;
/* ---------------------------------------------------------------- */
static int
Record_init(Record *self)
{
int l;
UNLESS(self->schema)
UNLESS(self->schema=PyObject_GetAttr(OBJECT(self->ob_type),
py___record_schema__)) return -1;
if((l=PyObject_Length(self->schema)) < 0) return -1;
UNLESS(self->data)
{
UNLESS(self->data=malloc(sizeof(PyObject*)*(l+1)))
{
PyErr_NoMemory();
return -1;
}
memset(self->data, 0, sizeof(PyObject*)*(l+1));
}
return l;
}
static PyObject *
Record___setstate__(Record *self, PyObject *args)
{
PyObject *state=0, *parent, **d;
int l, ls, i;
if((l=Record_init(self)) < 0) return NULL;
UNLESS(PyArg_ParseTuple(args, "|OO", &state, &parent)) return NULL;
if(state) {
if(PyDict_Check(state))
{
PyObject *k, *v;
for(i=0; PyDict_Next(state, &i, &k, &v);)
if(k && v && PyObject_SetAttr(OBJECT(self),k,v) < 0)
PyErr_Clear();
}
else
{
if((ls=PyObject_Length(state)) < 0) return NULL;
for(i=0, d=self->data; i < l && i < ls; i++, d++)
{
ASSIGN(*d, PySequence_GetItem(state, i));
UNLESS(*d) return NULL;
}
}
}
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
Record___getstate__( Record *self, PyObject *args)
{
PyObject *r, **d, *v;
int i, l;
UNLESS(self->data) return PyTuple_New(0);
if((l=Record_init(self)) < 0) return NULL;
UNLESS(r=PyTuple_New(l)) return NULL;
for(d=self->data, i=0; i < l; i++, d++)
{
v= *d;
if(!v) v=Py_None;
Py_INCREF(v);
PyTuple_SET_ITEM(r,i,v);
}
return r;
}
static void
Record_deal(Record *self)
{
int l;
PyObject **d;
if(self->schema)
{
l=PyObject_Length(self->schema);
for(d=self->data; --l >= 0; d++)
{
Py_XDECREF(*d);
}
Py_DECREF(self->schema);
free(self->data);
}
}
#ifdef PERSISTENCE
static PyObject *
Record__p_deactivate(Record *self, PyObject *args)
{
Record_deal(self);
self->schema=NULL;
self->data=NULL;
PER_GHOSTIFY(self);
Py_INCREF(Py_None);
return Py_None;
}
#endif
static struct PyMethodDef Record_methods[] = {
{"__getstate__", (PyCFunction)Record___getstate__, METH_VARARGS,
"__getstate__() -- Get the record's data"
},
{"__setstate__", (PyCFunction)Record___setstate__, METH_VARARGS,
"__setstate__(v) -- Set the record's data"
},
{"__init__", (PyCFunction)Record___setstate__, METH_VARARGS,
"__init__([v]) -- Initialize a record, possibly with state"
},
#ifdef PERSISTENCE
{"_p_deactivate", (PyCFunction)Record__p_deactivate, METH_VARARGS,
"_p_deactivate() -- Reset the record's data"
},
#endif
{NULL, NULL} /* sentinel */
};
/* ---------- */
static void
Record_dealloc(Record *self)
{
Record_deal(self);
Py_DECREF(self->ob_type);
PyMem_DEL(self);
}
static PyObject *
Record_getattr(Record *self, PyObject *name)
{
int l, i;
PyObject *io;
if((l=Record_init(self)) < 0) return NULL;
if ((io=Py_FindAttr((PyObject *)self, name))) return io;
PyErr_Clear();
if((io=PyObject_GetItem(self->schema, name)))
{
UNLESS(PyInt_Check(io))
{
PyErr_SetString(PyExc_TypeError, "invalid record schema");
return NULL;
}
i=PyInt_AsLong(io);
if(i >= 0 && i < l)
{
ASSIGN(io, self->data[i]);
UNLESS(io) io=Py_None;
}
else ASSIGN(io, Py_None);
Py_INCREF(io);
return io;
}
PyErr_SetObject(PyExc_AttributeError, name);
return NULL;
}
static int
Record_setattr(Record *self, PyObject *name, PyObject *v)
{
int l, i;
PyObject *io;
if((l=Record_init(self)) < 0) return -1;
if((io=PyObject_GetItem(self->schema, name)))
{
UNLESS(PyInt_Check(io))
{
PyErr_SetString(PyExc_TypeError, "invalid record schema");
return -1;
}
i=PyInt_AsLong(io);
Py_DECREF(io);
if(i >= 0 && i < l)
{
Py_XINCREF(v);
ASSIGN(self->data[i],v);
return 0;
}
}
PyErr_SetObject(PyExc_AttributeError, name);
return -1;
}
#ifdef PERSISTENCE
static PyObject *
pRecord_getattr(Record *self, PyObject *name)
{
char *c;
UNLESS(c=PyString_AsString(name)) return NULL;
return cPersistenceCAPI->pergetattro(OBJECT(self),name,c,
Record_getattr);
}
static int
pRecord_setattr(Record *self, PyObject *name, PyObject *v)
{
return cPersistenceCAPI->persetattro(OBJECT(self),name,v,Record_setattr);
}
#endif
static int
Record_compare(Record *v, Record *w)
{
int lv, lw, i, c;
PyObject **dv, **dw;
if((lv=Record_init(v)) < 0) return -1;
if((lw=Record_init(w)) < 0) return -1;
if(lw < lv) lv=lw;
for(i=0, dv=v->data, dw=w->data; i < lv; i++, dv++, dw++)
{
if(*dv)
if(*dw)
{
if((c=PyObject_Compare(*dv,*dw))) return c;
}
else return 1;
else if(*dw) return -1;
}
if(*dv) return 1;
if(*dw) return -1;
return 0;
}
/* Code to handle accessing Record objects as sequence objects */
static PyObject *
Record_concat(Record *self, PyObject *bb)
{
PyErr_SetString(PyExc_TypeError,
"Record objects do not support concatenation");
return NULL;
}
static PyObject *
Record_repeat(Record *self, int n)
{
PyErr_SetString(PyExc_TypeError,
"Record objects do not support repetition");
return NULL;
}
static PyObject *
IndexError(int i)
{
PyObject *v;
if((v=PyInt_FromLong(i)))
{
PyErr_SetObject(PyExc_IndexError, v);
Py_DECREF(v);
}
return NULL;
}
static PyObject *
Record_item(Record *self, int i)
{
PyObject *o;
int l;
if((l=Record_init(self)) < 0) return NULL;
if(i < 0 || i >= l) return IndexError(i);
o=self->data[i];
UNLESS(o) o=Py_None;
Py_INCREF(o);
return o;
}
static PyObject *
Record_slice(Record *self, int ilow, int ihigh)
{
PyErr_SetString(PyExc_TypeError,
"Record objects do not support slicing");
return NULL;
}
static int
Record_ass_item(Record *self, int i, PyObject *v)
{
int l;
if((l=Record_init(self)) < 0) return -1;
if(i < 0 || i >= l)
{
IndexError(i);
return -1;
}
UNLESS(v)
{
PyErr_SetString(PyExc_TypeError,"cannot delete record items");
return -1;
}
Py_INCREF(v);
ASSIGN(self->data[i], v);
return 0;
}
static int
Record_ass_slice(Record *self, int ilow, int ihigh, PyObject *v)
{
PyErr_SetString(PyExc_TypeError,
"Record objects do not support slice assignment");
return -1;
}
static PySequenceMethods Record_as_sequence = {
(inquiry)Record_init, /*sq_length*/
(binaryfunc)Record_concat, /*sq_concat*/
(intargfunc)Record_repeat, /*sq_repeat*/
(intargfunc)Record_item, /*sq_item*/
(intintargfunc)Record_slice, /*sq_slice*/
(intobjargproc)Record_ass_item, /*sq_ass_item*/
(intintobjargproc)Record_ass_slice, /*sq_ass_slice*/
};
/* -------------------------------------------------------------- */
static PyObject *
Record_subscript(Record *self, PyObject *key)
{
int i, l;
PyObject *io;
if((l=Record_init(self)) < 0) return NULL;
if(PyInt_Check(key))
{
i=PyInt_AsLong(key);
if(i<0) i+=l;
return Record_item(self, i);
}
if((io=PyObject_GetItem(self->schema, key)))
{
UNLESS(PyInt_Check(io))
{
PyErr_SetString(PyExc_TypeError, "invalid record schema");
return NULL;
}
i=PyInt_AsLong(io);
if(i >= 0 && i < l)
{
ASSIGN(io, self->data[i]);
UNLESS(io) io=Py_None;
}
else ASSIGN(io, Py_None);
Py_INCREF(io);
return io;
}
PyErr_Clear();
if ((io=PyObject_GetAttr(OBJECT(self), key))) return io;
PyErr_SetObject(PyExc_KeyError, key);
return NULL;
}
static int
Record_ass_sub(Record *self, PyObject *key, PyObject *v)
{
int i, l;
PyObject *io;
if((l=Record_init(self)) < 0) return -1;
if(PyInt_Check(key))
{
i=PyInt_AsLong(key);
if(i<0) i+=l;
return Record_ass_item(self, i, v);
}
if((io=PyObject_GetItem(self->schema, key)))
{
UNLESS(PyInt_Check(io))
{
PyErr_SetString(PyExc_TypeError, "invalid record schema");
return -1;
}
i=PyInt_AsLong(io);
Py_DECREF(io);
if(i >= 0 && i < l)
{
Py_XINCREF(v);
ASSIGN(self->data[i],v);
return 0;
}
}
return -1;
}
static PyMappingMethods Record_as_mapping = {
(inquiry)Record_init, /*mp_length*/
(binaryfunc)Record_subscript, /*mp_subscript*/
(objobjargproc)Record_ass_sub, /*mp_ass_subscript*/
};
/* -------------------------------------------------------- */
static PyExtensionClass RecordType = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"Record", /*tp_name*/
sizeof(Record), /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
(destructor)Record_dealloc, /*tp_dealloc*/
(printfunc)0, /*tp_print*/
(getattrfunc)0, /*obsolete tp_getattr*/
(setattrfunc)0, /*obsolete tp_setattr*/
(cmpfunc)Record_compare, /*tp_compare*/
(reprfunc)0, /*tp_repr*/
0, /*tp_as_number*/
&Record_as_sequence, /*tp_as_sequence*/
&Record_as_mapping, /*tp_as_mapping*/
(hashfunc)0, /*tp_hash*/
(ternaryfunc)0, /*tp_call*/
(reprfunc)0, /*tp_str*/
#ifdef PERSISTENCE
(getattrofunc)pRecord_getattr, /*tp_getattro*/
(setattrofunc)pRecord_setattr, /*tp_setattro*/
#else
(getattrofunc)Record_getattr, /*tp_getattro*/
(setattrofunc)Record_setattr, /*tp_setattro*/
#endif
/* Space for future expansion */
0L,0L,
"Simple Record Types", /* Documentation string */
METHOD_CHAIN(Record_methods),
EXTENSIONCLASS_NOINSTDICT_FLAG,
};
/* End of code for Record objects */
/* -------------------------------------------------------- */
/* List of methods defined in the module */
static struct PyMethodDef Module_Level__methods[] = {
{NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */
};
/* Initialization function for the module (*must* be called initRecord) */
void
initRecord(void)
{
PyObject *m, *d;
UNLESS(py___record_schema__=PyString_FromString("__record_schema__")) return;
UNLESS(ExtensionClassImported) return;
#ifdef PERSISTENCE
if(cPersistenceCAPI=PyCObject_Import("cPersistence","CAPI"))
{
static PyMethodChain m;
m.methods=RecordType.methods.methods;
RecordType.methods.methods=cPersistenceCAPI->methods->methods;
RecordType.methods.link=&m;
}
else return;
#endif
/* Create the module and add the functions */
m = Py_InitModule4("Record", Module_Level__methods,
Record_module_documentation,
(PyObject*)NULL,PYTHON_API_VERSION);
/* Add some symbolic constants to the module */
d = PyModule_GetDict(m);
PyExtensionClass_Export(d,"Record",RecordType);
}
/*****************************************************************************
Copyright (c) 1996-2002 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
FOR A PARTICULAR PURPOSE
****************************************************************************/
static char ThreadLock_module_documentation[] =
""
"\n$Id: ThreadLock.c,v 1.13 2002/06/10 22:48:46 jeremy Exp $"
;
#include "Python.h"
#ifdef WITH_THREAD
#include "listobject.h"
#ifdef PyList_SET_ITEM
#include "pythread.h"
#define get_thread_ident PyThread_get_thread_ident
#define acquire_lock PyThread_acquire_lock
#define release_lock PyThread_release_lock
#define type_lock PyThread_type_lock
#define free_lock PyThread_free_lock
#define allocate_lock PyThread_allocate_lock
#else
#include "thread.h"
#endif
#endif
static PyObject *ErrorObject;
/* ----------------------------------------------------- */
#define UNLESS(E) if(!(E))
/* Declarations for objects of type ThreadLock */
typedef struct {
PyObject_HEAD
int count;
long id;
#ifdef WITH_THREAD
type_lock lock;
#endif
} ThreadLockObject;
staticforward PyTypeObject ThreadLockType;
static int
cacquire(ThreadLockObject *self, int wait)
{
int acquired = 1;
#ifdef WITH_THREAD
long id = get_thread_ident();
#else
long id = 1;
#endif
if(self->count >= 0 && self->id==id)
{
/* Somebody has locked me. It is either the current thread or
another thread. */
/* So this thread has it. I can't have a race condition, because,
if another thread had the lock, then the id would not be this
one. */
self->count++;
}
else
{
#ifdef WITH_THREAD
Py_BEGIN_ALLOW_THREADS
acquired = acquire_lock(self->lock, wait ? WAIT_LOCK : NOWAIT_LOCK);
Py_END_ALLOW_THREADS
#endif
if (acquired)
{
self->count=0;
self->id=id;
}
}
return acquired;
}
static PyObject *
acquire(ThreadLockObject *self, PyObject *args)
{
int wait = -1, acquired;
if (! PyArg_ParseTuple(args, "|i", &wait)) return NULL;
acquired=cacquire(self, wait);
if(acquired < 0) return NULL;
if (wait >= 0) return PyInt_FromLong(acquired);
Py_INCREF(Py_None);
return Py_None;
}
static int
crelease(ThreadLockObject *self)
{
#ifdef WITH_THREAD
long id = get_thread_ident();
#else
long id = 1;
#endif
if(self->count >= 0 && self->id==id)
{
/* Somebody has locked me. It is either the current thread or
another thread. */
/* So this thread has it. I can't have a race condition, because,
if another thread had the lock, then the id would not be this
one. */
self->count--;
#ifdef WITH_THREAD
if(self->count < 0) release_lock(self->lock);
#endif
}
else
{
PyErr_SetString(ErrorObject, "release unlocked lock");
return -1;
}
return 0;
}
static PyObject *
release(ThreadLockObject *self, PyObject *args)
{
if (! PyArg_ParseTuple(args, "")) return NULL;
if(crelease(self) < 0) return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
call_method(ThreadLockObject *self, PyObject *args)
{
PyObject *f, *a=0, *k=0;
UNLESS(PyArg_ParseTuple(args,"OO|O",&f, &a, &k)) return NULL;
if(cacquire(self, -1) < 0) return NULL;
f=PyEval_CallObjectWithKeywords(f,a,k);
if(crelease(self) < 0)
{
Py_XDECREF(f);
f=NULL;
}
return f;
}
static struct PyMethodDef ThreadLock_methods[] = {
{"guarded_apply", (PyCFunction)call_method, 1,
"guarded_apply(FUNCTION, ARGS[, KEYWORDS]) -- Make a guarded function call\n"
"\n"
"Acquire the lock, call the function, and then release the lock.\n"
},
{"acquire", (PyCFunction)acquire, 1,
"acquire([wait]) -- Acquire a lock, taking the thread ID into account"
},
{"release", (PyCFunction)release, 1,
"release() -- Release a lock, taking the thread ID into account"
},
{NULL, NULL} /* sentinel */
};
static void
ThreadLock_dealloc(ThreadLockObject *self)
{
#ifdef WITH_THREAD
free_lock(self->lock);
#endif
PyObject_DEL(self);
}
static PyObject *
ThreadLock_getattr(ThreadLockObject *self, PyObject *name)
{
char *cname;
if((cname=PyString_AsString(name)))
{
if(*cname=='c' && strcmp(cname,"count")==0)
return PyInt_FromLong(self->count);
if(*cname=='i' && strcmp(cname,"id")==0)
return PyInt_FromLong(self->id);
return Py_FindMethod(ThreadLock_methods, (PyObject *)self, cname);
}
PyErr_SetObject(PyExc_AttributeError, name);
return NULL;
}
static PyTypeObject ThreadLockType = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"ThreadLock", /*tp_name*/
sizeof(ThreadLockObject), /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
(destructor)ThreadLock_dealloc, /*tp_dealloc*/
(printfunc)0, /*tp_print*/
(getattrfunc)0, /*obsolete tp_getattr*/
(setattrfunc)0, /*obsolete tp_setattr*/
(cmpfunc)0, /*tp_compare*/
(reprfunc)0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
(hashfunc)0, /*tp_hash*/
(ternaryfunc)0, /*tp_call*/
(reprfunc)0, /*tp_str*/
(getattrofunc)ThreadLock_getattr, /*tp_getattro*/
0, /*tp_setattro*/
/* Space for future expansion */
0L,0L,
"Thread-based lock objects\n"
"\n"
"These lock objects may be allocated multiple times by the same\n"
"thread, but may only be allocated by one thread at a time.\n"
"This is useful for locking instances in possibly nested method calls\n"
};
static PyObject *
newThreadLockObject(ThreadLockObject *self, PyObject *args)
{
UNLESS(PyArg_ParseTuple(args,"")) return NULL;
UNLESS(self = PyObject_NEW(ThreadLockObject, &ThreadLockType)) return NULL;
self->count=-1;
#ifdef WITH_THREAD
self->lock = allocate_lock();
if (self->lock == NULL) {
PyObject_DEL(self);
self = NULL;
PyErr_SetString(ErrorObject, "can't allocate lock");
}
#endif
return (PyObject*)self;
}
static PyObject *
ident(PyObject *self, PyObject *args)
{
#ifdef WITH_THREAD
return PyInt_FromLong(get_thread_ident());
#else
return PyInt_FromLong(0);
#endif
}
static struct PyMethodDef Module_methods[] = {
{ "allocate_lock", (PyCFunction)newThreadLockObject, 1,
"allocate_lock() -- Return a new lock object"
},
{ "get_ident", (PyCFunction)ident, 1,
"get_ident() -- Get the id of the current thread"
},
{NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */
};
void
initThreadLock(void)
{
PyObject *m, *d;
m = Py_InitModule4("ThreadLock", Module_methods,
ThreadLock_module_documentation,
(PyObject*)NULL,PYTHON_API_VERSION);
d = PyModule_GetDict(m);
ThreadLockType.ob_type=&PyType_Type;
PyDict_SetItemString(d,"ThreadLockType", (PyObject*)&ThreadLockType);
ErrorObject = PyString_FromString("ThreadLock.error");
PyDict_SetItemString(d, "error", ErrorObject);
#ifdef WITH_THREAD
PyDict_SetItemString(d, "WITH_THREAD", PyInt_FromLong(1));
#else
PyDict_SetItemString(d, "WITH_THREAD", Py_None);
#endif
/* Check for errors */
if (PyErr_Occurred())
Py_FatalError("can't initialize module ThreadLock");
}
##############################################################################
#
# Copyright (c) 1996-2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
# Compatibility module
# $Id: Xaq.py,v 1.3 2002/08/14 21:24:48 mj Exp $
import Acquisition
Acquirer = Acquisition.Explicit
test_MultiMapping
1
2
3
2
[('spam', 3)]
[('eggs', 2), ('spam', 1)]
test_acquisition
A() red
A() green
A()
\ No newline at end of file
test_binding
<type 'Python Method'>
called bar() (1, 2, 3) {'name': 'spam'}
test_explicit_acquisition
A red
A green
A
\ No newline at end of file
test_method_hook
give us a hook, hook, hook...
C() () {}
give us a hook, hook, hook...
C() (1, 2, 3) {}
give us a hook, hook, hook...
C() (1, 2) {'spam': 'eggs'}
#! /usr/bin/env python
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import os
import sys
import test.regrtest
ec_tests = ["test_AqAlg", "test_MultiMapping", "test_Missing",
"test_ThreadLock", "test_acquisition", "test_add",
"test_binding", "test_explicit_acquisition",
"test_method_hook"]
ec_testdir = os.path.split(sys.argv[0])[0] or '.'
test.regrtest.STDTESTS = ec_tests
test.regrtest.NOTTESTS = []
test.regrtest.main(testdir=ec_testdir)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
__doc__='''Examples from the Acquisition Algebra Presentation
$Id: test_AqAlg.py,v 1.6 2003/05/09 21:57:10 jeremy Exp $'''
__version__='$Revision: 1.6 $'[11:-2]
import Acquisition
import sys
def uid(obj, uids={}):
uid = uids.get(id(obj))
if uid is None:
uid = uids[id(obj)] = len(uids) + 1
return uid
def pretty(self, indent=0):
context=getattr(self, 'aq_parent', None)
if context is None: return self.__name__
return "\n%s(%s \n%sof %s\n%s)" % (
' '*indent,
pretty(self.aq_self, indent+1),
' '*indent,
pretty(context, indent+1),
' '*indent,
)
class I(Acquisition.Implicit):
def __init__(self, name):
self.__name__=name
def __str__(self):
context=getattr(self, 'aq_parent', None)
if context is None: return self.__name__
return "(%s: %s of %s)" % (uid(self), self.aq_self, context)
__repr__=__str__
A=I('A')
A.B=I('B')
A.B.color='red'
A.C=I('C')
A.C.D=I('D')
def show(s, globals=globals()):
print s, '-->', eval(s, globals)
def main():
show('A')
show('Acquisition.aq_chain(A)')
show('Acquisition.aq_chain(A, 1)')
show('map(Acquisition.aq_base, Acquisition.aq_chain(A, 1))')
print
show('A.C')
show('Acquisition.aq_chain(A.C)')
show('Acquisition.aq_chain(A.C, 1)')
show('map(Acquisition.aq_base, Acquisition.aq_chain(A.C, 1))')
print
show('A.C.D')
show('Acquisition.aq_chain(A.C.D)')
show('Acquisition.aq_chain(A.C.D, 1)')
show('map(Acquisition.aq_base, Acquisition.aq_chain(A.C.D, 1))')
print
show('A.B.C')
show('Acquisition.aq_chain(A.B.C)')
show('Acquisition.aq_chain(A.B.C, 1)')
show('map(Acquisition.aq_base, Acquisition.aq_chain(A.B.C, 1))')
print
show('A.B.C.D')
show('Acquisition.aq_chain(A.B.C.D)')
show('Acquisition.aq_chain(A.B.C.D, 1)')
show('map(Acquisition.aq_base, Acquisition.aq_chain(A.B.C.D, 1))')
print
show('A.B.C.D.color')
show('Acquisition.aq_get(A.B.C.D, "color", None)')
show('Acquisition.aq_get(A.B.C.D, "color", None, 1)')
if __name__=='__main__':
main()
from Missing import Value
assert Value != 12
assert 12 != Value
assert u"abc" != Value
assert Value != u"abc"
assert 1 + Value == Value
assert Value + 1 == Value
assert Value == 1 + Value
assert Value == Value + 1
##############################################################################
#
# Copyright (c) 1996-2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
from MultiMapping import *
def sortprint(L):
L.sort()
print L
m=MultiMapping()
m.push({'spam':1, 'eggs':2})
print m['spam']
print m['eggs']
m.push({'spam':3})
print m['spam']
print m['eggs']
sortprint(m.pop().items())
sortprint(m.pop().items())
try:
print m.pop()
raise "That\'s odd", "This last pop should have failed!"
except: # I should probably raise a specific error in this case.
pass
##############################################################################
#
# Copyright (c) 1996-2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import ThreadLock, thread
from random import random
from time import sleep
from ExtensionClass import Base
with_lock=1
class P(Base):
def __oldcall_method__(self,f,a,k={}):
if with_lock:
try: lock=self.lock
except AttributeError: return apply(f,a,k)
else: return apply(f,a,k)
try:
lock.acquire()
return apply(f,a,k)
finally:
lock.release()
__call_method__=apply
def __init__(self,*args,**kw):
self.count=0
if with_lock:
self.lock=lock=ThreadLock.allocate_lock()
self.__call_method__=lock.guarded_apply
def inc(self):
c=self.count
if random() > 0.7:
sleep(1)
self.count=self.count+1
return c,self.count
def incn(self,n):
c=self.count
for i in range(n): self.inc()
return c,self.count
p=P(1,2,spam=3)
def test():
for i in range(8):
n = 3
old, new = p.incn(n)
if old + n != new:
print 'oops'
sleep(3)
thread_finished()
def thread_finished(lock=thread.allocate_lock()):
global num_threads
lock.acquire()
num_threads = num_threads - 1
lock.release()
num_threads = 8
for i in range(num_threads):
thread.start_new_thread(test, ())
while num_threads > 0:
sleep(1)
##############################################################################
#
# Copyright (c) 1996-2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
from ExtensionClass import Base
import Acquisition
class B(Base):
color='red'
class A(Acquisition.Implicit):
def hi(self):
print "%s()" % self.__class__.__name__, self.color
b=B()
b.a=A()
b.a.hi()
b.a.color='green'
b.a.hi()
try:
A().hi()
raise 'Program error', 'spam'
except AttributeError: pass
#
# New test for wrapper comparisons.
#
foo = b.a
bar = b.a
assert( foo == bar )
c = A()
b.c = c
b.c.d = c
assert( b.c.d == c )
assert( b.c.d == b.c )
assert( b.c == c )
def checkContext(self, o):
# Python equivalent to aq_inContextOf
from Acquisition import aq_base, aq_parent, aq_inner
subob = self
o = aq_base(o)
while 1:
if aq_base(subob) is o: return 1
self = aq_inner(subob)
if self is None: break
subob = aq_parent(self)
if subob is None: break
assert checkContext(b.c, b)
assert not checkContext(b.c, b.a)
assert b.a.aq_inContextOf(b)
assert b.c.aq_inContextOf(b)
assert b.c.d.aq_inContextOf(b)
assert b.c.d.aq_inContextOf(c)
assert b.c.d.aq_inContextOf(b.c)
assert not b.c.aq_inContextOf(foo)
assert not b.c.aq_inContextOf(b.a)
assert not b.a.aq_inContextOf('somestring')
##############################################################################
#
# Copyright (c) 1996-2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
from ExtensionClass import *
class foo(Base):
def __add__(self,other): print 'add called'
foo()+foo()
##############################################################################
#
# Copyright (c) 1996-2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
from ExtensionClass import Base
from MethodObject import Method
class foo(Method):
def __call__(self, ob, *args, **kw):
print 'called', ob, args, kw
class bar(Base):
def __repr__(self):
return "bar()"
hi = foo()
x=bar()
hi=x.hi
print type(hi)
hi(1,2,3,name='spam')
##############################################################################
#
# Copyright (c) 1996-2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
from ExtensionClass import Base
import Acquisition
class B(Base):
color='red'
class A(Acquisition.Explicit):
def hi(self):
print self.__class__.__name__, self.acquire('color')
b=B()
b.a=A()
b.a.hi()
b.a.color='green'
b.a.hi()
try:
A().hi()
raise 'Program error', 'spam'
except AttributeError: pass
##############################################################################
#
# Copyright (c) 1996-2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import ExtensionClass
class C(ExtensionClass.Base):
def __call_method__(self, meth, args, kw={}):
print 'give us a hook, hook, hook...'
return apply(meth, args, kw)
def hi(self, *args, **kw):
print "%s()" % self.__class__.__name__, args, kw
c=C()
c.hi()
c.hi(1,2,3)
c.hi(1,2,spam='eggs')
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment