text:
PEP: 468 Title: Preserving the order of **kwargs in a function. Version:
$Revision$ Last-Modified: $Date$ Author: Eric Snow
<EMAIL_ADDRESS>Discussions-To<EMAIL_ADDRESS>Status: Final Type: Standards Track Content-Type: text/x-rst Created:
05-Apr-2014 Python-Version: 3.6 Post-History: 05-Apr-2014, 08-Sep-2016
Resolution:
https://mail.python.org/pipermail/python-dev/2016-September/146329.html

Abstract

The **kwargs syntax in a function definition indicates that the
interpreter should collect all keyword arguments that do not correspond
to other named parameters. However, Python does not preserved the order
in which those collected keyword arguments were passed to the function.
In some contexts the order matters. This PEP dictates that the collected
keyword arguments be exposed in the function body as an ordered mapping.

Motivation

Python's **kwargs syntax in function definitions provides a powerful
means of dynamically handling keyword arguments. In some applications of
the syntax (see _`Use Cases`), the semantics applied to the collected
keyword arguments requires that order be preserved. Unsurprisingly, this
is similar to how OrderedDict is related to dict.

Currently to preserved the order you have to do so manually and
separately from the actual function call. This involves building an
ordered mapping, whether an OrderedDict or an iterable of 2-tuples,
which is then passed as a single argument to the function. [1]

With the capability described in this PEP, that boilerplate would no
longer be required.

For comparison, currently:

    kwargs = OrderedDict()
    kwargs['eggs'] = ...
    ...
    def spam(a, kwargs):
        ...

and with this proposal:

    def spam(a, **kwargs):
        ...

Alyssa (Nick) Coghlan, speaking of some of the uses cases, summed it up
well [2]:

    These *can* all be done today, but *not* by using keyword arguments.
    In my view, the problem to be addressed is that keyword arguments
    *look* like they should work for these cases, because they have a
    definite order in the source code. The only reason they don't work
    is because the interpreter throws that ordering information away.

    It's a textbook case of a language feature becoming an attractive
    nuisance in some circumstances: the simple and obvious solution for
    the above use cases *doesn't actually work* for reasons that aren't
    obviously clear if you don't have a firm grasp of Python's admittedly
    complicated argument handling.

This observation is supported by the appearance of this proposal over
the years and the numerous times that people have been confused by the
constructor for OrderedDict.[3][4] [5]

Use Cases

As Alyssa noted, the current behavior of **kwargs is unintuitive in
cases where one would expect order to matter. Aside from more specific
cases outlined below, in general "anything else where you want to
control the iteration order and set field names and values in a single
call will potentially benefit."[6] That matters in the case of factories
(e.g. __init__()) for ordered types.

Serialization

Obviously OrderedDict would benefit (both __init__() and update()) from
ordered kwargs. However, the benefit also extends to serialization
APIs[7]:

    In the context of serialisation, one key lesson we have learned is
    that arbitrary ordering is a problem when you want to minimise
    spurious diffs, and sorting isn't a simple solution.

    Tools like doctest don't tolerate spurious diffs at all, but are
    often amenable to a sorting based answer.

    The cases where it would be highly desirable to be able use keyword
    arguments to control the order of display of a collection of key
    value pairs are ones like:

    * printing out key:value pairs in CLI output
    * mapping semantic names to column order in a CSV
    * serialising attributes and elements in particular orders in XML
    * serialising map keys in particular orders in human readable formats
      like JSON and YAML (particularly when they're going to be placed
      under source control)

Debugging

In the words of Raymond Hettinger[8]:

    It makes it easier to debug if the arguments show-up in the order
    they were created.  AFAICT, no purpose is served by scrambling them.

Other Use Cases

-   Mock objects.[9]
-   Controlling object presentation.
-   Alternate namedtuple() where defaults can be specified.
-   Specifying argument priority by order.

Concerns

Performance

As already noted, the idea of ordered keyword arguments has come up on a
number of occasions. Each time it has been met with the same response,
namely that preserving keyword arg order would have a sufficiently
adverse effect on function call performance that it's not worth doing.
However, Guido noted the following[10]:

    Making **kwds ordered is still open, but requires careful design and
    implementation to avoid slowing down function calls that don't benefit.

As will be noted below, there are ways to work around this at the
expense of increased complication. Ultimately the simplest approach is
the one that makes the most sense: pack collected key word arguments
into an OrderedDict. However, without a C implementation of OrderedDict
there isn't much to discuss. That changed in Python 3.5. [11]

Note: in Python 3.6 dict is order-preserving. This virtually eliminates
performance concerns.

Other Python Implementations

Another important issue to consider is that new features must be
cognizant of the multiple Python implementations. At some point each of
them would be expected to have implemented ordered kwargs. In this
regard there doesn't seem to be an issue with the idea.[12] An informal
survey of the major Python implementations has indicated that this
feature will not be a significant burden.

Specification

Starting in version 3.6 Python will preserve the order of keyword
arguments as passed to a function. To accomplish this the collected
kwargs will now be an ordered mapping. Note that this does not
necessarily mean OrderedDict. dict in CPython 3.6 is now ordered,
similar to PyPy.

This will apply only to functions for which the definition uses the
**kwargs syntax for collecting otherwise unspecified keyword arguments.
Only the order of those keyword arguments will be preserved.

Relationship to **-unpacking syntax

The ** unpacking syntax in function calls has no special connection with
this proposal. Keyword arguments provided by unpacking will be treated
in exactly the same way as they are now: ones that match defined
parameters are gather there and the remainder will be collected into the
ordered kwargs (just like any other unmatched keyword argument).

Note that unpacking a mapping with undefined order, such as dict, will
preserve its iteration order like normal. It's just that the order will
remain undefined. The ordered mapping into which the unpacked key-value
pairs will then be packed will not be able to provide any alternate
ordering. This should not be surprising.

There have been brief discussions of simply passing these mappings
through to the functions kwargs without unpacking and repacking them,
but that is both outside the scope of this proposal and probably a bad
idea regardless. (There is a reason those discussions were brief.)

Relationship to inspect.Signature

Signature objects should need no changes. The kwargs parameter of
inspect.BoundArguments (returned by Signature.bind() and
Signature.bind_partial()) will change from a dict to an OrderedDict.

C-API

No changes.

Syntax

No syntax is added or changed by this proposal.

Backward-Compatibility

The following will change:

-   iteration order of kwargs will now be consistent (except of course
    in the case described above)

Reference Implementation

For CPython there's nothing to do.

Alternate Approaches

Opt-out Decorator

This is identical to the current proposal with the exception that Python
would also provide a decorator in functools that would cause collected
keyword arguments to be packed into a normal dict instead of an
OrderedDict.

Prognosis:

This would only be necessary if performance is determined to be
significantly different in some uncommon cases or that there are other
backward-compatibility concerns that cannot be resolved otherwise.

Opt-in Decorator

The status quo would be unchanged. Instead Python would provide a
decorator in functools that would register or mark the decorated
function as one that should get ordered keyword arguments. The
performance overhead to check the function at call time would be
marginal.

Prognosis:

The only real down-side is in the case of function wrappers factories
(e.g. functools.partial and many decorators) that aim to perfectly
preserve keyword arguments by using kwargs in the wrapper definition and
kwargs unpacking in the call to the wrapped function. Each wrapper would
have to be updated separately, though having functools.wraps() do this
automaticallywould help.

------------------------------------------------------------------------

The order of keyword arguments would be stored separately in a list at
call time. The list would be bound to __kworder__ in the function
locals.

Prognosis:

This likewise complicates the wrapper case.

Compact dict with faster iteration

Raymond Hettinger has introduced the idea of a dict implementation that
would result in preserving insertion order on dicts (until the first
deletion). This would be a perfect fit for kwargs.[13]

Prognosis:

The idea is still uncertain in both viability and timeframe.

Note that Python 3.6 now has this dict implementation.

***kwargs

This would add a new form to a function's signature as a mutually
exclusive parallel to **kwargs. The new syntax, ***kwargs (note that
there are three asterisks), would indicate that kwargs should preserve
the order of keyword arguments.

Prognosis:

New syntax is only added to Python under the most dire circumstances.
With other available solutions, new syntax is not justifiable.
Furthermore, like all opt-in solutions, the new syntax would complicate
the pass-through case.

annotations

This is a variation on the decorator approach. Instead of using a
decorator to mark the function, you would use a function annotation on
**kwargs.

Prognosis:

In addition to the pass-through complication, annotations have been
actively discouraged in Python core development. Use of annotations to
opt-in to order preservation runs the risk of interfering with other
application-level use of annotations.

dict.__order__

dict objects would have a new attribute, __order__ that would default to
None and that in the kwargs case the interpreter would use in the same
way as described above for __kworder__.

Prognosis:

It would mean zero impact on kwargs performance but the change would be
pretty intrusive (Python uses dict a lot). Also, for the wrapper case
the interpreter would have to be careful to preserve __order__.

KWArgsDict.__order__

This is the same as the dict.__order__ idea, but kwargs would be an
instance of a new minimal dict subclass that provides the __order__
attribute. dict would instead be unchanged.

Prognosis:

Simply switching to OrderedDict is a less complicated and more intuitive
change.

Acknowledgements

Thanks to Andrew Barnert for helpful feedback and to the participants of
all the past email threads.

Footnotes

References

Copyright

This document has been placed in the public domain.

  Local Variables: mode: indented-text indent-tabs-mode: nil
  sentence-end-double-space: t fill-column: 70 coding: utf-8 End:

[1] Alternately, you could also replace ** in your function definition
with * and then pass in key/value 2-tuples. This has the advantage of
not requiring the keys to be valid identifier strings. See
https://mail.python.org/pipermail/python-ideas/2014-April/027491.html.

[2] https://mail.python.org/pipermail/python-ideas/2014-April/027512.html

[3] https://mail.python.org/pipermail/python-ideas/2009-April/004163.html

https://mail.python.org/pipermail/python-ideas/2010-October/008445.html

https://mail.python.org/pipermail/python-ideas/2011-January/009037.html

https://mail.python.org/pipermail/python-ideas/2013-February/019690.html

https://mail.python.org/pipermail/python-ideas/2013-May/020727.html

https://mail.python.org/pipermail/python-ideas/2014-March/027225.html

http://bugs.python.org/issue16276

http://bugs.python.org/issue16553

http://bugs.python.org/issue19026

http://bugs.python.org/issue5397#msg82972

[4] https://mail.python.org/pipermail/python-dev/2007-February/071310.html

[5] https://mail.python.org/pipermail/python-dev/2012-December/123028.html

  https://mail.python.org/pipermail/python-dev/2012-December/123105.html

https://mail.python.org/pipermail/python-dev/2013-May/126327.html

  https://mail.python.org/pipermail/python-dev/2013-May/126328.html

[6] https://mail.python.org/pipermail/python-dev/2012-December/123105.html

[7] https://mail.python.org/pipermail/python-ideas/2014-April/027512.html

[8] https://mail.python.org/pipermail/python-dev/2013-May/126327.html

[9] https://mail.python.org/pipermail/python-ideas/2009-April/004163.html

  https://mail.python.org/pipermail/python-ideas/2009-April/004165.html

  https://mail.python.org/pipermail/python-ideas/2009-April/004175.html

[10] https://mail.python.org/pipermail/python-dev/2013-May/126404.html

[11] http://bugs.python.org/issue16991

[12] https://mail.python.org/pipermail/python-dev/2012-December/123100.html

[13] https://mail.python.org/pipermail/python-dev/2012-December/123028.html

  https://mail.python.org/pipermail/python-dev/2012-December/123105.html

https://mail.python.org/pipermail/python-dev/2013-May/126327.html

  https://mail.python.org/pipermail/python-dev/2013-May/126328.html


text:
PEP: 430 Title: Migrating to Python 3 as the default online
documentation Version: $Revision$ Last-Modified: $Date$ Author: Alyssa
Coghlan<EMAIL_ADDRESS>BDFL-Delegate: Georg Brandl Status: Final
Type: Informational Content-Type: text/x-rst Created: 27-Oct-2012

Abstract

This PEP proposes a strategy for migrating the default version of the
Python documentation presented to users of Python when accessing
docs.python.org from 2.7 to Python 3.3.

It proposes a backwards compatible scheme that preserves the meaning of
existing deep links in to the Python 2 documentation, while still
presenting the Python 3 documentation by default, and presenting the
Python 2 and 3 documentation in a way that avoids making the Python 3
documentation look like a second-class citizen.

Background

With the transition of the overall Python ecosystem from Python 2 to
Python 3 still in progress, one question which arises
periodically[1],[2] is when and how to handle the change from providing
the Python 2 documentation as the default version displayed at the
docs.python.org root URL to providing the Python 3 documentation.

Key Concerns

There are a couple of key concerns that any migration proposal needs to
address.

Don't Confuse Beginners

Many beginners learn Python through third party resources. These
resources, not all of which are online resources, may reference in to
the python.org online documentation for additional background and
details.

Importantly, even when the online documentation is updated, the "version
added" and "version changed" tags usually provide enough information for
users to adjust appropriately for the specific version they are using.

While deep links in to the python.org documentation may occasionally
break within the Python 2 series, this is very rare.

Migrating to Python 3 is a very different matter. Many links would break
due to renames and removals, and the "version added" and "version
changed" information for the Python 2 series is completely absent.

Don't Break Useful Resources

There are many useful Python resources out there, such as the mailing
list archives on python.org and question-and-answer sites like Stack
Overflow, where links are highly unlikely to be updated, no matter how
much notice is provided.

Old posts and answers to questions all currently link to docs.python.org
expecting to get the Python 2 documentation at unqualified URLs. Links
from answers that relate to Python 3 are explicitly qualified with
/py3k/ in the path component.

Proposal

This PEP (based on an idea originally put forward back in May[3]) is to
not migrate the Python 2 specific deep links at all, and instead adopt a
scheme where all URLs presented to users on docs.python.org are
qualified appropriately with the relevant release series.

Visitors to the root URL at http://docs.python.org will be automatically
redirected to http://docs.python.org/3/, but links deeper in the
version-specific hierarchy, such as to
http://docs.python.org/library/os, will instead be redirected to a
Python 2 specific link such as http://docs.python.org/2/library/os.

The specific subpaths which will be redirected to explicitly qualified
paths for the Python 2 docs are:

-   /c-api/
-   /distutils/
-   /extending/
-   /faq/
-   /howto/
-   /library/
-   /reference/
-   /tutorial/
-   /using/
-   /whatsnew/
-   /about.html
-   /bugs.html
-   /contents.html
-   /copyright.html
-   /license.html
-   /genindex.html
-   /glossary.html
-   /py-modindex.html
-   /search.html

The existing /py3k/ subpath will be redirected to the new /3/ subpath.

Presented URLs

With this scheme, the following URLs would be presented to users after
resolution of any aliasing and rewriting rules:

-   http://docs.python.org/x/*
-   http://docs.python.org/x.y/*
-   http://docs.python.org/dev/*
-   http://docs.python.org/release/x.y.z/*
-   http://docs.python.org/devguide

The /x/ URLs mean "give me the latest documentation for a released
version in this release series". It will draw the documentation from the
relevant maintenance branch in source control (this will always be the
2.7 branch for Python 2 and is currently 3.3 for Python 3). Differences
relative to previous versions in the release series will be available
through "version added" and "version changed" markers.

The /x.y/ URLs mean "give me the latest documentation for this release".
It will draw the documentation from the relevant maintenance branch in
source control (or the default branch for the currently in development
version). It differs from the status quo in that the URLs will actually
remain available in the user's browser for easy copy and pasting.
(Currently, references to specific versions that are not the latest in
their release series will resolve to a stable URL for a specific
maintenance version in the "release" hierarchy, while the current latest
version in the release series resolves to the release series URL. This
makes it hard to get a "latest version specific URL", since it is always
necessary to construct them manually).

The /dev/ URL means the documentation for the default branch in source
control.

The /release/x.y.x/ URLs will refer to the documentation of those
releases, exactly as it was at the time of the release.

The developer's guide is not version specific, and thus retains its own
stable /devguide/ URL.

Rationale

There is some desire to switch the unqualified references to mean Python
3 as a sign of confidence in Python 3. Such a move would either break a
lot of things, or else involve an awful lot of work to avoid breaking
things.

I believe we can get much the same effect without breaking the world by:

1.  Deprecating the use of unqualified references to the online
    documentation (while promising to preserve the meaning of such
    references indefinitely)
2.  Updating all python.org and python-dev controlled links to use
    qualified references (excluding archived email)
3.  Redirecting visitors to the root of http://docs.python.org to
    http://docs.python.org/3.x

Most importantly, because this scheme doesn't alter the behaviour of any
existing deep links, it could be implemented with a significantly
shorter warning period than would be required for a scheme that risked
breaking deep links, or started to redirect unqualified links to Python
3. The only part of the scheme which would require any warning at all is
the step of redirecting the "http://docs.python.org/" landing page to
the Python 3.3 documentation.

Namespaces are one honking great idea - let's do more of those.

Note that the approach described in this PEP gives two ways to access
the content of the default branch: as /dev/ or using the appropriate
/x.y/ reference. This is deliberate, as the default branch is referenced
for two different purposes:

-   to provide additional information when discussing an upcoming
    feature of the next release (a /x.y/ URL is appropriate)
-   to provide a stable destination for developers to access the
    documentation of the next feature release, regardless of the version
    (a /dev/ URL is appropriate)

Implementation

The URLs on docs.python.org are controlled by the python.org
infrastructure team rather than through the CPython source repo, so
acceptance and implementation of the ideas in this PEP will be up to the
team.

References

Copyright

This document has been placed in the public domain.

[1] May 2012 discussion
(https://mail.python.org/pipermail/python-dev/2012-May/119524.html)

[2] October 2012 discussion
(https://mail.python.org/pipermail/python-ideas/2012-October/017406.html)

[3] Using a "/latest/" path prefix
(https://mail.python.org/pipermail/python-dev/2012-May/119567.html)

license: Public Domain


text:
PEP: 629 Title: Versioning PyPI's Simple API Author: Donald Stufft
<EMAIL_ADDRESS>BDFL-Delegate: Brett Cannon<EMAIL_ADDRESS>Discussions-To:
https://discuss.python.org/t/pep-629-versioning-pypis-simple-api/4720
Status: Final Type: Standards Track Topic: Packaging Content-Type:
text/x-rst Created: 16-Jul-2020 Post-History: 16-Jul-2020

Note

This PEP was accepted on 2020-08-20. PyPI merged an implementation on
2020-01-28, marking this PEP as "Final".

Abstract

This PEP proposes adding a method for versioning the simple API so that
clients can determine which features of the simple API that a specific
repository supports.

Rationale

When evolving the simple API, clients wish to be able to determine which
features the repository supports. Currently there is no mechanism to do
this, except by attempting to detect new features by looking at the data
in the responses and see if it appears like a particular feature is in
use.

This works reasonably well for a modern version of a client to determine
if the repository supports all of the features it wants to implement,
however it does not do anything to tell an older version the client that
the repository supports features that it might not understand and to
allow messaging to indicate that it might not be correctly understanding
the output of the repository.

An example of a scenario where this happened was the phasing in of
python-requires metadata, while existing clients could still
successfully use the repository, they were lacking the ability to
understand this new piece of data which would have informed their
behavior to select a better file for end users.

Overview

This PEP proposes the inclusion of a meta tag on the responses of every
successful request to a simple API page, which contains a name attribute
of "pypi:repository-version", and a content that is a PEP 440 compatible
version number, which is further constrained to ONLY be Major.Minor, and
none of the additional features supported by PEP 440.

This would end up looking like:

    <meta name="pypi:repository-version" content="1.0">

When interpreting the repository version:

-   Incrementing the major version is used to signal a backwards
    incompatible change such that existing clients would no longer be
    expected to be able to meaningfully use the API.
-   Incrementing the minor version is used to signal a backwards
    compatible change such that existing clients would still be expected
    to be able to meaningfully use the API.

It is left up to the discretion of any future PEPs as to what
specifically constitutes a backwards incompatible vs compatible change
beyond the broad suggestion that existing clients will be able to
"meaningfully" continue to use the API, and can include adding,
modifying, or removing existing features.

It is expectation of this PEP that the major version will never be
incremented, and any future major API evolutions would utilize a
different mechanism for API evolution. However the major version is
included to disambiguate with future versions (e.g. a hypothetical
simple api v2 that lived at /v2/, but which would be confusing if the
repository-version was set to a version >= 2).

This PEP sets the current API version to "1.0", and expects that future
PEPs that further evolve the simple API will increment the minor version
number.

Clients

Clients interacting with the simple API SHOULD introspect each response
for the repository version, and if that data does not exist MUST assume
that it is version 1.0.

When encountering a major version greater than expected, clients MUST
hard fail with an appropriate error message for the user.

When encountering a minor version greater than expected, clients SHOULD
warn users with an appropriate message.

Clients MAY still continue to use feature detection in order to
determine what features a repository uses.

Rejected Ideas

Using a Header

Instead of baking this information into the actual HTML, an alternative
would be to use a HTTP header. This idea was considered and ultimately
was rejected because it would make mirrors have to start modifying
headers instead of being able to operate as a "dumb" HTTP server of
files.

Using an URL

Another traditional mechanism for versioning APIs is to bake it into the
URL, something like /1.0/simple/ or so. This works well for major
version changes where olders clients are not expected to be capable of
continuing to use it, but it is not well suited to minor version bumps,
particularly when the version numbers can be viewed as largely advisory
for end users.

Copyright

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.

  Local Variables: mode: indented-text indent-tabs-mode: nil
  sentence-end-double-space: t fill-column: 70 coding: utf-8 End:


text:
PEP: 3124 Title: Overloading, Generic Functions, Interfaces, and
Adaptation Version: $Revision$ Last-Modified: $Date$ Author: Phillip J.
Eby<EMAIL_ADDRESS>Discussions-To<EMAIL_ADDRESS>Status: Deferred Type: Standards Track Content-Type: text/x-rst
Requires: 3107, 3115, 3119 Created: 28-Apr-2007 Post-History:
30-Apr-2007 Replaces: 245, 246

Deferred

See https://mail.python.org/pipermail/python-3000/2007-July/008784.html.

Abstract

This PEP proposes a new standard library module, overloading, to provide
generic programming features including dynamic overloading (aka generic
functions), interfaces, adaptation, method combining (ala CLOS and
AspectJ), and simple forms of aspect-oriented programming (AOP).

The proposed API is also open to extension; that is, it will be possible
for library developers to implement their own specialized interface
types, generic function dispatchers, method combination algorithms,
etc., and those extensions will be treated as first-class citizens by
the proposed API.

The API will be implemented in pure Python with no C, but may have some
dependency on CPython-specific features such as sys._getframe and the
func_code attribute of functions. It is expected that e.g. Jython and
IronPython will have other ways of implementing similar functionality
(perhaps using Java or C#).

Rationale and Goals

Python has always provided a variety of built-in and standard-library
generic functions, such as len(), iter(), pprint.pprint(), and most of
the functions in the operator module. However, it currently:

1.  does not have a simple or straightforward way for developers to
    create new generic functions,
2.  does not have a standard way for methods to be added to existing
    generic functions (i.e., some are added using registration
    functions, others require defining __special__ methods, possibly by
    monkeypatching), and
3.  does not allow dispatching on multiple argument types (except in a
    limited form for arithmetic operators, where "right-hand" (__r*__)
    methods can be used to do two-argument dispatch.

In addition, it is currently a common anti-pattern for Python code to
inspect the types of received arguments, in order to decide what to do
with the objects. For example, code may wish to accept either an object
of some type, or a sequence of objects of that type.

Currently, the "obvious way" to do this is by type inspection, but this
is brittle and closed to extension. A developer using an already-written
library may be unable to change how their objects are treated by such
code, especially if the objects they are using were created by a third
party.

Therefore, this PEP proposes a standard library module to address these,
and related issues, using decorators and argument annotations (PEP
3107). The primary features to be provided are:

-   a dynamic overloading facility, similar to the static overloading
    found in languages such as Java and C++, but including optional
    method combination features as found in CLOS and AspectJ.
-   a simple "interfaces and adaptation" library inspired by Haskell's
    typeclasses (but more dynamic, and without any static
    type-checking), with an extension API to allow registering
    user-defined interface types such as those found in PyProtocols and
    Zope.
-   a simple "aspect" implementation to make it easy to create stateful
    adapters and to do other stateful AOP.

These features are to be provided in such a way that extended
implementations can be created and used. For example, it should be
possible for libraries to define new dispatching criteria for generic
functions, and new kinds of interfaces, and use them in place of the
predefined features. For example, it should be possible to use a
zope.interface interface object to specify the desired type of a
function argument, as long as the zope.interface package registered
itself correctly (or a third party did the registration).

In this way, the proposed API simply offers a uniform way of accessing
the functionality within its scope, rather than prescribing a single
implementation to be used for all libraries, frameworks, and
applications.

User API

The overloading API will be implemented as a single module, named
overloading, providing the following features:

Overloading/Generic Functions

The @overload decorator allows you to define alternate implementations
of a function, specialized by argument type(s). A function with the same
name must already exist in the local namespace. The existing function is
modified in-place by the decorator to add the new implementation, and
the modified function is returned by the decorator. Thus, the following
code:

    from overloading import overload
    from collections import Iterable

    def flatten(ob):
        """Flatten an object to its component iterables"""
        yield ob

    @overload
    def flatten(ob: Iterable):
        for o in ob:
            for ob in flatten(o):
                yield ob

    @overload
    def flatten(ob: basestring):
        yield ob

creates a single flatten() function whose implementation roughly equates
to:

    def flatten(ob):
        if isinstance(ob, basestring) or not isinstance(ob, Iterable):
            yield ob
        else:
            for o in ob:
                for ob in flatten(o):
                    yield ob

except that the flatten() function defined by overloading remains open
to extension by adding more overloads, while the hardcoded version
cannot be extended.

For example, if someone wants to use flatten() with a string-like type
that doesn't subclass basestring, they would be out of luck with the
second implementation. With the overloaded implementation, however, they
can either write this:

    @overload
    def flatten(ob: MyString):
        yield ob

or this (to avoid copying the implementation):

    from overloading import RuleSet
    RuleSet(flatten).copy_rules((basestring,), (MyString,))

(Note also that, although PEP 3119 proposes that it should be possible
for abstract base classes like Iterable to allow classes like MyString
to claim subclass-hood, such a claim is global, throughout the
application. In contrast, adding a specific overload or copying a rule
is specific to an individual function, and therefore less likely to have
undesired side effects.)

@overload vs. @when

The @overload decorator is a common-case shorthand for the more general
@when decorator. It allows you to leave out the name of the function you
are overloading, at the expense of requiring the target function to be
in the local namespace. It also doesn't support adding additional
criteria besides the ones specified via argument annotations. The
following function definitions have identical effects, except for name
binding side-effects (which will be described below):

    from overloading import when

    @overload
    def flatten(ob: basestring):
        yield ob

    @when(flatten)
    def flatten(ob: basestring):
        yield ob

    @when(flatten)
    def flatten_basestring(ob: basestring):
        yield ob

    @when(flatten, (basestring,))
    def flatten_basestring(ob):
        yield ob

The first definition above will bind flatten to whatever it was
previously bound to. The second will do the same, if it was already
bound to the when decorator's first argument. If flatten is unbound or
bound to something else, it will be rebound to the function definition
as given. The last two definitions above will always bind
flatten_basestring to the function definition as given.

Using this approach allows you to both give a method a descriptive name
(often useful in tracebacks!) and to reuse the method later.

Except as otherwise specified, all overloading decorators have the same
signature and binding rules as @when. They accept a function and an
optional "predicate" object.

The default predicate implementation is a tuple of types with positional
matching to the overloaded function's arguments. However, an arbitrary
number of other kinds of predicates can be created and registered using
the Extension API, and will then be usable with @when and other
decorators created by this module (like @before, @after, and @around).

Method Combination and Overriding

When an overloaded function is invoked, the implementation with the
signature that most specifically matches the calling arguments is the
one used. If no implementation matches, a NoApplicableMethods error is
raised. If more than one implementation matches, but none of the
signatures are more specific than the others, an AmbiguousMethods error
is raised.

For example, the following pair of implementations are ambiguous, if the
foo() function is ever called with two integer arguments, because both
signatures would apply, but neither signature is more specific than the
other (i.e., neither implies the other):

    def foo(bar:int, baz:object):
        pass

    @overload
    def foo(bar:object, baz:int):
        pass

In contrast, the following pair of implementations can never be
ambiguous, because one signature always implies the other; the int/int
signature is more specific than the object/object signature:

    def foo(bar:object, baz:object):
        pass

    @overload
    def foo(bar:int, baz:int):
        pass

A signature S1 implies another signature S2, if whenever S1 would apply,
S2 would also. A signature S1 is "more specific" than another signature
S2, if S1 implies S2, but S2 does not imply S1.

Although the examples above have all used concrete or abstract types as
argument annotations, there is no requirement that the annotations be
such. They can also be "interface" objects (discussed in the Interfaces
and Adaptation section), including user-defined interface types. (They
can also be other objects whose types are appropriately registered via
the Extension API.)

Proceeding to the "Next" Method

If the first parameter of an overloaded function is named __proceed__,
it will be passed a callable representing the next most-specific method.
For example, this code:

    def foo(bar:object, baz:object):
        print "got objects!"

    @overload
    def foo(__proceed__, bar:int, baz:int):
        print "got integers!"
        return __proceed__(bar, baz)

Will print "got integers!" followed by "got objects!".

If there is no next most-specific method, __proceed__ will be bound to a
NoApplicableMethods instance. When called, a new NoApplicableMethods
instance will be raised, with the arguments passed to the first
instance.

Similarly, if the next most-specific methods have ambiguous precedence
with respect to each other, __proceed__ will be bound to an
AmbiguousMethods instance, and if called, it will raise a new instance.

Thus, a method can either check if __proceed__ is an error instance, or
simply invoke it. The NoApplicableMethods and AmbiguousMethods error
classes have a common DispatchError base class, so
isinstance(__proceed__, overloading.DispatchError) is sufficient to
identify whether __proceed__ can be safely called.

(Implementation note: using a magic argument name like __proceed__ could
potentially be replaced by a magic function that would be called to
obtain the next method. A magic function, however, would degrade
performance and might be more difficult to implement on non-CPython
platforms. Method chaining via magic argument names, however, can be
efficiently implemented on any Python platform that supports creating
bound methods from functions -- one simply recursively binds each
function to be chained, using the following function or error as the
im_self of the bound method.)

"Before" and "After" Methods

In addition to the simple next-method chaining shown above, it is
sometimes useful to have other ways of combining methods. For example,
the "observer pattern" can sometimes be implemented by adding extra
methods to a function, that execute before or after the normal
implementation.

To support these use cases, the overloading module will supply @before,
@after, and @around decorators, that roughly correspond to the same
types of methods in the Common Lisp Object System (CLOS), or the
corresponding "advice" types in AspectJ.

Like @when, all of these decorators must be passed the function to be
overloaded, and can optionally accept a predicate as well:

    from overloading import before, after

    def begin_transaction(db):
        print "Beginning the actual transaction"

    @before(begin_transaction)
    def check_single_access(db: SingletonDB):
        if db.inuse:
            raise TransactionError("Database already in use")

    @after(begin_transaction)
    def start_logging(db: LoggableDB):
        db.set_log_level(VERBOSE)

@before and @after methods are invoked either before or after the main
function body, and are never considered ambiguous. That is, it will not
cause any errors to have multiple "before" or "after" methods with
identical or overlapping signatures. Ambiguities are resolved using the
order in which the methods were added to the target function.

"Before" methods are invoked most-specific method first, with ambiguous
methods being executed in the order they were added. All "before"
methods are called before any of the function's "primary" methods (i.e.
normal @overload methods) are executed.

"After" methods are invoked in the reverse order, after all of the
function's "primary" methods are executed. That is, they are executed
least-specific methods first, with ambiguous methods being executed in
the reverse of the order in which they were added.

The return values of both "before" and "after" methods are ignored, and
any uncaught exceptions raised by any methods (primary or other)
immediately end the dispatching process. "Before" and "after" methods
cannot have __proceed__ arguments, as they are not responsible for
calling any other methods. They are simply called as a notification
before or after the primary methods.

Thus, "before" and "after" methods can be used to check or establish
preconditions (e.g. by raising an error if the conditions aren't met) or
to ensure postconditions, without needing to duplicate any existing
functionality.

"Around" Methods

The @around decorator declares a method as an "around" method. "Around"
methods are much like primary methods, except that the least-specific
"around" method has higher precedence than the most-specific "before"
method.

Unlike "before" and "after" methods, however, "Around" methods are
responsible for calling their __proceed__ argument, in order to continue
the invocation process. "Around" methods are usually used to transform
input arguments or return values, or to wrap specific cases with special
error handling or try/finally conditions, e.g.:

    from overloading import around

    @around(commit_transaction)
    def lock_while_committing(__proceed__, db: SingletonDB):
        with db.global_lock:
            return __proceed__(db)

They can also be used to replace the normal handling for a specific
case, by not invoking the __proceed__ function.

The __proceed__ given to an "around" method will either be the next
applicable "around" method, a DispatchError instance, or a synthetic
method object that will call all the "before" methods, followed by the
primary method chain, followed by all the "after" methods, and return
the result from the primary method chain.

Thus, just as with normal methods, __proceed__ can be checked for
DispatchError-ness, or simply invoked. The "around" method should return
the value returned by __proceed__, unless of course it wishes to modify
or replace it with a different return value for the function as a whole.

Custom Combinations

The decorators described above (@overload, @when, @before, @after, and
@around) collectively implement what in CLOS is called the "standard
method combination" -- the most common patterns used in combining
methods.

Sometimes, however, an application or library may have use for a more
sophisticated type of method combination. For example, if you would like
to have "discount" methods that return a percentage off, to be
subtracted from the value returned by the primary method(s), you might
write something like this:

    from overloading import always_overrides, merge_by_default
    from overloading import Around, Before, After, Method, MethodList

    class Discount(MethodList):
        """Apply return values as discounts"""

        def __call__(self, *args, **kw):
            retval = self.tail(*args, **kw)
            for sig, body in self.sorted():
                retval -= retval * body(*args, **kw)
            return retval

    # merge discounts by priority
    merge_by_default(Discount)

    # discounts have precedence over before/after/primary methods
    always_overrides(Discount, Before)
    always_overrides(Discount, After)
    always_overrides(Discount, Method)

    # but not over "around" methods
    always_overrides(Around, Discount)

    # Make a decorator called "discount" that works just like the
    # standard decorators...
    discount = Discount.make_decorator('discount')

    # and now let's use it...
    def price(product):
        return product.list_price

    @discount(price)
    def ten_percent_off_shoes(product: Shoe)
        return Decimal('0.1')

Similar techniques can be used to implement a wide variety of CLOS-style
method qualifiers and combination rules. The process of creating custom
method combination objects and their corresponding decorators is
described in more detail under the Extension API section.

Note, by the way, that the @discount decorator shown will work correctly
with any new predicates defined by other code. For example, if
zope.interface were to register its interface types to work correctly as
argument annotations, you would be able to specify discounts on the
basis of its interface types, not just classes or overloading-defined
interface types.

Similarly, if a library like RuleDispatch or PEAK-Rules were to register
an appropriate predicate implementation and dispatch engine, one would
then be able to use those predicates for discounts as well, e.g.:

    from somewhere import Pred  # some predicate implementation

    @discount(
        price,
        Pred("isinstance(product,Shoe) and"
             " product.material.name=='Blue Suede'")
    )
    def forty_off_blue_suede_shoes(product):
        return Decimal('0.4')

The process of defining custom predicate types and dispatching engines
is also described in more detail under the Extension API section.

Overloading Inside Classes

All of the decorators above have a special additional behavior when they
are directly invoked within a class body: the first parameter (other
than __proceed__, if present) of the decorated function will be treated
as though it had an annotation equal to the class in which it was
defined.

That is, this code:

    class And(object):
        # ...
        @when(get_conjuncts)
        def __conjuncts(self):
            return self.conjuncts

produces the same effect as this (apart from the existence of a private
method):

    class And(object):
        # ...

    @when(get_conjuncts)
    def get_conjuncts_of_and(ob: And):
        return ob.conjuncts

This behavior is both a convenience enhancement when defining lots of
methods, and a requirement for safely distinguishing multi-argument
overloads in subclasses. Consider, for example, the following code:

    class A(object):
        def foo(self, ob):
            print "got an object"

        @overload
        def foo(__proceed__, self, ob:Iterable):
            print "it's iterable!"
            return __proceed__(self, ob)

    class B(A):
        foo = A.foo     # foo must be defined in local namespace

        @overload
        def foo(__proceed__, self, ob:Iterable):
            print "B got an iterable!"
            return __proceed__(self, ob)

Due to the implicit class rule, calling B().foo([]) will print "B got an
iterable!" followed by "it's iterable!", and finally, "got an object",
while A().foo([]) would print only the messages defined in A.

Conversely, without the implicit class rule, the two "Iterable" methods
would have the exact same applicability conditions, so calling either
A().foo([]) or B().foo([]) would result in an AmbiguousMethods error.

It is currently an open issue to determine the best way to implement
this rule in Python 3.0. Under Python 2.x, a class' metaclass was not
chosen until the end of the class body, which means that decorators
could insert a custom metaclass to do processing of this sort. (This is
how RuleDispatch, for example, implements the implicit class rule.)

PEP 3115, however, requires that a class' metaclass be determined before
the class body has executed, making it impossible to use this technique
for class decoration any more.

At this writing, discussion on this issue is ongoing.

Interfaces and Adaptation

The overloading module provides a simple implementation of interfaces
and adaptation. The following example defines an IStack interface, and
declares that list objects support it:

    from overloading import abstract, Interface

    class IStack(Interface):
        @abstract
        def push(self, ob)
            """Push 'ob' onto the stack"""

        @abstract
        def pop(self):
            """Pop a value and return it"""

    when(IStack.push, (list, object))(list.append)
    when(IStack.pop, (list,))(list.pop)

    mylist = []
    mystack = IStack(mylist)
    mystack.push(42)
    assert mystack.pop()==42

The Interface class is a kind of "universal adapter". It accepts a
single argument: an object to adapt. It then binds all its methods to
the target object, in place of itself. Thus, calling mystack.push(42) is
the same as calling IStack.push(mylist, 42).

The @abstract decorator marks a function as being abstract: i.e., having
no implementation. If an @abstract function is called, it raises
NoApplicableMethods. To become executable, overloaded methods must be
added using the techniques previously described. (That is, methods can
be added using @when, @before, @after, @around, or any custom method
combination decorators.)

In the example above, the list.append method is added as a method for
IStack.push() when its arguments are a list and an arbitrary object.
Thus, IStack.push(mylist, 42) is translated to list.append(mylist, 42),
thereby implementing the desired operation.

Abstract and Concrete Methods

Note, by the way, that the @abstract decorator is not limited to use in
interface definitions; it can be used anywhere that you wish to create
an "empty" generic function that initially has no methods. In
particular, it need not be used inside a class.

Also note that interface methods need not be abstract; one could, for
example, write an interface like this:

    class IWriteMapping(Interface):
        @abstract
        def __setitem__(self, key, value):
            """This has to be implemented"""

        def update(self, other:IReadMapping):
            for k, v in IReadMapping(other).items():
                self[k] = v

As long as __setitem__ is defined for some type, the above interface
will provide a usable update() implementation. However, if some specific
type (or pair of types) has a more efficient way of handling update()
operations, an appropriate overload can still be registered for use in
that case.

Subclassing and Re-assembly

Interfaces can be subclassed:

    class ISizedStack(IStack):
        @abstract
        def __len__(self):
            """Return the number of items on the stack"""

    # define __len__ support for ISizedStack
    when(ISizedStack.__len__, (list,))(list.__len__)

Or assembled by combining functions from existing interfaces:

    class Sizable(Interface):
        __len__ = ISizedStack.__len__

    # list now implements Sizable as well as ISizedStack, without
    # making any new declarations!

A class can be considered to "adapt to" an interface at a given point in
time, if no method defined in the interface is guaranteed to raise a
NoApplicableMethods error if invoked on an instance of that class at
that point in time.

In normal usage, however, it is "easier to ask forgiveness than
permission". That is, it is easier to simply use an interface on an
object by adapting it to the interface (e.g. IStack(mylist)) or invoking
interface methods directly (e.g. IStack.push(mylist, 42)), than to try
to figure out whether the object is adaptable to (or directly
implements) the interface.

Implementing an Interface in a Class

It is possible to declare that a class directly implements an interface,
using the declare_implementation() function:

    from overloading import declare_implementation

    class Stack(object):
        def __init__(self):
            self.data = []
        def push(self, ob):
            self.data.append(ob)
        def pop(self):
            return self.data.pop()

    declare_implementation(IStack, Stack)

The declare_implementation() call above is roughly equivalent to the
following steps:

    when(IStack.push, (Stack,object))(lambda self, ob: self.push(ob))
    when(IStack.pop, (Stack,))(lambda self, ob: self.pop())

That is, calling IStack.push() or IStack.pop() on an instance of any
subclass of Stack, will simply delegate to the actual push() or pop()
methods thereof.

For the sake of efficiency, calling IStack(s) where s is an instance of
Stack, may return s rather than an IStack adapter. (Note that calling
IStack(x) where x is already an IStack adapter will always return x
unchanged; this is an additional optimization allowed in cases where the
adaptee is known to directly implement the interface, without
adaptation.)

For convenience, it may be useful to declare implementations in the
class header, e.g.:

    class Stack(metaclass=Implementer, implements=IStack):
        ...

Instead of calling declare_implementation() after the end of the suite.

Interfaces as Type Specifiers

Interface subclasses can be used as argument annotations to indicate
what type of objects are acceptable to an overload, e.g.:

    @overload
    def traverse(g: IGraph, s: IStack):
        g = IGraph(g)
        s = IStack(s)
        # etc....

Note, however, that the actual arguments are not changed or adapted in
any way by the mere use of an interface as a type specifier. You must
explicitly cast the objects to the appropriate interface, as shown
above.

Note, however, that other patterns of interface use are possible. For
example, other interface implementations might not support adaptation,
or might require that function arguments already be adapted to the
specified interface. So the exact semantics of using an interface as a
type specifier are dependent on the interface objects you actually use.

For the interface objects defined by this PEP, however, the semantics
are as described above. An interface I1 is considered "more specific"
than another interface I2, if the set of descriptors in I1's inheritance
hierarchy are a proper superset of the descriptors in I2's inheritance
hierarchy.

So, for example, ISizedStack is more specific than both ISizable and
ISizedStack, irrespective of the inheritance relationships between these
interfaces. It is purely a question of what operations are included
within those interfaces -- and the names of the operations are
unimportant.

Interfaces (at least the ones provided by overloading) are always
considered less-specific than concrete classes. Other interface
implementations can decide on their own specificity rules, both between
interfaces and other interfaces, and between interfaces and classes.

Non-Method Attributes in Interfaces

The Interface implementation actually treats all attributes and methods
(i.e. descriptors) in the same way: their __get__ (and __set__ and
__delete__, if present) methods are called with the wrapped (adapted)
object as "self". For functions, this has the effect of creating a bound
method linking the generic function to the wrapped object.

For non-function attributes, it may be easiest to specify them using the
property built-in, and the corresponding fget, fset, and fdel
attributes:

    class ILength(Interface):
        @property
        @abstract
        def length(self):
            """Read-only length attribute"""

    # ILength(aList).length == list.__len__(aList)
    when(ILength.length.fget, (list,))(list.__len__)

Alternatively, methods such as _get_foo() and _set_foo() may be defined
as part of the interface, and the property defined in terms of those
methods, but this is a bit more difficult for users to implement
correctly when creating a class that directly implements the interface,
as they would then need to match all the individual method names, not
just the name of the property or attribute.

Aspects

The adaptation system described above assumes that adapters are
"stateless", which is to say that adapters have no attributes or state
apart from that of the adapted object. This follows the
"typeclass/instance" model of Haskell, and the concept of "pure" (i.e.,
transitively composable) adapters.

However, there are occasionally cases where, to provide a complete
implementation of some interface, some sort of additional state is
required.

One possibility of course, would be to attach monkeypatched "private"
attributes to the adaptee. But this is subject to name collisions, and
complicates the process of initialization (since any code using these
attributes has to check for their existence and initialize them if
necessary). It also doesn't work on objects that don't have a __dict__
attribute.

So the Aspect class is provided to make it easy to attach extra
information to objects that either:

1.  have a __dict__ attribute (so aspect instances can be stored in it,
    keyed by aspect class),
2.  support weak referencing (so aspect instances can be managed using a
    global but thread-safe weak-reference dictionary), or
3.  implement or can be adapt to the overloading.IAspectOwner interface
    (technically, #1 or #2 imply this).

Subclassing Aspect creates an adapter class whose state is tied to the
life of the adapted object.

For example, suppose you would like to count all the times a certain
method is called on instances of Target (a classic AOP example). You
might do something like:

    from overloading import Aspect

    class Count(Aspect):
        count = 0

    @after(Target.some_method)
    def count_after_call(self:Target, *args, **kw):
        Count(self).count += 1

The above code will keep track of the number of times that
Target.some_method() is successfully called on an instance of Target
(i.e., it will not count errors unless they occur in a more-specific
"after" method). Other code can then access the count using
Count(someTarget).count.

Aspect instances can of course have __init__ methods, to initialize any
data structures. They can use either __slots__ or dictionary-based
attributes for storage.

While this facility is rather primitive compared to a full-featured AOP
tool like AspectJ, persons who wish to build pointcut libraries or other
AspectJ-like features can certainly use Aspect objects and
method-combination decorators as a base for building more expressive AOP
tools.

XXX spec out full aspect API, including keys, N-to-1 aspects, manual

    attach/detach/delete of aspect instances, and the IAspectOwner
    interface.

Extension API

TODO: explain how all of these work

implies(o1, o2)

declare_implementation(iface, class)

predicate_signatures(ob)

parse_rule(ruleset, body, predicate, actiontype, localdict, globaldict)

combine_actions(a1, a2)

rules_for(f)

Rule objects

ActionDef objects

RuleSet objects

Method objects

MethodList objects

IAspectOwner

Overloading Usage Patterns

In discussion on the Python-3000 list, the proposed feature of allowing
arbitrary functions to be overloaded has been somewhat controversial,
with some people expressing concern that this would make programs more
difficult to understand.

The general thrust of this argument is that one cannot rely on what a
function does, if it can be changed from anywhere in the program at any
time. Even though in principle this can already happen through
monkeypatching or code substitution, it is considered poor practice to
do so.

However, providing support for overloading any function (or so the
argument goes), is implicitly blessing such changes as being an
acceptable practice.

This argument appears to make sense in theory, but it is almost entirely
mooted in practice for two reasons.

First, people are generally not perverse, defining a function to do one
thing in one place, and then summarily defining it to do the opposite
somewhere else! The principal reasons to extend the behavior of a
function that has not been specifically made generic are to:

-   Add special cases not contemplated by the original function's
    author, such as support for additional types.
-   Be notified of an action in order to cause some related operation to
    be performed, either before the original operation is performed,
    after it, or both. This can include general-purpose operations like
    adding logging, timing, or tracing, as well as application-specific
    behavior.

None of these reasons for adding overloads imply any change to the
intended default or overall behavior of the existing function, however.
Just as a base class method may be overridden by a subclass for these
same two reasons, so too may a function be overloaded to provide for
such enhancements.

In other words, universal overloading does not equal arbitrary
overloading, in the sense that we need not expect people to randomly
redefine the behavior of existing functions in illogical or
unpredictable ways. If they did so, it would be no less of a bad
practice than any other way of writing illogical or unpredictable code!

However, to distinguish bad practice from good, it is perhaps necessary
to clarify further what good practice for defining overloads is. And
that brings us to the second reason why generic functions do not
necessarily make programs harder to understand: overloading patterns in
actual programs tend to follow very predictable patterns. (Both in
Python and in languages that have no non-generic functions.)

If a module is defining a new generic operation, it will usually also
define any required overloads for existing types in the same place.
Likewise, if a module is defining a new type, then it will usually
define overloads there for any generic functions that it knows or cares
about.

As a result, the vast majority of overloads can be found adjacent to
either the function being overloaded, or to a newly-defined type for
which the overload is adding support. Thus, overloads are
highly-discoverable in the common case, as you are either looking at the
function or the type, or both.

It is only in rather infrequent cases that one will have overloads in a
module that contains neither the function nor the type(s) for which the
overload is added. This would be the case if, say, a third-party created
a bridge of support between one library's types and another library's
generic function(s). In such a case, however, best practice suggests
prominently advertising this, especially by way of the module name.

For example, PyProtocols defines such bridge support for working with
Zope interfaces and legacy Twisted interfaces, using modules called
protocols.twisted_support and protocols.zope_support. (These bridges are
done with interface adapters, rather than generic functions, but the
basic principle is the same.)

In short, understanding programs in the presence of universal
overloading need not be any more difficult, given that the vast majority
of overloads will either be adjacent to a function, or the definition of
a type that is passed to that function.

And, in the absence of incompetence or deliberate intention to be
obscure, the few overloads that are not adjacent to the relevant type(s)
or function(s), will generally not need to be understood or known about
outside the scope where those overloads are defined. (Except in the
"support modules" case, where best practice suggests naming them
accordingly.)

Implementation Notes

Most of the functionality described in this PEP is already implemented
in the in-development version of the PEAK-Rules framework. In
particular, the basic overloading and method combination framework
(minus the @overload decorator) already exists there. The implementation
of all of these features in peak.rules.core is 656 lines of Python at
this writing.

peak.rules.core currently relies on the DecoratorTools and
BytecodeAssembler modules, but both of these dependencies can be
replaced, as DecoratorTools is used mainly for Python 2.3 compatibility
and to implement structure types (which can be done with named tuples in
later versions of Python). The use of BytecodeAssembler can be replaced
using an "exec" or "compile" workaround, given a reasonable effort. (It
would be easier to do this if the func_closure attribute of function
objects was writable.)

The Interface class has been previously prototyped, but is not included
in PEAK-Rules at the present time.

The "implicit class rule" has previously been implemented in the
RuleDispatch library. However, it relies on the __metaclass__ hook that
is currently eliminated in PEP 3115.

I don't currently know how to make @overload play nicely with
classmethod and staticmethod in class bodies. It's not really clear if
it needs to, however.

Copyright

This document has been placed in the public domain.

  Local Variables: mode: indented-text indent-tabs-mode: nil
  sentence-end-double-space: t fill-column: 70 coding: utf-8 End: