[Python-checkins] r56534 - peps/trunk/pep-3124.txt
phillip.eby
python-checkins at python.org
Tue Jul 24 23:52:25 CEST 2007
Author: phillip.eby
Date: Tue Jul 24 23:52:24 2007
New Revision: 56534
Modified:
peps/trunk/pep-3124.txt
Log:
Misc. fixes/clarifications, add section with rationale for why universal
overloading doesn't automatially lead to chaos and anarchy.
Modified: peps/trunk/pep-3124.txt
==============================================================================
--- peps/trunk/pep-3124.txt (original)
+++ peps/trunk/pep-3124.txt Tue Jul 24 23:52:24 2007
@@ -25,7 +25,8 @@
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.
+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
@@ -384,7 +385,7 @@
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" or method.
+most-specific "before" method.
Unlike "before" and "after" methods, however, "Around" methods *are*
responsible for calling their ``__proceed__`` argument, in order to
@@ -615,10 +616,32 @@
``list.append(mylist, 42)``, thereby implementing the desired
operation.
-(Note: 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.)
+
+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
@@ -765,10 +788,11 @@
# 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 a bit more difficult for users
+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.
@@ -777,9 +801,9 @@
Aspects
-------
-The adaptation system provided assumes that adapters are "stateless",
-which is to say that adapters have no attributes or storage apart from
-those of the adapted object. This follows the "typeclass/instance"
+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.
@@ -789,8 +813,10 @@
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. It also doesn't work
-on objects that don't have a ``__dict__`` attribute.
+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:
@@ -802,7 +828,7 @@
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)
+ interface (technically, #1 or #2 imply this).
Subclassing ``Aspect`` creates an adapter class whose state is tied
to the life of the adapted object.
@@ -817,13 +843,14 @@
count = 0
@after(Target.some_method)
- def count_after_call(self, *args, **kw):
+ 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 (i.e., it will not
-count errors). Other code can then access the count using
-``Count(someTarget).count``.
+``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__``
@@ -832,8 +859,8 @@
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 more expressive AOP
-tools.
+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``
@@ -870,6 +897,98 @@
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
====================
More information about the Python-checkins
mailing list