* New in 0.8: Source and tests are compatible with Python 3 (w/o ``setup.py``)
* 0.8.1: setup.py is now compatible with Python 3 as well
* New in 0.7: `Multiple Types or Objects`_
* New in 0.6: `Inspection and Extension`_, and thread-safe method registration
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
... 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)
... 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)
... 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):
... """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
... 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)
... 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)
Help on function move:
...
move(*args, **kw)
Default implementation goes here
...
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)
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)
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")
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)
... 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
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
passed *positionally* when the function is called.
* Documentation tools don't see the function's original argument signature, so
you have to describe it in the docstring.
* If you have optional arguments, you must duplicate them on every method in
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.)
.. _excellent alternative to the Visitor pattern: http://peak.telecommunity.com/DevCenter/VisitorRevisited
Raw data
{
"_id": null,
"home_page": "http://cheeseshop.python.org/pypi/simplegeneric",
"name": "simplegeneric",
"maintainer": "",
"docs_url": null,
"requires_python": null,
"maintainer_email": "",
"keywords": "",
"author": "Phillip J. Eby",
"author_email": "peak@eby-sarna.com",
"download_url": "https://files.pythonhosted.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip",
"platform": "UNKNOWN",
"description": "* New in 0.8: Source and tests are compatible with Python 3 (w/o ``setup.py``)\r\n\r\n * 0.8.1: setup.py is now compatible with Python 3 as well\r\n\r\n* New in 0.7: `Multiple Types or Objects`_\r\n\r\n* New in 0.6: `Inspection and Extension`_, and thread-safe method registration\r\n\r\nThe ``simplegeneric`` module lets you define simple single-dispatch\r\ngeneric functions, akin to Python's built-in generic functions like\r\n``len()``, ``iter()`` and so on. However, instead of using\r\nspecially-named methods, these generic functions use simple lookup\r\ntables, akin to those used by e.g. ``pickle.dump()`` and other\r\ngeneric functions found in the Python standard library.\r\n\r\nAs you can see from the above examples, generic functions are actually\r\nquite common in Python already, but there is no standard way to create\r\nsimple ones. This library attempts to fill that gap, as generic\r\nfunctions are an `excellent alternative to the Visitor pattern`_, as\r\nwell as being a great substitute for most common uses of adaptation.\r\n\r\nThis library tries to be the simplest possible implementation of generic\r\nfunctions, and it therefore eschews the use of multiple or predicate\r\ndispatch, as well as avoiding speedup techniques such as C dispatching\r\nor code generation. But it has absolutely no dependencies, other than\r\nPython 2.4, and the implementation is just a single Python module of\r\nless than 100 lines.\r\n\r\n\r\nUsage\r\n-----\r\n\r\nDefining and using a generic function is straightforward::\r\n\r\n >>> from simplegeneric import generic\r\n >>> @generic\r\n ... def move(item, target):\r\n ... \"\"\"Default implementation goes here\"\"\"\r\n ... print(\"what you say?!\")\r\n\r\n >>> @move.when_type(int)\r\n ... def move_int(item, target):\r\n ... print(\"In AD %d, %s was beginning.\" % (item, target))\r\n\r\n >>> @move.when_type(str)\r\n ... def move_str(item, target):\r\n ... print(\"How are you %s!!\" % item)\r\n ... print(\"All your %s are belong to us.\" % (target,))\r\n\r\n >>> zig = object()\r\n >>> @move.when_object(zig)\r\n ... def move_zig(item, target):\r\n ... print(\"You know what you %s.\" % (target,))\r\n ... print(\"For great justice!\")\r\n\r\n >>> move(2101, \"war\")\r\n In AD 2101, war was beginning.\r\n\r\n >>> move(\"gentlemen\", \"base\")\r\n How are you gentlemen!!\r\n All your base are belong to us.\r\n\r\n >>> move(zig, \"doing\")\r\n You know what you doing.\r\n For great justice!\r\n\r\n >>> move(27.0, 56.2)\r\n what you say?!\r\n\r\n\r\nInheritance and Allowed Types\r\n-----------------------------\r\n\r\nDefining multiple methods for the same type or object is an error::\r\n\r\n >>> @move.when_type(str)\r\n ... def this_is_wrong(item, target):\r\n ... pass\r\n Traceback (most recent call last):\r\n ...\r\n TypeError: <function move...> already has method for type <...'str'>\r\n\r\n >>> @move.when_object(zig)\r\n ... def this_is_wrong(item, target): pass\r\n Traceback (most recent call last):\r\n ...\r\n TypeError: <function move...> already has method for object <object ...>\r\n\r\nAnd the ``when_type()`` decorator only accepts classes or types::\r\n\r\n >>> @move.when_type(23)\r\n ... def move_23(item, target):\r\n ... print(\"You have no chance to survive!\")\r\n Traceback (most recent call last):\r\n ...\r\n TypeError: 23 is not a type or class\r\n\r\nMethods defined for supertypes are inherited following MRO order::\r\n\r\n >>> class MyString(str):\r\n ... \"\"\"String subclass\"\"\"\r\n\r\n >>> move(MyString(\"ladies\"), \"drinks\")\r\n How are you ladies!!\r\n All your drinks are belong to us.\r\n\r\nClassic class instances are also supported (although the lookup process\r\nis slower than for new-style instances)::\r\n\r\n >>> class X: pass\r\n >>> class Y(X): pass\r\n\r\n >>> @move.when_type(X)\r\n ... def move_x(item, target):\r\n ... print(\"Someone set us up the %s!!!\" % (target,))\r\n\r\n >>> move(X(), \"bomb\")\r\n Someone set us up the bomb!!!\r\n\r\n >>> move(Y(), \"dance\")\r\n Someone set us up the dance!!!\r\n\r\n\r\nMultiple Types or Objects\r\n-------------------------\r\n\r\nAs a convenience, you can now pass more than one type or object to the\r\nregistration methods::\r\n\r\n >>> @generic\r\n ... def isbuiltin(ob):\r\n ... return False\r\n >>> @isbuiltin.when_type(int, str, float, complex, type)\r\n ... @isbuiltin.when_object(None, Ellipsis)\r\n ... def yes(ob):\r\n ... return True\r\n \r\n >>> isbuiltin(1)\r\n True\r\n >>> isbuiltin(object)\r\n True\r\n >>> isbuiltin(object())\r\n False\r\n >>> isbuiltin(X())\r\n False\r\n >>> isbuiltin(None)\r\n True\r\n >>> isbuiltin(Ellipsis)\r\n True\r\n\r\n\r\nDefaults and Docs\r\n-----------------\r\n\r\nYou can obtain a function's default implementation using its ``default``\r\nattribute::\r\n\r\n >>> @move.when_type(Y)\r\n ... def move_y(item, target):\r\n ... print(\"Someone set us up the %s!!!\" % (target,))\r\n ... move.default(item, target)\r\n\r\n >>> move(Y(), \"dance\")\r\n Someone set us up the dance!!!\r\n what you say?!\r\n\r\n\r\n``help()`` and other documentation tools see generic functions as normal\r\nfunction objects, with the same name, attributes, docstring, and module as\r\nthe prototype/default function::\r\n\r\n >>> help(move)\r\n Help on function move:\r\n ...\r\n move(*args, **kw)\r\n Default implementation goes here\r\n ...\r\n\r\n\r\nInspection and Extension\r\n------------------------\r\n\r\nYou can find out if a generic function has a method for a type or object using\r\nthe ``has_object()`` and ``has_type()`` methods::\r\n\r\n >>> move.has_object(zig)\r\n True\r\n >>> move.has_object(42)\r\n False\r\n\r\n >>> move.has_type(X)\r\n True\r\n >>> move.has_type(float)\r\n False\r\n\r\nNote that ``has_type()`` only queries whether there is a method registered for\r\nthe *exact* type, not subtypes or supertypes::\r\n\r\n >>> class Z(X): pass\r\n >>> move.has_type(Z)\r\n False\r\n\r\nYou can create a generic function that \"inherits\" from an existing generic\r\nfunction by calling ``generic()`` on the existing function::\r\n\r\n >>> move2 = generic(move)\r\n >>> move(2101, \"war\")\r\n In AD 2101, war was beginning.\r\n\r\nAny methods added to the new generic function override *all* methods in the\r\n\"base\" function::\r\n\r\n >>> @move2.when_type(X)\r\n ... def move2_X(item, target):\r\n ... print(\"You have no chance to survive make your %s!\" % (target,))\r\n\r\n >>> move2(X(), \"time\")\r\n You have no chance to survive make your time!\r\n\r\n >>> move2(Y(), \"time\")\r\n You have no chance to survive make your time!\r\n\r\nNotice that even though ``move()`` has a method for type ``Y``, the method\r\ndefined for ``X`` in ``move2()`` takes precedence. This is because the\r\n``move`` function is used as the ``default`` method of ``move2``, and ``move2``\r\nhas no method for type ``Y``::\r\n\r\n >>> move2.default is move\r\n True\r\n >>> move.has_type(Y)\r\n True\r\n >>> move2.has_type(Y)\r\n False\r\n\r\n\r\nLimitations\r\n-----------\r\n\r\n* The first argument is always used for dispatching, and it must always be\r\n passed *positionally* when the function is called.\r\n\r\n* Documentation tools don't see the function's original argument signature, so\r\n you have to describe it in the docstring.\r\n\r\n* If you have optional arguments, you must duplicate them on every method in\r\n order for them to work correctly. (On the plus side, it means you can have\r\n different defaults or required arguments for each method, although relying on\r\n that quirk probably isn't a good idea.)\r\n\r\nThese restrictions may be lifted in later releases, if I feel the need. They\r\nwould require runtime code generation the way I do it in ``RuleDispatch``,\r\nhowever, which is somewhat of a pain. (Alternately I could use the\r\n``BytecodeAssembler`` package to do the code generation, as that's a lot easier\r\nto use than string-based code generation, but that would introduce more\r\ndependencies, and I'm trying to keep this simple so I can just\r\ntoss it into Chandler without a big footprint increase.)\r\n\r\n.. _excellent alternative to the Visitor pattern: http://peak.telecommunity.com/DevCenter/VisitorRevisited",
"bugtrack_url": null,
"license": "ZPL 2.1",
"summary": "Simple generic functions (similar to Python's own len(), pickle.dump(), etc.)",
"version": "0.8.1",
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "3d574d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b",
"md5": "f9c1fab00fd981be588fc32759f474e3",
"sha256": "dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173"
},
"downloads": -1,
"filename": "simplegeneric-0.8.1.zip",
"has_sig": false,
"md5_digest": "f9c1fab00fd981be588fc32759f474e3",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 12663,
"upload_time": "2012-04-01T23:39:06",
"upload_time_iso_8601": "2012-04-01T23:39:06.146671Z",
"url": "https://files.pythonhosted.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2012-04-01 23:39:06",
"github": false,
"gitlab": false,
"bitbucket": false,
"lcname": "simplegeneric"
}