Docs
* Outline sections. * Rewrite overview and beginning of multidispatch feature.
This commit is contained in:
parent
f4fc390168
commit
4ff1e9966d
6
docs/_static/nature.css
vendored
6
docs/_static/nature.css
vendored
@ -238,6 +238,12 @@ p.admonition-title {
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
div.sidebar {
|
||||
font-size: 0.9em;
|
||||
background: #F8F8F8;
|
||||
border: 1px solid #EEE;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 10px;
|
||||
|
252
docs/index.rst
252
docs/index.rst
@ -10,36 +10,59 @@ Generic is a general purpose programming library aminig to bring some of the
|
||||
generic and reusable programming features to Python.
|
||||
|
||||
.. contents:: Documentation contents
|
||||
:local:
|
||||
:backlinks: none
|
||||
|
||||
Overview and installation instructions
|
||||
--------------------------------------
|
||||
Overview
|
||||
--------
|
||||
|
||||
Generic is trying to be simple and easy-to-use programming library that provides
|
||||
a core set of tools for generic programming in Python. It has no external
|
||||
dependencies. It is very small and trying to be well documented and tested.
|
||||
a core set of tools for generic programming in Python by being as much
|
||||
"pythonic" as possible. It has no external dependencies, it is very small and
|
||||
trying to be well documented and tested.
|
||||
|
||||
The installation process is quite simple, you can just use *easy_install*::
|
||||
Installation and development process
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
easy_install generic
|
||||
The installation process is quite simple, you can just use ``easy_install
|
||||
generic`` or ``pip install generic`` command to obtain the most recent stable
|
||||
version of library.
|
||||
|
||||
or *pip*::
|
||||
|
||||
pip install generic
|
||||
|
||||
Recent in-development version of library is hosted on the *GitHub*, so you can clone the repo with::
|
||||
Recent in-development version of library is hosted_ on the *GitHub*, so you can clone the repo with::
|
||||
|
||||
git clone http://github.com/andreypopp/generic
|
||||
|
||||
For reporting bugs you can use issues_ on GitHub.
|
||||
|
||||
.. _issues: https://github.com/andreypopp/generic/issues
|
||||
.. _hosted: https://github.com/andreypopp/generic
|
||||
|
||||
What's in 0.3 version
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Generic 0.3 is the first more or less public and usable release, it contains:
|
||||
|
||||
* Registry implementation borrowed from happy_ project by Chris Rossi.
|
||||
|
||||
* Event management API with event inheritance support.
|
||||
|
||||
* Multidispatching with functions and methods.
|
||||
|
||||
|
||||
See changelog_ for more details.
|
||||
|
||||
.. _happy: https://bitbucket.org/chrisrossi/happy
|
||||
.. _changelog: https://github.com/andreypopp/generic/blob/master/CHANGELOG.rst
|
||||
|
||||
Similar projects
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Multidispatching
|
||||
----------------
|
||||
|
||||
Multidispatching is not a new concept in the world of programming languages --
|
||||
it's well known in the various LISPs dialects and more recenlty -- from Clojure,
|
||||
which is exactly LISP but on JVM.
|
||||
|
||||
Multidispatching is a some kind of control structure for dispatching function
|
||||
call into separate code branches based on function argument values. The basic
|
||||
and naive implementation of such technique can be written as::
|
||||
Multidispatching is a technique of providing different code implementations for
|
||||
different data behind the simple and uniform interface. Let's better
|
||||
provide example of such approach::
|
||||
|
||||
def double(x):
|
||||
if isinstance(x, int):
|
||||
@ -58,23 +81,30 @@ and naive implementation of such technique can be written as::
|
||||
assert double(2) == 4
|
||||
assert double("2") == "4"
|
||||
|
||||
So the ``double`` function just inspects its argument type and dispatch
|
||||
execution to ``double_int`` or ``double_basestring`` by situation. We will call
|
||||
such functions *multifunctions*. Note, that this technique can be also applied
|
||||
to functions of multiple arguments or you can define methods as multifunctions
|
||||
(in this case we will call them *multimethods*).
|
||||
The ``double`` function just inspects its argument type and dispatch
|
||||
execution to ``double_int`` or ``double_basestring`` by situation. Such function is called *multifunction* and other two -- *concrete implementations*.
|
||||
|
||||
The approach above works but it has some drawbacks:
|
||||
.. sidebar:: A bit of history
|
||||
|
||||
Multidispatching is not a new concept in the world of programming languages
|
||||
-- it's well known in the LISP world. The most recent implementation of LISP
|
||||
on JVM -- Clojure -- also provides such feature.
|
||||
|
||||
* It requires too much boilerplate -- it is really boring to write down all
|
||||
these ``isinstance`` checks by hand.
|
||||
But what's wrong with the example above? Why generic is trying to provide
|
||||
another way to define multifunctions (and *multimethods*) in Python?
|
||||
|
||||
* It provides no extensibility -- you (or other developer) cannot manage
|
||||
``double`` function to handle more types than ``basestring`` or ``int`` by
|
||||
not modifying original source code of ``double`` function.
|
||||
There're two issues which are not addressed by this naive implementation --
|
||||
*simplicity* and *extensibity*.
|
||||
|
||||
Generic is targeting at providing another way to define multifunctions and
|
||||
multimethods which are not affected by these issues.
|
||||
Let's see again at the ``double`` function -- it just consist of a bunch of
|
||||
conditional statements with ``isinstance`` checks. Writing them down by hand is
|
||||
cumbersome, it should be generated automatically from some declarations.
|
||||
|
||||
Speaking of extensibily -- ``double`` function cannot be extended to handle more
|
||||
argument types by not modifying its source code, which isn't a good thing.
|
||||
|
||||
Generic is trying to address these issues by providing a declarative way for
|
||||
defining multifunctions and multimethods.
|
||||
|
||||
Multifunctions
|
||||
~~~~~~~~~~~~~~
|
||||
@ -86,7 +116,7 @@ library::
|
||||
|
||||
@multifunction(int)
|
||||
def double(x):
|
||||
returx x * 2
|
||||
return x * 2
|
||||
|
||||
@double.when(basestring)
|
||||
def double(x):
|
||||
@ -115,6 +145,9 @@ another modules::
|
||||
So you can use that approach for application extensibilty by wrapping
|
||||
functionality to be extended by 3rd party software into multifunctions.
|
||||
|
||||
Overriding multifunction implementations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Multimethods
|
||||
~~~~~~~~~~~~
|
||||
|
||||
@ -140,6 +173,161 @@ Let's define multimethod with generic::
|
||||
assert o.double(2) == 4
|
||||
assert o.double("2") == "4"
|
||||
|
||||
First of all spot some differences between multimethods and multifunctions --
|
||||
we're using ``multimethod`` decorator and decorating enclosed class with
|
||||
``has_multimethods`` class-decorator. The later is becuase of Python object
|
||||
model -- we cannot inject attributes inside class while the class is being
|
||||
constructed.
|
||||
|
||||
.. sidebar:: Why using class decorator?
|
||||
|
||||
We need to convert our multimethods instances into UnboundMethod objects
|
||||
after the class is constructed -- this is the way CPython, the refercen
|
||||
Python implementation, handles class construction.
|
||||
|
||||
Apart from that two things there're no other big differences -- multimethods
|
||||
behave the same. Another fancy thing we can do with multimethods is
|
||||
|
||||
Case study: pretty printing
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Thread safety
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Event management
|
||||
----------------
|
||||
|
||||
Another topic generic brings to the table is *event management*, which is the
|
||||
right tool for building extensible and yet simple pieces of software.
|
||||
|
||||
What event management means? It is about
|
||||
|
||||
* Making your application components speak in term of events not direct calls
|
||||
into each other.
|
||||
|
||||
* Providing a way to 3rd party developers extend your application by providing
|
||||
own event handlers.
|
||||
|
||||
It may sounds complex and huge but in its core it is just about events (which
|
||||
are just plain simple Python objects) and event handlers (which are Python
|
||||
functions).
|
||||
|
||||
Generic provides *global event management API* which is suitable for small and
|
||||
simple applications and more customized *event management API* (on which global
|
||||
API is based on) for more complex application.
|
||||
|
||||
Global event management API
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Let's start with the example::
|
||||
|
||||
from generic.event import subscribe, fire
|
||||
|
||||
class PizzaDelivered(object):
|
||||
""" Indicates pizza delivered situatuion in your application."""
|
||||
|
||||
def __init__(self, pizza):
|
||||
self.pizza = pizza
|
||||
self.money = None
|
||||
|
||||
def pay(self, money):
|
||||
self.money = money
|
||||
|
||||
@subscribe(PizzaDelivered)
|
||||
def pay_bill(event):
|
||||
event.pay(25)
|
||||
|
||||
pizza_delivered = PizzaDelivered("4 cheeses")
|
||||
assert pizza_delivered.money is None
|
||||
fire(pizza_delivered)
|
||||
assert not pizza_delivered.money == 25
|
||||
|
||||
The last assertion says us that ``pay_bill`` function was executed with
|
||||
``pizza_delivered`` event object as argument after we've fired it.
|
||||
|
||||
As I've mentined previously events allow us to decouple application components
|
||||
from each other -- we do not call ``pay_bill`` function directly, which is good
|
||||
thing.
|
||||
|
||||
Also we can now extend our pizza-style application by just registering another
|
||||
handlers::
|
||||
|
||||
# Another module
|
||||
from pizzaapp import PizzaDelivered
|
||||
from generic.event import subscribe
|
||||
from logging import getLogger
|
||||
|
||||
log = getLogger('pizzalogger')
|
||||
|
||||
@subscribe(PizzaDelivered)
|
||||
def pizza_logger(event):
|
||||
log.info("Pizza '%s' was delivered", event.pizza)
|
||||
|
||||
So after ``fire(pizza_delivered)`` call we will see corresponding record in our
|
||||
log output.
|
||||
|
||||
Working with event managers directly
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sometimes using global API isn't a good thing -- maybe we want to host several
|
||||
application instances inside one Python interpreter or just to separate
|
||||
different event types into different event management tiers. Fortunately we can
|
||||
work with so-called *event managers* directly by creating them and registering
|
||||
event handlers against them::
|
||||
|
||||
from generic.event import Manager
|
||||
|
||||
events = Manager()
|
||||
|
||||
class Event(object):
|
||||
def __init__(self):
|
||||
self.processed = False
|
||||
|
||||
@events.subscribe(Event):
|
||||
def handle(event):
|
||||
event.processed = True
|
||||
|
||||
e = Event()
|
||||
assert not e.processed
|
||||
events.fire(e)
|
||||
assert e.processed
|
||||
|
||||
As you can working with manager directly isn't more difficult than working with
|
||||
global event management API (actually global API implemented as global event
|
||||
manager object sitting inside of ``generic.event`` module).
|
||||
|
||||
You can create as many event managers as you want -- one per thread and store it
|
||||
in thread-local container or just one per application instance and so on.
|
||||
|
||||
Subclassing events
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Case study: processing bank accounts
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Thread safety
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
What about threadsafety? There're two aspects to discuss here.
|
||||
|
||||
Registry
|
||||
--------
|
||||
|
||||
Type axis
|
||||
~~~~~~~~~
|
||||
|
||||
Implementing custom axis
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Case study: HTTP request dispatching
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Thread safety
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
API reference
|
||||
-------------
|
||||
|
||||
Indices and tables
|
||||
------------------
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user