Package: withref
withrefmakes Python’swithstatement able to simplify complex
dereferences. This is what I initially, naively thought the statement was
for, similar to thewithstatement of half-remembered Pascal.The typical Python use is a more complex guard over the entry and exit
points for using an object. See e.g.this effbot article, Butwithrefmakes
the simpler “just give me the dereferenced object, please” use case work as
well.Usagefrom __future__ import print_function # Python 2/3 compatibility
from withref import ref
from stuf import stuf

# stuf used for demonstration purposes only. Convenient way to
# make dict entries accessible as dot-deferenceable properties.

a = stuf({ 'b': { 'c': { 'c1': 1 }, 'd': 44.1 } })

with ref(a.b.c) as c:
    c.c1 = 99

print(a)
a_ideal = stuf({'b': {'c': {'c1': 99}, 'd': 44.1}})
assert a == a_idealIt works with array-style references too, of course:with ref(a['b']['c']) as cc:
    c['c1'] = 99

assert a == a_idealBut beware OVER-dereferencing! While this works correctly:with ref(a.b.c.c1) as c1:
    print(c1)
    assert c1 == 99This does NOT:with ref(a.b.c.c1) as c1:
    c1 = 12345
    assert c1 == 12345

assert a.b.c.c1 == 99   # with heavy heart
                        # because we just ostensibly set it to 12345Unlike Pascal,refis not built into the language proper. And unlike Perl,
Python is less eager to providelvaluesfor every
mention of a variable or value. As a result,refcan provide thevalueof the full-deferencedc1, but not the assignablelvalue.Like Pascal’swith,refremoves N-1 layers of the enclosing structure
for values, but only N-2 for assignment. Still, in complex multi-layer
structures, this can be a nice simplification:with ref(app.config.server.wsgi) as wsgi:
    wsgi.logger = some_logger
    wsgi.debug_level = 4
    wsgi.port = 8080arguably beats:app.config.server.wsgi.logger = some_logger
app.config.server.wsgi.debug_level = 4
app.config.server.wsgi.port = 8080for simplicity and clarity.I see a lot of configuration code that constantly repeats the same long
multi-level dereferences. That style is repetitive (anti-DRY) and tends to
left-align code blocks, both of which impede program comprehension. Using thewithstatement is a neat way to simplify and at the same time add a little extra
visual structure.And while assignment (“lvalue production”) isn’t always possible, there still are
some interesting tricks possible with simple value production:with ref("this is a string"[0:4]) as t:
    print(t)AlternativeWhileyadda.yadda.yaddareferencing is all too common, one can also
use a more proximate variable assignment:wsgi = app.config.server.wsgi
wsgi.logger = some_logger
wsgi.debug_level = 4
wsgi.port = 8080This lacks the more indented structure of thewithrefapproach, but
is still much preferable to what you often find in the field.See AlsoThewithhacksmodule, which includes
many other fun hacks (multi-line lambdas, new looping structures, etc.)–but
also requires thebyteplaymodule which
actively introspects & munges Python bytecode (“Danger, Will Robinson!
Danger!”), and has not been updated to work beyond Python 2.6. So it’s out of
currency with all but the very tail-end of modern Python.Possible Future ExtensionAswithhacksshows, using introspection we could determine the lvalue of
the calling object even in the edge-case where it is a leaf-node of its
enclosing structure. This would not require any bytecode changes, and should
be compatible with modern versions of Python (e.g. 2.7] and 3.x).Whether that trick can be done simply, portably, rock-solid reliably, and
transparently enough to satisfy those who code the modules that tend to most
need this kind of dereferencing simplification–i.e. complex modules often
used in production settings, into which they are understandably loathe to
introduce any possible sources of error or any performance impedance–that
is the key open question.NotesAutomated multi-version testing managed withpytest,pytest-cov,coverageandtox.
Packaging linting withpyroma.Successfully packaged for, and
tested against, all late-model versions of Python: 2.6, 2.7, 3.3,
3.4, 3.5, 3.6, as well as recent PyPy and PyPy3 builds.The author,Jonathan Euniceor@jeunice on Twitterwelcomes your comments and suggestions.InstallationTo install or upgrade to the latest version:pip install -U withrefYou may need to prefix these withsudoto authorize
installation. In environments without super-user privileges, you may want to
usepip’s--useroption, to install only for a single user, rather
than system-wide. Depending on your local system configuration, you
may also need version-specific installers such aspip2andpip3.