Source

glue.py /

Filename Size Date modified Message
104 B
108 B
1.3 KB
6.4 KB
48.5 KB
983 B

glue.py is a single lightweight utility module expanding Python with new powerful idioms for accessors, callsets, weak methods, enums, singletons and observable primitives that can easily be integrated with existing projects. It is particularly useful for writing model/view oriented applications.

Installation

You'll require Python 2.7, Python 3+ or PyPy 1.9 to use glue.py. Earlier versions are not supported.

glue.py can easily be installed from the Python package repository using distutils or pip:

pip install glue.py

If you plan to make amendments to the codebase, or you're interested in the latest development release, it is best to check out the source tree and install glue.py in development mode:

python setup.py develop

Usage

glue.py provides a larger set of interoperating utility classes and functions, of which only the most important ones will be described here in detail. It's not a bad idea to just browse through the source and look what else is there besides what's covered in this introduction.

Generated Accessors

While Python supports accessors through properties, providing regulated access to class variables has been traditionally cumbersome and repetitive, even though the syntax has somewhat improved with recent versions. Consider this classical approach to property access:

from types import NoneType

class Sponge(object):
    pass

class Pineapple(object):
    on_owner_changed = None

    def __init__(self):
        self.__owner = None

    def get_owner(self):
        return self.__owner
    def set_owner(self, value):
        if value == self.__owner:
            return
        assert isinstance(value, (Sponge, NoneType))
        self.__owner = value
        if self.on_owner_changed:
            self.on_owner_changed(self)
    owner = property(get_owner, set_owner, 
        doc = "the owner of this Pineapple")

apart from preventing a rewrite, the setter also provides an optional hook that can be triggered when the value changes. Notice how many times the word "owner" has to be repeated in one form or the other in order to expose this relatively simple attribute. Until Python 2.7, most people adapted this handy recipe to cut down on repetition:

     def owner():
        doc = """the owner of this pineapple"""
        def fget(self):
            return self.__owner
        def fset(self, value):
            if value == self.__owner:
                return
            assert isinstance(value, (Sponge, NoneType))
            self.__owner = value
            if self.on_owner_changed:
                self.on_owner_changed(self)
        return property(**locals())
    owner = owner()

That's slightly better, but not perfect, and also a little convoluted. These days, this approach is the status quo:

    @property
    def owner(self):
        """The owner of this pineapple"""
        return self.__owner
    @owner.setter
    def owner(self, value):
        if value == self.__owner:
            return
        assert isinstance(value, (Sponge, NoneType))
        self.__owner = value
        if self.on_owner_changed:
            self.on_owner_changed(self)

it seems like we're mostly shuffling details around... all three solutions vary around 12 lines, and with a bunch of these, classes may seem a lot larger than they are. This is how glue.py does it:

from glue import (lazydecls, defproperty, autoinit, callset)
from types import NoneType

class Sponge(object):
    pass

@lazydecls
class Pineapple(object):
    on_owner_changed = callset()

    owner = defproperty(
        default = None, types=(Sponge, NoneType), hook = on_owner_changed, 
        doc = "The owner of this pineapple")

    def __init__(self):
        autoinit()

...case closed. For more information, try help(glue.defproperty).

Callable Sets

You may have noticed that the Pineapple class exposes the on_owner_changed callback as a callset, which is just a short way of instantiating a CallableSet. Callable sets are a fast way to create observables:

from glue import callset

def func1(value): 
    print("func1 called with value",value)
    return value+10

def func2(value): 
    print("func2 called with value",value)
    return value+20

def func3(value): 
    print("func3 called with value",value)
    return value+30

Trying this on the command line:

>>> funcs = callset([func1,func2,func3])
>>> print(max(funcs(10)))
func1 called with value 10
func2 called with value 10
func3 called with value 10
40

In the pineapple example, this is how a view would typically subscribe to events sent by the Pineapple class:

@lazydecls
class Squid(object):
    neighbor_house = defproperty()
    annoying_coworkers = defproperty(default = set)

    def __init__(self, **kwargs):
        # auto-assign matching keyword args
        autoinit(**kwargs)
        # track if the owner of the neighbor house changes by registering
        # Squid's callback. The callback will be weakly proxied, and
        # automatically unregisters when this Squid instance is no longer
        # referenced. 
        self.neighbor_house.on_owner_changed.addweak(self.someone_moved_in)

    def someone_moved_in(self, event):
        # since the on_owner_changed callset is global to Pineapple, Squid
        # would be notified of any owner changes in Pineapples, so filter
        # for the right one.
        if not (event.instance is self.neighbor_house):
            return
        # check if the new tenant is one of our annoying coworkers
        if event.value in self.annoying_coworkers:
            # state opinion on the situation
            print("Meh.")

Trying this on the command line:

>>> house = Pineapple()
>>> bob = Sponge()
>>> squidward = Squid(neighbor_house = house)
>>> squidward.annoying_coworkers.add(bob)
>>> house.owner = bob
Meh.

And that's the gist of it.

Weak Callable Proxies

TODO

Observable Dicts, Lists and Sets

TODO

Enums

TODO

Singletons

TODO