Skip to main content

En passant assignment for clearer conditionals

Project description

| |version| |downloads| |supported-versions| |supported-implementations| |wheel|

.. |version| image:: http://img.shields.io/pypi/v/enpassant.svg?style=flat
:alt: PyPI Package latest release
:target: https://pypi.python.org/pypi/enpassant

.. |downloads| image:: http://img.shields.io/pypi/dm/enpassant.svg?style=flat
:alt: PyPI Package monthly downloads
:target: https://pypi.python.org/pypi/enpassant

.. |wheel| image:: https://pypip.in/wheel/enpassant/badge.png?style=flat
:alt: PyPI Wheel
:target: https://pypi.python.org/pypi/enpassant

.. |supported-versions| image:: https://pypip.in/py_versions/enpassant/badge.png?style=flat
:alt: Supported versions
:target: https://pypi.python.org/pypi/enpassant

.. |supported-implementations| image:: https://pypip.in/implementation/enpassant/badge.png?style=flat
:alt: Supported implementations
:target: https://pypi.python.org/pypi/enpassant

.. |wheel| image:: https://img.shields.io/pypi/wheel/enpassant.svg
:alt: Wheel packaging support
:target: https://pypi.python.org/pypi/enpassant

Simple *en passant* assignment, giving Python clearer conditional statements

Usage
=====

::

from enpassant import *
result = Passer()

while result / expensive_request():
print result.report()

Discussion
==========

Many languages support *en passant* (in passing) assignment, like so::

if result = expensive_request():
print result.report()

Python does not. This leads to more code lines and, in some cases,
less visual
clarity::

result = expensive_request()
if result:
print result.report()

Or worse, in the case of looping structures::

result = expensive_request()
while result:
print result.report()
result = expensive_request()

It doesn't look so bad here, in a highly distilled example. But in real
programs, the called function often has parameters to be managed, and the
surrounding code is invariably longer and more complicated.
The more
complicated the surrounding computations and requests, the simpler the
comparison itself should be. As the `Zen of Python
<http://www.python.org/dev/peps/pep-0020/>`_ intones: "Simple is better than
complex." and "Readability counts."

I hope that Python
will eventually provide a concise way of handling this, such as::

while expensive_request() as result:
print result.report()

But in the meanwhile, ``enpassant`` provides a workaround.

How it Works
============

::

from enpassant import *
result = Passer()

while result / expensive_request():
print result.report()

``result`` is merely a proxy object that, when it encounters the division
operator, returns the denominator. That is, ``result / whatever ==
whatever``. But it also *remembers* the denominator value. Then, whenever
you want the result value provided (presumably, later in the body of your
loop or conditional), simply access it through ``result``. If you want the
full object returned by ``expensive_request()`` you can get it via
``result.value``. Or or the result has items or attributes, they are
available by indexing or naming the attribute directly. *Easy peasy!*

NB: If you change the items or attributes of ``result``, those settings are
also forwarded to the underlying object. ``result`` is not a copy, but a
true proxy, and as close to the actual object returned as I can make it
given current Python strictures.

Some Details
============

``enpassant`` "assignment" is transparent to conditional expressions,
because the value of the expression is always the value of the denominator.
But ``Passers`` are also guaranteed to have a Boolean value identical to
that of the value they contain, should you wish to use them in subsequent
tests.

The ``result`` in the example above isn't the pure result of the following
function call (or expression), but rather a proxy to it. While item (``[]``)
and attribute (``.``) access work directly on ``result``, this is because
``Passer`` objects pass on *getitem* and *get-attribute* requests to their
enclosed value. Usually, this is a convenience, and avoids having to
needlessly state that it's really ``result.value`` that's being indexed or
dereferenced. But if you need the specific object returned (say for an
object identity or ``isinstance`` test, use ``result.value`` directly.

Alternative Value Access
========================

It is also possible to retrieve the value of a ``Passer`` by calling it::

if result / expensive_request():
print result().report()

This technique makes clear that the value is being rendered via some
process, rather than just presented as a normal Python name / variable. And
the resulting object from ``result()`` is the true and complete result of
the earlier function call, with no need for implicit / auto-magical
forwarding of items and attributes. Which style makes sense is a matter of
judgment and taste.

Or, if you prefer something terser, the ``+`` (unary positive) operation
will also yield the value::

if result / expensive_request():
print +result.report()

Alternative Invocations
=======================

.. |larrow| unicode:: 0x2190 .. leftwards arrow

If you prefer the less-than (``<``) or less-than-or-equal (``<=``) operators
as indicators that ``result`` takes the value of the following value, they
are supported as aliases of the division operation (``/``). Thus, the
following are identical::

if result / expensive_request():
print result.report()

if result < expensive_request():
print result.report()

if result <= expensive_request():
print result.report()

It's a matter of preference which seems most logical, appropriate, and
expressive.
None of them are as good
Note, however, that the operation usually known as division
(``/``) has a much higher precedence (i.e. tighter binding to its operands)
than the typical comparison operations (``<`` and ``<=``). If used with a
more complex expressions, either know your precedence or use parenthesis to
disambiguate!

It'd be swell if Python supported arbitrary symbols. Unicode has what would
be reasonable alternative assignment markers, such as |larrow| (`LEFTARDS
ARROW <http://www.fileformat.info/info/unicode/char/2190/index.htm>`_), but
alas! Until Python gets more Unicode-savvy, we have to choose some existing
ASCII operator to repurpose.

It is also possible to use a function call idiom if you prefer::

if result(expensive_request()):
print result.report()

This has the virtue of looking like a "wrapping" of the expensive
request value, rather than reusing / overloading an existing operation.

Grabber and Similar
===================

I've begun experimenting with other forms of collecting and rendering values.
This version of ``enpassant`` includes the results of one of those experiments.
Objects of the ``Grabber`` class can have their attributes set on their first
access. Subsequent uses of that attribute yield the set value.::

info = Grabber()
info.name('Joe')
assert info.name == 'Joe'

The challenge with this approach is that once set, attribute values cannot be
reset.

Notes
=====

* Version 0.5 bumps to beta status. Adds wheel packaging
and updates setup, versioning, and code packaging.
Shifts to Apache Software License. Updates testing matrix.

* `En passant <http://en.wikipedia.org/wiki/En_passant>`_ is a chess
term.

* En passant assignment / naming is discussed in
`Issue1714448 <http://bugs.python.org/issue1714448>`_
and `PEP 379 <http://www.python.org/dev/peps/pep-0379/>`_, which have
been rejected and withdrawn, respectively. But that is years gone
by. I hope the idea will be productively reconsidered in the future.

* Automated multi-version testing managed with `pytest
<http://pypi.python.org/pypi/pytest>`_ and `tox
<http://pypi.python.org/pypi/tox>`_.
Packaging linting with `pyroma <https://pypi.python.org/pypi/pyroma>`_.

Successfully packaged for, and
tested against, all late-model versions of Python: 2.6, 2.7, 3.2, 3.3,
3.4, and 3.5 pre-release (3.5.0b3) as well as PyPy 2.6.0 (based on
2.7.9) and PyPy3 2.4.0 (based on 3.2.5).

* On Python 2.6, uses Raymond Hettinger's `ordereddict <https://pypi.python.org/pypi/ordereddict>`_
module (which is included in the source tree for ease of installation)
to provide ``OrderedDict``. Thank you, Raymond!

* The `simplere <http://pypi.python.org/pypi/simplere>`_
package similarly provides
en passant handling (and other helpers) for the important,
common case of regular expression
searches.

* The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
`@jeunice on Twitter <http://twitter.com/jeunice>`_
welcomes your comments and suggestions.

Installation
============

To install or upgrade to the latest version::

pip install -U enpassant

To ``easy_install`` under a specific Python version (3.3 in this example)::

python3.3 -m easy_install --upgrade enpassant

(You may need to prefix these with ``sudo`` to authorize
installation. In environments without super-user privileges, you may want to
use ``pip``'s ``--user`` option, to install only for a single user, rather
than system-wide.)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

enpassant-0.5.0.zip (19.0 kB view details)

Uploaded Source

enpassant-0.5.0.tar.gz (9.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

enpassant-0.5.0-py2.py3-none-any.whl (12.8 kB view details)

Uploaded Python 2Python 3

File details

Details for the file enpassant-0.5.0.zip.

File metadata

  • Download URL: enpassant-0.5.0.zip
  • Upload date:
  • Size: 19.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for enpassant-0.5.0.zip
Algorithm Hash digest
SHA256 577d67bf20221174e8ef0c984272ac30014ba9e2b03ebd707b2a8248190df3aa
MD5 b28dca3b078ab920988936a49c7d47f6
BLAKE2b-256 b224bd2860e9cf96aa776c18c4016d0393eba1d873af3eaa582a2f5fcc0ccb2e

See more details on using hashes here.

File details

Details for the file enpassant-0.5.0.tar.gz.

File metadata

  • Download URL: enpassant-0.5.0.tar.gz
  • Upload date:
  • Size: 9.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for enpassant-0.5.0.tar.gz
Algorithm Hash digest
SHA256 6285b21dae13483bbb6c47d94dd03a2f52091a7bb2dbb82cc12ed7b567b18359
MD5 6408f057a152679ee1a65a850efa8637
BLAKE2b-256 745de307f3af483f716348625694947db1a919efa418d8f3372280e1d9e3dd83

See more details on using hashes here.

File details

Details for the file enpassant-0.5.0-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for enpassant-0.5.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 83d64089823e152d1939f6c4fe0d19d20bc3fc69e9f96ea14f843c8ea7c2caf0
MD5 bfe3490b42ab24dc9a2172220f9410e0
BLAKE2b-256 0b3fa6b8e579f1b414ea3813db885233269cb7803074bf06d8840ab4cd98e8cc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page