Permalink
Cannot retrieve contributors at this time
1038 lines (780 sloc)
38.2 KB
| PEP: 3124 | |
| Title: Overloading, Generic Functions, Interfaces, and Adaptation | |
| Version: $Revision$ | |
| Last-Modified: $Date$ | |
| Author: Phillip J. Eby <pje@telecommunity.com> | |
| Discussions-To: Python 3000 List <python-3000@python.org> | |
| 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: |