Welcome, guest | Sign In | My Account | Store | Cart

Notice! PyPM is being replaced with the ActiveState Platform, which enhances PyPM’s build and deploy capabilities. Create your free Platform account to download ActivePython or customize Python with the packages you require and get automatic updates.

Download
ActivePython
INSTALL>
pypm install simplegeneric

How to install simplegeneric

  1. Download and install ActivePython
  2. Open Command Prompt
  3. Type pypm install simplegeneric
 Python 2.7Python 3.2Python 3.3
Windows (32-bit)
0.8.1 Available View build log
0.8 Available View build log
0.7 Available View build log
0.8.1 Available View build log
Windows (64-bit)
0.8.1 Available View build log
0.8 Available View build log
0.7 Available View build log
0.8.1 Available View build log
Mac OS X (10.5+)
0.8.1 Available View build log
0.8 Available View build log
0.7 Available View build log
0.8.1 Available View build log
Linux (32-bit)
0.8.1 Available View build log
0.8 Available View build log
0.7 Available View build log
0.8.1 Available View build log
Linux (64-bit)
0.8.1 Available View build log
0.8 Available View build log
0.7 Available View build log
0.8.1 Available View build log
0.8.1 Available View build log
 
License
ZPL 2.1
Imports
Lastest release
version 0.8.1 on May 23rd, 2012

The simplegeneric module lets you define simple single-dispatch generic functions, akin to Python's built-in generic functions like len(), iter() and so on. However, instead of using specially-named methods, these generic functions use simple lookup tables, akin to those used by e.g. pickle.dump() and other generic functions found in the Python standard library.

As you can see from the above examples, generic functions are actually quite common in Python already, but there is no standard way to create simple ones. This library attempts to fill that gap, as generic functions are an excellent alternative to the Visitor pattern, as well as being a great substitute for most common uses of adaptation.

This library tries to be the simplest possible implementation of generic functions, and it therefore eschews the use of multiple or predicate dispatch, as well as avoiding speedup techniques such as C dispatching or code generation. But it has absolutely no dependencies, other than Python 2.4, and the implementation is just a single Python module of less than 100 lines.

Usage

Defining and using a generic function is straightforward:

>>> from simplegeneric import generic
>>> @generic

System Message: ERROR/3 (<string>, line 34)

Inconsistent literal block quoting.

... def move(item, target): ... """Default implementation goes here""" ... print("what you say?!")

>>> @move.when_type(int)
... def move_int(item, target):
...     print("In AD %d, %s was beginning." % (item, target))
>>> @move.when_type(str)
... def move_str(item, target):
...     print("How are you %s!!" % item)
...     print("All your %s are belong to us." % (target,))
>>> zig = object()
>>> @move.when_object(zig)
... def move_zig(item, target):
...     print("You know what you %s." % (target,))
...     print("For great justice!")
>>> move(2101, "war")
In AD 2101, war was beginning.
>>> move("gentlemen", "base")
How are you gentlemen!!
All your base are belong to us.
>>> move(zig, "doing")
You know what you doing.
For great justice!
>>> move(27.0, 56.2)
what you say?!

Inheritance and Allowed Types

Defining multiple methods for the same type or object is an error:

>>> @move.when_type(str)

System Message: ERROR/3 (<string>, line 74)

Inconsistent literal block quoting.

... def this_is_wrong(item, target): ... pass Traceback (most recent call last): ... TypeError: <function move...> already has method for type <...'str'>

>>> @move.when_object(zig)
... def this_is_wrong(item, target): pass
Traceback (most recent call last):
...
TypeError: <function move...> already has method for object <object ...>

And the when_type() decorator only accepts classes or types:

>>> @move.when_type(23)

System Message: ERROR/3 (<string>, line 89)

Inconsistent literal block quoting.

... def move_23(item, target): ... print("You have no chance to survive!") Traceback (most recent call last): ... TypeError: 23 is not a type or class

Methods defined for supertypes are inherited following MRO order:

>>> class MyString(str):

System Message: ERROR/3 (<string>, line 98)

Inconsistent literal block quoting.

... """String subclass"""

>>> move(MyString("ladies"), "drinks")
How are you ladies!!
All your drinks are belong to us.

Classic class instances are also supported (although the lookup process is slower than for new-style instances):

>>> class X: pass
>>> class Y(X): pass
>>> @move.when_type(X)
... def move_x(item, target):
...     print("Someone set us up the %s!!!" % (target,))
>>> move(X(), "bomb")
Someone set us up the bomb!!!
>>> move(Y(), "dance")
Someone set us up the dance!!!

Multiple Types or Objects

As a convenience, you can now pass more than one type or object to the registration methods:

>>> @generic

System Message: ERROR/3 (<string>, line 128)

Inconsistent literal block quoting.

... def isbuiltin(ob): ... return False >>> @isbuiltin.when_type(int, str, float, complex, type) ... @isbuiltin.when_object(None, Ellipsis) ... def yes(ob): ... return True

>>> isbuiltin(1)
True
>>> isbuiltin(object)
True
>>> isbuiltin(object())
False
>>> isbuiltin(X())
False
>>> isbuiltin(None)
True
>>> isbuiltin(Ellipsis)
True

Defaults and Docs

You can obtain a function's default implementation using its default attribute:

>>> @move.when_type(Y)

System Message: ERROR/3 (<string>, line 156)

Inconsistent literal block quoting.

... def move_y(item, target): ... print("Someone set us up the %s!!!" % (target,)) ... move.default(item, target)

>>> move(Y(), "dance")
Someone set us up the dance!!!
what you say?!

help() and other documentation tools see generic functions as normal function objects, with the same name, attributes, docstring, and module as the prototype/default function:

>>> help(move)

System Message: ERROR/3 (<string>, line 170)

Inconsistent literal block quoting.

Help on function move: ... move(*args, **kw) Default implementation goes here ...

System Message: WARNING/2 (<string>, line 170); backlink

Inline emphasis start-string without end-string.

System Message: WARNING/2 (<string>, line 170); backlink

Inline strong start-string without end-string.

Inspection and Extension

You can find out if a generic function has a method for a type or object using the has_object() and has_type() methods:

>>> move.has_object(zig)

System Message: ERROR/3 (<string>, line 184)

Inconsistent literal block quoting.

True >>> move.has_object(42) False

>>> move.has_type(X)
True
>>> move.has_type(float)
False

Note that has_type() only queries whether there is a method registered for the exact type, not subtypes or supertypes:

>>> class Z(X): pass
>>> move.has_type(Z)

System Message: ERROR/3 (<string>, line 198)

Inconsistent literal block quoting.

False

You can create a generic function that "inherits" from an existing generic function by calling generic() on the existing function:

>>> move2 = generic(move)
>>> move(2101, "war")

System Message: ERROR/3 (<string>, line 205)

Inconsistent literal block quoting.

In AD 2101, war was beginning.

Any methods added to the new generic function override all methods in the "base" function:

>>> @move2.when_type(X)

System Message: ERROR/3 (<string>, line 211)

Inconsistent literal block quoting.

... def move2_X(item, target): ... print("You have no chance to survive make your %s!" % (target,))

>>> move2(X(), "time")
You have no chance to survive make your time!
>>> move2(Y(), "time")
You have no chance to survive make your time!

Notice that even though move() has a method for type Y, the method defined for X in move2() takes precedence. This is because the move function is used as the default method of move2, and move2 has no method for type Y:

>>> move2.default is move

System Message: ERROR/3 (<string>, line 226)

Inconsistent literal block quoting.

True >>> move.has_type(Y) True >>> move2.has_type(Y) False

Limitations

  • The first argument is always used for dispatching, and it must always be

System Message: WARNING/2 (<string>, line 237)

Bullet list ends without a blank line; unexpected unindent.

passed positionally when the function is called.

  • Documentation tools don't see the function's original argument signature, so

System Message: WARNING/2 (<string>, line 240)

Bullet list ends without a blank line; unexpected unindent.

you have to describe it in the docstring.

  • If you have optional arguments, you must duplicate them on every method in

System Message: WARNING/2 (<string>, line 243)

Bullet list ends without a blank line; unexpected unindent.

order for them to work correctly. (On the plus side, it means you can have different defaults or required arguments for each method, although relying on that quirk probably isn't a good idea.)

These restrictions may be lifted in later releases, if I feel the need. They would require runtime code generation the way I do it in RuleDispatch, however, which is somewhat of a pain. (Alternately I could use the BytecodeAssembler package to do the code generation, as that's a lot easier to use than string-based code generation, but that would introduce more dependencies, and I'm trying to keep this simple so I can just toss it into Chandler without a big footprint increase.)

Subscribe to package updates

Last updated May 23rd, 2012

Download Stats

Last month:6

What does the lock icon mean?

Builds marked with a lock icon are only available via PyPM to users with a current ActivePython Business Edition subscription.

Need custom builds or support?

ActivePython Enterprise Edition guarantees priority access to technical support, indemnification, expert consulting and quality-assured language builds.

Plan on re-distributing ActivePython?

Get re-distribution rights and eliminate legal risks with ActivePython OEM Edition.