The PEAK Developers' Center   PEAK-Rules UserPreferences
HelpContents Search Diffs Info Edit Subscribe XML Print View
The following 806 words could not be found in the dictionary of 50 words (including 50 LocalSpellingWords) and are highlighted below:
Account   Adding   After   All   Also   Always   Ambiguous   And   Applicable   Are   Around   Bank   Base   Basic   Because   Before   Blah   Builder   But   By   Cannot   Center   Chambers   Chen   Code   Combination   Contents   Core   Creating   Criteria   Currently   Custom   Decorator   Default   Design   Dev   Developer   Discount   Dispatch   Elvis   Emulation   Error   File   For   Functions   Generation   Generic   Get   Gets   Guide   Guido   However   If   In   Indexing   Insufficient   Introduction   Is   It   Its   Let   List   Mailing   Matching   Max   Method   Methods   Min   Most   No   None   Note   Notice   Now   Object   Off   Okay   Or   Out   Overview   Passing   Percent   Please   Porting   Predicates   Price   Product   Protocols   Psyco   Py3   Python   Replace   Rule   Rules   Similarly   So   Some   Sometimes   Source   Subtract   Suede   Suppose   Syntax   Table   Ten   Terminology   That   The   These   This   Thus   To   Tools   Traceback   Transferring   Types   Unlike   Upcharge   User   Using   Value   Values   We   When   Whether   While   Withdraw   You   ability   able   abstract   accept   access   according   acct   actions   actually   adaptation   add   added   adding   additional   adjust   adult   advanced   advise   after   age   agree   aka   algorithm   all   allow   allowed   allows   almost   already   also   alter   although   always   ambiguity   ambiguous   amount   amounts   an   and   another   any   anyway   anywhere   applicable   application   applications   applies   apply   approach   ar   arbitrary   are   arg   args   argument   arguments   around   as   assert   at   automatic   automatically   available   balance   bank   bar   base   basics   baz   be   been   before   begin   beginning   behave   being   between   bit   blue   body   born   both   box   built   bunch   but   by   bytecode   caching   call   called   calling   calls   can   cannot   care   case   cases   certain   chain   check   checking   choose   circumstance   class   classify   clone   code   codebase   com   combination   combinations   combine   combined   combiners   combining   comes   common   compatibility   compile   compiled   complete   complex   compute   computed   computing   conceived   condition   conditions   conflict   containing   contents   convenience   convert   core   corresponding   could   course   create   created   creating   creator   current   currently   custom   customer   daily   decentralized   declare   decorated   decorator   decorators   def   default   define   defined   defining   definitions   demo   dependencies   depending   deprecated   deprecation   described   describing   design   designed   desired   details   detect   dev   development   did   didn   differences   different   differently   direct   directly   discount   discounts   disorganized   dispatch   dispatcher   dispatchers   dispatching   distribution   do   doctests   documentation   documents   does   doing   don   doomed   down   drop   dump   each   easily   easy   eby   effect   egg   either   emulation   engine   engines   enough   entering   entire   entirely   equally   equivalent   error   errors   especially   etc   even   event   evolve   example   except   exception   exceptions   execute   executing   execution   exensible   exist   existing   experiment   expressions   extensibility   extensible   extensive   fast   features   fee   few   find   first   flat   flux   following   for   form   format   framework   free   from   full   fully   func   function   functions   funds   further   future   generate   generated   generates   generation   generic   generics   get   gets   give   given   go   good   got   ground   handle   happens   hard   has   have   help   here   highly   hints   how   however   hurriedly   if   ignoring   immediately   implementation   implementing   import   in   include   included   including   incomplete   incrementally   indexing   individual   infant   init   inner   innermost   instance   instead   int   intended   interfaces   internals   interpreter   into   invocation   invoke   invoked   invokes   is   isinstance   isn   issue   it   items   iteration   iterator   itertools   its   job   join   jump   just   kicker   know   lacking   lacks   lambda   lambdas   last   later   least   leaving   len   less   let   lets   level   like   lines   list   listinfo   little   ll   local   long   look   lot   machinery   mailing   mailman   major   make   makes   mark   marks   match   matching   max   maybe   me   means   mess   method   methods   might   min   minimize   mix   modify   module   modules   monkeypatch   more   most   much   multiple   multiplier   must   named   names   need   needed   needs   neither   new   next   no   nominal   nor   normal   not   note   notice   notification   now   number   ob   object   objects   obsolescence   obvious   occur   of   offer   official   omit   omits   on   once   one   ones   only   operates   opposed   optimize   optionally   options   or   order   original   other   our   outer   outermost   over   overall   overdraft   overhead   override   own   package   packages   pain   pass   pattern   peak   percentage   perhaps   places   plug   pluggability   point   poor   port   porting   positional   possible   post   postprocessing   potentially   pprint   practical   precedence   predecessor   predeclare   predicate   predict   preschooler   preteen   pretty   prevent   previous   price   pricing   primary   print   printing   prior   probably   processed   produce   product   protection   provides   questions   quick   raise   raised   raises   rather   re   real   recent   recognize   recreate   regarding   release   remaining   replacing   respectively   rest   result   resulting   results   return   returned   reverse   reversed   roughly   routine   rules   run   running   safe   same   sarna   scenarios   second   section   see   self   semantics   senior   separate   sequence   series   set   setitem   setting   shoes   should   showed   sig   signature   signs   similar   simple   simplest   simply   single   sixteen   small   snapshots   so   some   sometimes   soon   sorted   special   specialized   specifc   specific   specificity   specifies   standard   start   state   stdlib   still   str   straightforward   strategy   streamline   string   strings   strongly   stuff   suede   sum   summing   super   supplied   support   supports   sure   svn   svnroot   sweet   tail   take   taken   taking   tax   techniques   teenager   telecommunity   tell   template   templates   terminates   testing   than   that   the   their   then   there   these   they   thing   things   think   thinking   this   through   thrown   thus   time   times   title   to   toc   together   too   total   trap   trick   try   trying   tune   tuned   tuning   tuple   tuples   turn   tutorial   two   type   types   uncaught   under   up   update   us   usage   usages   use   used   useful   uses   using   validation   value   values   variables   version   very   want   warnings   was   way   ways   we   were   what   whatever   when   where   whether   which   whose   will   willing   with   withdraw   without   won   words   work   works   world   wrap   wrapper   write   writing   written   yet   yielding   yields   you   your  

Clear message

Creating Generic Functions using PEAK-Rules

PEAK-Rules is a highly-extensible framework for creating and using generic functions, from the very simple to the very complex. Out of the box, it supports multiple-dispatch on positional arguments using tuples of types, full predicate dispatch using strings containing Python expressions, and CLOS-like method combining. (But the framework allows you to mix and match dispatch engines and custom method combinations, if you need or want to.)

Basic usage:

>>> from peak.rules import abstract, when, around, before, after

>>> @abstract()
... def pprint(ob):
...     """A pretty-printing generic function"""

>>> @when(pprint, (list,))
... def pprint_list(ob):
...     print "pretty-printing a list"

>>> @when(pprint, "isinstance(ob,list) and len(ob)>50")
... def pprint_long_list(ob):
...     print "pretty-printing a long list"

>>> pprint([1,2,3])
pretty-printing a list

>>> pprint([42]*1000)
pretty-printing a long list

>>> pprint(42)
Traceback (most recent call last):
NoApplicableMethods: ...

PEAK-Rules works with Python 2.3 and up -- just omit the @ signs if your code needs to run under 2.3. Also, note that with PEAK-Rules, any function can be generic: you don't have to predeclare a function as generic. (The abstract decorator is used to declare a function with no default method; i.e., one that will give a NoApplicableMethods if no rules match the arguments it's invoked with, as opposed to executing a default implementation.)

PEAK-Rules is still under development; it lacks much in the way of error checking, so if you mess up your rules, it may not be obvious where or how you did. User documentation is also lacking, although there are extensive doctests describing and testing most of its internals, including:

(Please note that these documents are still in a state of flux and some may still be incomplete or disorganized, prior to the first official release.)

Source distribution snapshots are generated daily, but you can also update directly from the development version in SVN.

Table of Contents

Developer's Guide

XXX basics tutorial should go here

Method Combination and Custom Method Types

Sometimes, more than one method of a generic function applies in a given circumstance. For example, you might need to sum the results of a series of pricing rules in order to compute a product's price. Or, sometimes you'd like a method to be able to modify the result of a less-specific method.

For these scenarios, you will want to use "method combination", either using PEAK-Rules' built-in method decorators, or custom method types of your own.

Using next_method

By default, a generic function will only invoke the most-specific applicable method. However, if you add a next_method argument to the beginning of an individual method's signature, you can use it to call the "next method" that applies. That is, the second-most-specific method. If that method also has a next_method argument, it too will be able to invoke the next method after it, and so on, down through all the applicable methods. For example:

>>> from peak.rules import DispatchError

>>> @abstract()
... def foo(bar, baz):
...     """Foo bar and baz"""

>>> @when(foo, "bar>1 and baz=='spam'")
... def foo_one_spam(next_method, bar, baz):
...     return bar + next_method(bar, baz)

>>> @when(foo, "baz=='spam'")
... def foo_spam(bar, baz):
...     return 42

>>> @when(foo, "baz=='blue'")
... def foo_spam(next_method, bar, baz):
...     # if next_method is an instance of DispatchError, it means
...     # that calling it will raise that error (NoApplicableMethods
...     # or AmbiguousMethods)
...     assert isinstance(next_method, DispatchError)
...     # but we'll call it anyway, just to demo the error
...     return 22 + next_method(bar, baz)

>>> foo(2,"spam")   # 2 + 42

>>> foo(2,"blue")   # 22 + next method!
Traceback (most recent call last):
  File ... combiners.txt... in foo_spam
    return 22 + next_method(self,bar,baz)
NoApplicableMethods: ...

Notice that next_method comes before self in the arguments if the generic function is an instance method. (If used, it must be the very first argument of the method.) Its value is supplied automatically by the generic function machinery, so when you call next_method you do not have to care whether the next method needs to know its next method; just pass in all of the other arguments (including self if applicable) and the next_method implementation will do the rest.

Also notice that methods that do not call their next method do not need to have a next_method argument. If a method calls next_method when there are no further methods available, NoApplicableMethods is raised. Similarly, if there is more than one "next method" and they are all equally specific (i.e. ambiguous), then AmbiguousMethods is raised.

Most of the time, you will know when writing a routine whether it's safe to call next_method. But sometimes you need a routine to behave differently depending on whether a next method is available. If calling next_method will raise an error, then next_method will be an instance of the error class, so you can detect it with isinstance(). If there are no remaining methods, then next_method will be an instance of NoApplicableMethods, and if the next method is ambiguous, it will be an AmbiguousMethods instance. In either case, calling next_method will raise that error with the supplied arguments. (And DispatchError is a base class of both AmbiguousMethods and NoApplicableMethods, so you can just check for that.)

Before/After Methods

Sometimes you'd like for some additional validation or notification to occur before or after the "normal" or "primary" methods. This is what "before", "after", and "around" methods are for. For example:

>>> class BankAccount:
...     def __init__(self,balance,protection=0):
...         self.balance = balance
... = protection
...     def withdraw(self,amount):
...         """Withdraw 'amount' from bank"""
...         self.balance -= amount      # nominal case
...     @before(withdraw, "amount>self.balance and")
...     def prevent_overdraft(self, amount):
...         raise ValueError("Insufficient funds")
...     @after(withdraw, "amount>self.balance")
...     def automatic_overdraft(self, amount):
...         print "Transferring",-self.balance,"from overdraft protection"
... += self.balance
...         self.balance = 0

>>> acct = BankAccount(200)
>>> acct.withdraw(400)
Traceback (most recent call last):
ValueError: Insufficient funds

>>> = 300
>>> acct.withdraw(400)
Transferring 200 from overdraft protection
>>> acct.balance

This specific example could have been written entirely with normal when() methods, by using more complex conditions. But, in more complex scenarios, where different modules may be adding rules to the same generic function, it's not possible for one module to predict whether its conditions will be more specific than another's, and whether it will need to call next_method, etc.

So, generic functions offer before() and after() methods, that run before and after the when() (aka "primary") methods, respectively. Unlike primary methods, before() and after() methods:

  • Are allowed to have ambiguous conditions (and if they do, they execute in the order in which they were added to the generic function)
  • Are always run when their conditions apply, with no need to call next_method to invoke the next method
  • Cannot return a useful value and do not have access to the return value of any other method

The overall order of method execution is:

  1. All applicable before() methods, from most-specific to least-specific, methods at the same level of specificity execute in the order they were added.
  2. Most-specifc primary method, which may optionally chain to less-specific primary methods. AmbiguousMethods or NoApplicableMethods may be raised if the most-specific method is ambiguous or no primary methods are applicable.
  3. All applicable after() methods, from least-specific to most-specific, with methods at the same level of specificity executing in the reverse order from the order they were added. (In other words, the more specific the after() condition, the "more after" it gets run!)

If any of these methods raises an uncaught exception, the overall function execution terminates at that point, and methods later in the order are not run.

"Around" Methods

Sometimes you need to recognize certain special cases, and perhaps not run the entire generic function, or need to alter its return value in some way, or perhaps trap and handle certain exceptions, etc. You can do this with "around" methods, which run "around" the entire "before/primary/after" sequence described in the previous section.

A good way to think of this is that it's as if the "around" methods form a separate generic function, whose default (least-specific) method is the original, "inner" generic function.

When "around" methods are applicable on a given invocation of the generic function, the most-specific "around" method is invoked. It may then choose to call its next_method to invoke the next-most-specific "around" method, and so on. When there are no more "around" methods, calling next_method instead invokes the "before", "primary", and "after" methods, according to the sequence described in the previous section. For example:

>>> @around(BankAccount.withdraw, "amount > self.balance")
... def overdraft_fee(next_method,self,amount):
...     print "Adding overdraft fee of $25"
...     return next_method(self,amount+25)

>>> acct.withdraw(20)
Adding overdraft fee of $25
Transferring 45 from overdraft protection


Sometimes, if you're defining a generic function whose job is to classify things, it can get to be a pain defining a bunch of functions or lambdas just to return a few values -- especially if the generic function has a complex signature! So peak.rules provides a convenience function, value() for doing this:

>>> from peak.rules import value
>>> value(42)

>>> value(42)('whatever')

>>> classify = abstract(lambda age:None)

>>> when(classify, "age<2")(value("infant"))

>>> when(classify, "age<13")(value("preteen"))

>>> when(classify, "age<5")(value("preschooler"))

>>> when(classify, "age<20")(value("teenager"))

>>> when(classify, "age>=20")(value("adult"))

>>> when(classify, "age>=55")(value("senior"))

>>> when(classify, "age==16")(value("sweet sixteen"))
value('sweet sixteen')

>>> classify(17)

>>> classify(42)

Method Combination

The combine_using() decorator marks a function as yielding its method results (most-specific to least-specific, with later-defined methods taking precedence), and optionally specifies how the resulting iteration will be post-processed:

>>> from peak.rules import combine_using

Let's take a look at how it works, by trying it with different ways of postprocessing on an example generic function. We'll start by defining a function to recreate a generic function with the same set of methods, so you can see what happens when we pass different arguments to combine_using:

>>> class A: pass
>>> class B(A): pass
>>> class C(A, B): pass
>>> class D(B, A): pass

>>> def demo(*args):
...     """We'll be setting this function up multiple times, so we do it in
...        a function.  In normal code, you won't need this outer function!
...     """
...     @combine_using(*args)
...     def func(ob):
...         return "default"
...     when(func, (object,))(value("object"))
...     when(func, (int,))   (value("int"))
...     when(func, (str,))   (value("str"))
...     when(func, (A,))     (value("A"))
...     when(func, (B,))     (value("B"))
...     return func

In the simplest case, you can just call @combine_using() with no arguments, and get a generic function that yields the results returned by its methods, in order from most-specific to least-specific:

>>> func = demo()

>>> list(func(A()))
['A', 'object', 'default']

>>> list(func(42))
['int', 'object', 'default']

In the event of ambiguity between methods, methods defined later are called first:

>>> list(func(C()))
['B', 'A', 'object', 'default']

>>> list(func(D()))
['B', 'A', 'object', 'default']

Passing a function to @combine_using(), however, makes it wrap the result iterator with that function, e.g.:

>>> func = demo(list)

>>> func(A())
['A', 'object', 'default']

While including abstract anywhere in the wrapper sequence makes the function abstract (i.e., it omits the original function's body from the defined methods):

>>> func = demo(abstract, list)

>>> func(A())  # 'default' isn't included any more:
['A', 'object']

You can also include more than one function in the wrapper list, and they will be called on the result iterator, first function outermost, ignoring any abstract in the sequence:

>>> func = demo(str.title, ' '.join)

>>> func(B())
'B A Object Default'

>>> func = demo(str.title, abstract, ' '.join)

>>> func(B())
'B A Object'

Some stdlib functions you might find useful for combine_using() include:

  • itertools.chain
  • sorted
  • reversed
  • list
  • set
  • "".join (or other string)
  • any
  • all
  • sum
  • min
  • max

(And of course, you can write and use arbitrary functions of your own.)

By the way, when using "around" methods with a method combination, the innermost next_method will return the fully processed combination of all the "when" methods, with the "before/after" methods running before and after the result is returned:

>>> from peak.rules import before, after, around

>>> def b(ob): print "before"
>>> def a(ob): print "after"
>>> def ar(next_method, ob):
...     print "entering around"
...     print next_method(ob)
...     print "leaving around"

>>> b = before(func, ())(b)
>>> a = after(func, ())(a)
>>> ar = around(func, ())(ar) 

>>> func(B())
entering around
B A Object
leaving around

Custom Method Types

If the standard before/after/around/when/combine_using decorators don't work for your application, you can create custom ones by defining your own "method types" and decorators.

Suppose, for example, that you are using a "pricing rules" generic function that operates by summing its methods' return values to produce a total:

>>> @combine_using(sum)
... def getPrice(product, customer=None, options=()):
...     """Get this product's price"""
...     return 0    # base price for arbitrary items

>>> class Product:
...     @when(getPrice)
...     def __addBasePrice(self, customer, options):
...         """Always include the product's base price"""
...         return self.base_price

>>> @when(getPrice, "'blue suede' in options")
... def blueSuedeUpcharge(product,customer,options):
...     return 24

>>> getPrice("arbitrary thing")

>>> shoes = Product()
>>> shoes.base_price = 42

>>> getPrice(shoes)

>>> getPrice(shoes, options=['blue suede'])

This is useful, sure, but what if you also want to be able to compute discounts or tax as a percentage of the total, rather than as flat additional amounts?

We can do this by implementing a custom "method type" and a corresponding decorator, to let us mark rules as computing a discount instead of a flat amount.

We'll start by defining the template that will be used to generate our method's implementation.

This format for method templates is taken from the DecoratorTools package's @template_method decorator. $args is used in places where the original generic function's calling signature is needed, and all local variables should be named so as not to conflict with possible argument names. The first argument of the template method will be the generic function the method is being used with, and all other arguments are defined by the method type's creator.

In our case, we'll need two arguments: one for the "body" (the discount method being decorated) and one for the "next method" that will be called to get the base price:

>>> def discount_template(__func, __body, __next_method):
...     return """
...     __price = __next_method($args)
...     return __price - (__body($args) * __price)
...     """

Okay, that's the easy bit. Now we need to define a bunch of other stuff to turn it into a method type and a decorator:

>>> from peak.rules.core import Around, MethodList, compile_method, \
...     combine_actions

>>> class DiscountMethod(Around):
...     """Subtract a discount"""
...     def override(self, other):
...         if self.__class__ == other.__class__:
...             return self.override(other.tail)  # drop the other one
...         return self.tail_with(combine_actions(self.tail, other))
...     def compiled(self, engine):
...         body = compile_method(self.body, engine)
...         next = compile_method(self.tail, engine)
...         return engine.apply_template(discount_template, body, next)

>>> discount_when = DiscountMethod.make_decorator(
...     "discount_when", "Discount price by the returned multiplier"
... )

>>> DiscountMethod >> MethodList    # mark precedence
<class 'peak.rules.core.MethodList'>

The make_decorator() method of Method objects lets you create decorators similar to when(), that we can now use to add a discount:

>>> @discount_when(getPrice, 
...    "customer=='Elvis' and 'blue suede' in options and product is shoes"
... )
... def ElvisGetsTenPercentOff(product,customer,options):
...     return .1

>>> getPrice(shoes)

>>> print getPrice(shoes, 'Elvis', options=['blue suede'])

>>> getPrice(shoes, 'Elvis')     # no suede, no discount!
This is still pretty hard; but without some real-world use cases for custom methods, it's hard to tell how to streamline the common cases.

Porting Code from RuleDispatch

The major design differences between PEAK-Rules and RuleDispatch are:

  1. It's designed for extensibility/pluggability from the ground up
  2. It's built from the ground up using generic functions instead of adaptation, so its code is a lot more straightforward. (The current implementation, combined with all its dependencies, is roughly the same number of lines as RuleDispatch without any of its dependencies -- and already has features that can't even be added to RuleDispatch.)
  3. It generates custom bytecode for each generic function, to minimize calling and interpreter overhead, and to potentially allow compatibility with Psyco and PyPy in the future. (Currently, neither Psyco nor PyPy support the "computed jump" trick used in the generated code, so don't try to Psyco-optimize any generic functions yet - it'll probably core dump!)

Because of its exensible design, PEAK-Rules can use custom-tuned engines for specific application scenarios, and over time it may evolve the ability to accept "tuning hints" to adjust the indexing techniques for special cases.

PEAK-Rules also supports the full method combination semantics of RuleDispatch using a new decentralized approach, that allows you to easily create new method types or combination semantics, complete with their own decorators (like when, around, etc.)

These decorators also all work with existing functions; you do not have to predeclare a function generic in order to use it. You can also omit the condition from the decorator call, in which case the effect is the same as RuleDispatch's strategy.default, i.e. there is no condition. Thus, you can actually use PEAK-Rules's around() as a quick way to monkeypatch existing functions, even ones defined by other packages. (And the decorators use the DecoratorTools package, so you can omit the @ signs for Python 2.3 compatibility.)

RuleDispatch was always conceived as a single implementation of a single dispatch algorithm intended to be "good enough" for all uses. Guido's argument on the Py3K mailing list, however, was that applications with custom dispatch needs should write custom dispatchers. And I almost agree -- except that I think they should get a RuleDispatch-like dispatcher for free, and be able to tune or write ones to plug in for specialized needs.

The kicker was that Guido's experiment with type-tuple caching (a predecessor algorithm to the Chambers-and-Chen algorithm used by RuleDispatch) showed it to be fast enough for common uses, even without any C code, as long as you were willing to do a little code generation. The code was super-small, simple, and fast enough that it got me thinking it was good enough for maybe 50% of what you need generic functions for, especially if you added method combination.

And thus, PEAK-Rules was born, and RuleDispatch doomed to obsolescence. (It didn't help that RuleDispatch was a hurriedly-thrown-together experiment, with poor testing and little documentation, either.)

So, if you are currently using RuleDispatch, we strongly advise that you port your code. To convert the most common RuleDispatch usages, simply do the following:

  • Replace @dispatch.on() and @dispatch.generic() with @abstract()
  • Replace @func.when(sig) with @when(func, sig) (and the same for before, after, and around)
  • When replacing @func.when(type) calls where func was defined with @dispatch.on, use @func.when("isinstance(arg, type)"), where arg is the argument that was named in the @dispatch.on() call.

RuleDispatch Emulation

If your code doesn't use much of the RuleDispatch API, you may be able to use PEAK-Rules' "emulation API", which supports the following RuleDispatch APIs:

  • dispatch.on, dispatch.generic, and``
  • strategy.default, strategy.Min, strategy.Max
  • DispatchError, NoApplicableMethods, and AmbiguousMethod errors
  • The when(), before(), after() and around() methods of generic functions.

(Note that some APIs may issue deprecation warnings (e.g., and over time, the entire API will be deprecated. Please update your code as soon as practical.)

The emulation API does NOT support:

  • custom combiners (use custom method types instead)
  • The addMethod or __setitem__ APIs for adding rules
  • the clone() method of generics created with dispatch.on
  • PyProtocols (i.e., interfaces cannot be used for dispatching)

In the future, a PyProtocols emulation API may be added, but it doesn't exist yet.

To use the emulation API, simply import dispatch from peak.rules:

>>> from peak.rules import dispatch

>>> @dispatch.generic()     # roughly equivalent to @abstract()
... def a_function(an_arg, other_arg):
...     """Blah"""

>>> @a_function.when((int, str))
... def a_when_int_str(an_arg, other_arg):
...     print "int and str"

>>> a_function(42, "blue")
int and str

>>> a_function("blue", 42)
Traceback (most recent call last):
NoApplicableMethods: (('blue', 42), {})

Whether you use dispatch.generic or dispatch.on to define a generic function, you can begin using peak.rules.when to declare methods immediately:

>>> @when(a_function, (str, int))
... def a_when_str_int(an_arg, other_arg):
...     print "str and int"

>>> a_function("blue", 42)
str and int

This means that you don't have to update your entire codebase at once; you can port your method definitions incrementally, if desired.

Mailing List

Please direct questions regarding this package to the PEAK mailing list; see for details.

EditText of this page (last modified 2010-08-17 19:58:51)
FindPage by browsing, title search , text search or an index
Or try one of these actions: AttachFile, DeletePage, LikePages, LocalSiteMap, SpellCheck