parameterize

a Python implementation of parameterize and SRFI 39


Keywords
parameters, parameterize, dynamic, scope
License
MIT
Install
pip install parameterize==0.2

Documentation

parameterize

a Python implementation of SRFI 39

Build Status

parameterize implements optional dynamic scope and parameter objects in Python. It's a spiritual implementation of parameters from Scheme, as outlined in SRFI 39. If you know what that means, awesome! If not, read on.

Requirements

parameterize should work, by itself, on Python 2.7 or later (including Python 3).

Parameter Objects

Basically, parameter objects hold a single value. They are meant to be declared as global variables, so that the values they contain can be accessed anywhere. However, parameter objects have a neat trick: they have a context manager, parameterize(), that lets you override the parameter's value for a single piece of code. Any changes to the parameter that happen within this code block can't escape it, and can't affect other threads of execution.

So, parameters are sort of halfway between global variables and local variables. They're accessable from anywhere, and modifiable from anywhere, but modifications are always contained completely within their parameterize() block.

Motivating Example

In Python, the sys.stdout global variable can be overridden to point to any file-like object to redirect output from built-in output primitives like print(). So, there's a nice hacky way to redirect the output of a function:

with open('output.txt', 'w') as f:
    oldstdout = sys.stdout
    sys.stdout = f
	something_that_prints_a_lot()
	sys.stdout = oldstdout

This is definitely a hack, though. It can be made a little bit nicer by wrapping the replacement code into a context manager, but it's hard to write a context manager that works for any variable like this (what if we wanted to redirect sys.stderr instead?). Also, this has issues with threading: if another thread tries to write to stdout while something_that_prints_a_lot() is running, it'll end up in output.txt.

parameterize and Parameter objects solve all these little problems for you. All we need to do is create an stdout parameter (with the initial value of sys.stdout), and replace sys.stdout with a proxy object that always points to the value of this parameter:

import parameterize
import sys

# create our parameter and proxy object
stdoutp = parameterize.Parameter(sys.stdout)
sys.stdout = stdoutp.proxy()

print('before')
with open('output.txt', 'w') as f:
    with stdoutp.parameterize(f):
	    print('inside')
print('after')

This code will print "before" and "after" to the console, and "inside" to output.txt. The mutation of sys.stdout only appears within the with stdoutp.parameterize block, and plays nicely with other threads.

This sort of pattern (where there's some global context that affects possibly deeply-nested functions, without passing this context through the call chain) is also very common in web frameworks, where code handling a response needs access to the request data, but may not have been passed it directly. Werkzeug calls them context locals, and Flask calls it an application context. parameterize is a stand-alone way to do this outside of these frameworks.

Incidentally, the above example about replacing sys.stdout with a parameter is exactly how R7RS Scheme handles IO redirection.

API

parameterize is a small module. To create a parameter object, use Parameter(default, converter). default sets the initial value, and defaults to None when not provided. converter is a function that is called whenever the parameter is set, and should return a validated or converted value. If converter is not supplied, it defaults to doing nothing.

Parameter objects have a get() method, and a set(v) method, to get and set the value contained by the object. As a convenience, if you have a parameter object p, p() is the same as p.get(), and p(v) is the same as p.set(v).

Parameter objects also have parameterize(v), a context manager that sets the parameter to v within its block, and prevents all changes to the parameter within that block from escaping. This is the big deal about parameterize, and what seperates parameters from normal global variables.

If the parameter value is an object, you can call proxy() to get an object that will act like the object contained within the parameter, and track any changes made to that value. This proxying mechanism is a work in progress, and is probably more magic than you need. Still, it is useful sometimes. Patches very welcome.

License

parameterize is distributed under the MIT License. See LICENSE.txt for details.