The PEAK Developers' Center   Crosscuts UserPreferences
 
HelpContents Search Diffs Info Edit Subscribe XML Print View
The following 385 words could not be found in the dictionary of 50 words (including 50 LocalSpellingWords) and are highlighted below:
And   Assertion   But   Calling   Center   Considerations   Contents   Crosscuts   Current   Define   Dev   Developer   Each   Error   Example   For   From   Guide   Have   Here   However   If   In   Internals   Invisible   Mark   Multiple   Next   No   None   Not   Note   Notice   Now   Or   Parameter   Passing   Performance   Questions   Receivers   Reference   Request   Returns   Runtime   Table   Tests   That   The   Traceback   Type   Verify   You   Your   about   above   across   active   actually   add   adding   all   already   also   alternative   alternatives   amounts   an   and   another   any   app   append   are   args   argument   arguments   around   as   at   back   be   because   better   between   body   bound   bug   but   by   call   callback   callbacks   called   callees   caller   calling   calls   can   cannot   case   cb   changing   class   clients   closer   code   com   comments   comparable   complete   connections   consult   containing   contents   cool   corresponding   course   crosscuts   curently   current   cut   database   db   decorated   def   default   define   defined   definition   del   details   developer   different   directed   directly   distance   don   down   dummy   dummy2   duplicate   during   dynamic   dynamically   eby   environ   error   essence   even   ever   everything   exactly   example   executing   exhausted   exited   explicit   export   exported   exporter   exporters   exporting   exposed   extreme   features   for   found   frame   framework   free   from   func   function   functions   generally   get   giving   global   go   going   got   gritting   guide   has   have   here   hit   however   if   import   in   init   inner   instead   intensive   invisible   invoked   involved   is   issue   it   its   just   keyword   know   kw   languages   last   least   levels   limitations   link   linked   links   list   listinfo   local   long   look   lookup   loop   ma   mailing   mailman   make   mark   massive   match   matching   module   more   most   mucking   multiple   must   name   names   nearest   need   needed   needing   needs   nested   new   no   none   normal   not   notice   object   objects   obtain   of   often   old   on   once   one   or   original   other   outer   overhead   package   painful   parameter   parameters   part   pass   passed   peak   performance   place   please   print   proceeds   provides   providing   raise   re   receive   receiver   receivers   receiving   recent   refactor   referenced   regardless   reports   repr   request   requests   resource   response   return   returned   returns   run   running   runs   same   sarna   scope   scoped   searched   selected   self   set   should   simple   simply   single   skipped   slightly   some   something   stack   start   still   stuff   stupid   such   sure   take   target   teeth   telecommunity   than   that   the   them   then   there   they   things   this   though   thread   through   tight   time   to   toc   too   toolkits   touch   traversal   two   unbound   unless   until   up   upward   urge   usable   usage   use   used   users   uses   using   util   value   values   variable   variables   was   way   we   were   whatever   when   whenever   where   will   wish   with   without   would   writing   wsgi   you   your   yourself   zero  

Clear message


Invisible Parameter Passing using Crosscuts

Have you ever found yourself needing to refactor massive amounts of code just to make sure that one stupid parameter got passed all the way down from the place it was defined, to another place where it was needed, even though none of the functions in between needed to use it? Or have you found yourself gritting your teeth and giving in to the urge to use a global or thread-local variable because the alternatives were just too painful, or because some of the code involved was part of another package?

The peak.util.crosscuts module provides a simple alternative, often found in other dynamic languages and AOP toolkits: dynamically-scoped variables. In essence, they're a way to pass an "invisible parameter" through the call stack, without actually changing any functions or adding any call-time overhead. A simple (and slightly stupid) example:

>>> from peak.util import crosscuts

Here, we define a receiver function to obtain the current Request object:

>>> @crosscuts.receiver
... def current_request():
...     raise RuntimeError("No current request")

Next, we mark the request local variable in this function as containing the current_request value for this function and its callees:

>>> @crosscuts.export(request=current_request)
... def wsgi_app(environ, start_response=None):
...     request = Request(environ)
...     for cb in callbacks: cb()   # look ma, no parameters

And make a zero-argument callback that uses the receiver function to get the current request:

>>> def some_callback():
...     print current_request()

Now, by default, the receiver function runs its original, default body if there's no current value on the call stack:

>>> some_callback()
Traceback (most recent call last):
  ...
RuntimeError: No current request

But, once everything's set up and we actually run the exporting function, the receiver returns the nearest active value on the call stack:

>>> callbacks = [some_callback]

>>> class Request:
...     def __init__(self, environ): self.environ = environ
...     def __repr__(self): return 'Request(%r)' % (self.environ,)

>>> wsgi_app({'x':'y'})
Request({'x': 'y'})

>>> wsgi_app({1:2})
Request({1: 2})

But then things go back to normal once there are no running exporters:

>>> some_callback()
Traceback (most recent call last):
  ...
RuntimeError: No current request

Multiple receivers and exporters can be in use at the same time, and the call stack is searched upward until a matching exporter is found for the called receiver:

>>> @crosscuts.receiver
... def database():
...     raise RuntimeError("No database")

>>> @crosscuts.export(db=database)
... def using_db(db, func, *args, **kw):
...     return func(*args, **kw)

>>> def another_callback():
...     print "Current database:", database()

>>> another_callback()
Traceback (most recent call last):
  ...
RuntimeError: No database

>>> callbacks.append(another_callback)

>>> using_db(123, wsgi_app, {4:5})
Request({4: 5})
Current database: 123

Notice, by the way, that if you're writing a framework that needs to pass its objects (requests, connections, whatever) around, your clients' code need not know about the crosscuts module: users can just import your receiver functions, and call them as they would any comparable API. Your code can simply export local variables from selected functions, instead of mucking around with global variables.

For more on the features, limitations, API details, and other cool stuff, please consult the complete Crosscuts developer's guide. Questions, comments, and bug reports for this package should be directed to the PEAK mailing list.

Table of Contents

Developer's Guide

API Reference

@crosscuts.receiver

Define a receiving function for exported variables. Example usage:

>>> @crosscuts.receiver
... def current_request():
...     raise RuntimeError("No current request")

Returns a receiver function that can be used as a @crosscuts.export() target. Calling the receiver function will return the value of "nearest" matching exported local variable defined on the call stack.

That is, when the receiver is called, lookup proceeds from the caller of the function that invoked it, until a matching, exported, curently-defined local variable is found, or until the call stack is exhausted. If no match is found, the original decorated function is called, and its return value is returned as a default. (That function, of course, is also free to raise an error instead, as in the example above.)

Note, by the way, that a receiver's default function must take exactly zero arguments:

>>> @crosscuts.receiver
... def dummy(a):
...     pass
Traceback (most recent call last):
  ...
AssertionError: Receivers can't take arguments

@crosscuts.export(**kw)

Mark a function's local variable(s) as providing values for the corresponding receivers. Example usage:

@crosscuts.export(request=current_request)
def wsgi_app(environ, start_response):
    request = Request(environ)
    # From here on (unless you 'del request' or the function is exited)
    # any functions called from this frame will receive the value of
    # `request` when they call `current_request()`.  However,
    # `current_request()` will still return its old value if it's
    # called *directly* from this function, regardless of the contents
    # of `request`.  (i.e., the new value is ONLY exported to callees.)

Each keyword argument links an argument or local variable name in the function to a crosscuts.receiver object, such that calling the receiver will return the value of the linked local variable, if it's defined on the current call stack. (That is, whenever the function is executing and the local variable is defined, its value is exported as the receiver's return value.)

Multiple variables can be exported in a single export() call, but they must all be referenced in the function's definition, and you cannot link two different names to the same receiver, even across multiple calls:

>>> @crosscuts.export(something=current_request)
... def dummy():
...     pass
Traceback (most recent call last):
  ...
TypeError: <function dummy...> has no local variable or argument 'something'

>>> @crosscuts.export(x=current_request, y=current_request)
... def dummy(x, y):
...     pass
Traceback (most recent call last):
  ...
TypeError: <...current_request...> is already exposed by 'y' in <...dummy...>

>>> @crosscuts.export(x=current_request)
... @crosscuts.export(y=current_request)
... def dummy(x, y):
...     pass
Traceback (most recent call last):
  ...
TypeError: <...current_request...> is already exposed by 'y' in <...dummy...>

You can, however, export the same name to two different receivers, if you use multiple export calls:

>>> @crosscuts.export(x=current_request)
... @crosscuts.export(x=database)
... def dummy(x):
...     def nested():
...         print current_request(), database()
...     nested()

>>> dummy(42)
42 42

But of course, they have to be receivers defined with @crosscuts.receiver!

>>> @crosscuts.export(x=dummy)
... def dummy2(x):
...     pass
Traceback (most recent call last):
  ...
TypeError: ('Not a crosscuts.receiver', <function dummy...>)

Performance Considerations

Note that calling a receiver function can be resource intensive if there is a long stack distance between the receiver's caller, and the nearest exporter of that variable. If you notice a performance issue in a tight loop, you may wish to re-export a variable closer to where it's actually used, to cut down on the lookup distance, or in an extreme case, add and pass explicit parameters to the functions involved.

(For new code, though, it's generally going to be better to use crosscuts than to duplicate parameters through multiple calling levels that don't actually use or touch them, at least until you're sure there's a performance hit.)

Internals and Tests

Verify that unbound exported variables are skipped during traversal:

>>> @crosscuts.export(request=current_request, db=database)
... def wsgi_app(environ, start_response=None):
...     request = Request(environ)
...     for cb in callbacks: cb()   # look ma, no parameters
...     db = None   # it's a variable, but not bound at callback time

>>> using_db(123, wsgi_app, {4:5})
Request({4: 5})
Current database: 123

>>> wsgi_app({1:2})
Traceback (most recent call last):
  ...
RuntimeError: No database

Verify that nested scope variables are also usable:

>>> def outer(x):
...     @crosscuts.export(x=database)
...     @crosscuts.export(x=current_request)
...     def inner():
...         x
...         for cb in callbacks: cb()
...     return inner

>>> outer(23)()
23
Current database: 23

PythonPowered
EditText of this page (last modified 2009-09-13 21:04:16)
FindPage by browsing, title search , text search or an index
Or try one of these actions: AttachFile, DeletePage, LikePages, LocalSiteMap, SpellCheck