{ "info": { "author": "Danilo J. S. Bellini", "author_email": "danilo.bellini.gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Intended Audience :: Other Audience", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Topic :: Adaptive Technologies", "Topic :: Software Development", "Topic :: Software Development :: Assemblers", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Disassemblers", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "PyScanPrev\n==========\n\n.. image::\n https://img.shields.io/travis/danilobellini/pyscanprev/master.svg\n :target: https://travis-ci.org/danilobellini/pyscanprev\n :alt: Travis CI builds\n\n.. image::\n https://img.shields.io/coveralls/danilobellini/pyscanprev/master.svg\n :target: https://coveralls.io/r/danilobellini/pyscanprev\n :alt: Coveralls coverage report\n\n\nWould you like to replace this:\n\n.. code-block:: python\n\n reduce(lambda prev, x: abs(prev - x), [2, 3, 4, 5])\n\nby this?\n\n.. code-block:: python\n\n last(abs(prev - x) for x in [2, 3, 4, 5])\n\nWhy not?\n\n.. code-block:: python\n\n >>> from functools import reduce\n >>> from pyscanprev import enable_scan, last\n >>> @enable_scan(\"prev\")\n ... def evaluate(data):\n ... return reduce(lambda prev, x: abs(prev - x), data), \\\n ... last(abs(prev - x) for x in data)\n >>> evaluate([2, 3, 4, 5])\n (2, 2)\n\n\nExamples\n--------\n\n.. list-table::\n :header-rows: 1\n\n * - Example\n - Description\n\n * - `Comparison`_\n - Simple scan/accumulate and fold/reduce examples using the\n PyScanPrev resources, the Python standard library and\n the 3-for-section comprehensions for comparing readability\n and expressiveness.\n\n * - `Conditional Toggling`_\n - Sometimes feedback isn't really required, but you should at\n least store some state about what's going on in the input.\n That's the case in this example, which toggles/updates the\n state only for certain inputs, aLtErNaTiNg CaSeS, iGnOrInG oThEr\n ChArAcTeRs. Historically, PyScanPrev was created after thinking\n on how would be the simplest way to solve the problem described\n in this example.\n\n * - `Examples from the itertools.accumulate documentation`_\n - This is a copy and an adaptation of the ``itertools.accumulate``\n examples in the Python documentation to use the PyScanPrev\n comprehensions instead of that standard library function. The\n examples include:\n\n - running product\n - running maximum\n - amortization tables\n - chaotic logistic map\n\n * - `Factorial and Multiplication`_\n - Fold/reduce factorial and multiplication / product of a sequence\n implementation using PyScanPrev.\n\n * - `Fibonacci`_\n - Fibonacci sequence with PyScanPrev.\n\n * - `Gray Code`_\n - Generating Gray codes using the definition is slower than using\n bitwise operations, but the recursive definition can be written\n as a scan/fold expression and is useful for testing, as this\n example shows.\n\n * - `Single pole lowpass IIR Filter`_\n - DSP (Digital Signal Processing) applications ofter requires\n feedback, i.e., accessing some previous output value in a\n process. PyScanPrev can be used to write simple signal\n processing models like IIR (Infinite Impuse Response) linear\n filters. This is an example with a single pole lowpass digital\n filter. For testing and displaying the results, this example\n uses `AudioLazy`_ and `hipsterplot`_\\ .\n\n * - `State-space model`_\n - Simulation of linear discrete time modeling in control\n engineering. The example gives an implementation of both time\n varying and time invariant state-space models. These models are\n then used to simulate:\n\n - an accumulator\n - a LTI (Linear Time Invariant) continuous time\n mass-spring-damper SHM (Simple Harmonic Motion) model\n - a linear time varying \"leaking bucket\"-spring-damper model\n - another LTI IIR filter, designed from the difference equation\n and simulated as a state-space model, compared with the\n `AudioLazy`_ results\n\n The discretization process is included in the example, and the\n simulations use `hipsterplot`_\\ to plot the motion\n path/trajectory. This example includes an explanation/proof to\n the conversion from difference equations to a state-space model\n (via Z Transform).\n\n.. _`Comparison`:\n https://github.com/danilobellini/pyscanprev/blob/v0.1.0/examples/comparison.rst\n.. _`Conditional Toggling`:\n https://github.com/danilobellini/pyscanprev/blob/v0.1.0/examples/conditional-toggling.rst\n.. _`Examples from the itertools.accumulate documentation`:\n https://github.com/danilobellini/pyscanprev/blob/v0.1.0/examples/itertools-accumulate-docs.rst\n.. _`Factorial and Multiplication`:\n https://github.com/danilobellini/pyscanprev/blob/v0.1.0/examples/factorial-prod.rst\n.. _`Fibonacci`:\n https://github.com/danilobellini/pyscanprev/blob/v0.1.0/examples/fibonacci.rst\n.. _`Gray Code`:\n https://github.com/danilobellini/pyscanprev/blob/v0.1.0/examples/gray.rst\n.. _`Single pole lowpass IIR Filter`:\n https://github.com/danilobellini/pyscanprev/blob/v0.1.0/examples/iir-filter.rst\n.. _`State-space model`:\n https://github.com/danilobellini/pyscanprev/blob/v0.1.0/examples/state-space.rst\n\n.. _`AudioLazy`: https://github.com/danilobellini/audiolazy\n.. _`hipsterplot`: https://github.com/imh/hipsterplot\n\n\nWhy \"Scan\"?\n-----------\n\n`Scan`_ is a high order function that can be seen as something between\nthe map and the reduce (fold) functions, returning all steps of a\nfold. Since Python 3.3, it's available in the function\n`itertools.accumulate`_\\ .\n\nThe goal here is to find an easy way to read the previous value in a\ngenerator expression and anything alike, so that the scan/fold\n(accumulate/reduce) code can be written using them.\n`Readability counts`_\\ !\n\n.. _`Scan`:\n https://en.wikipedia.org/wiki/Prefix_sum#Scan_higher_order_function\n.. _`itertools.accumulate`:\n https://docs.python.org/library/itertools.html#itertools.accumulate\n.. _`Readability counts`:\n https://www.python.org/dev/peps/pep-0020\n\n\nPackage contents\n----------------\n\n.. list-table::\n\n * -\n .. code-block:: python\n\n enable_scan(name)\n\n Decorator that allows functions to have generator expressions\n and list/set comprehensions with a variable (the one with the\n given name) in its body for accessing the previous resulting\n value.\n\n * -\n .. code-block:: python\n\n last(iterable)\n\n Gets the last value from an iterable, making it straightforward\n to write a reduce/fold from the scan result.\n\n * -\n .. code-block:: python\n\n prepend(value, iterable)\n\n A version of ``[value] + some_list`` for general iterables,\n returning a generator. This function was created to allow\n PyScanPrev-enabled generator expressions and list/set\n comprehensions to include an explicit start value, but it can be\n used to prepend a value in any context, e.g. to force a start\n value on ``itertools.accumulate``.\n\n * -\n .. code-block:: python\n\n scan(func, iterable, [start], *, echo_start=True)\n\n It's an implementation of the scan higher order function with\n more features than ``itertools.accumulate`` (the ``start`` and\n the keyword-only ``echo_start`` parameters) and consistent to\n the ``functools.reduce`` function signature.\n\n\nTell me, how is that possible at all?\n-------------------------------------\n\nMagic! Some people say that's bytecode manipulation, but isn't that all the\nsame?\n\n\nInstalling\n----------\n\nYou can either use pip:\n\n.. code-block:: shell\n\n # From PyPI\n pip install pyscanprev\n\n # From GitHub master branch\n pip install --upgrade git+https://github.com/danilobellini/pyscanprev\n\nOr setup.py directly::\n\n python3 setup.py install\n\nThis software depends on `bytecode`_\\ , which requires Python 3.4+.\n\n.. _`bytecode`:\n https://pypi.python.org/pypi/bytecode\n\n\nThe world without this package (rationale)\n------------------------------------------\n\nIt's not usual nor widely known that the cross/cartesian product applied on\nmultiple \"for\" sections in a generator expression or a list/set/dict\ncomprehension allows more than one section to have the same target variable\nname. But that provides the means to do something akin to a scan, for example\nthis cumulative sum (Tested in Python 2.6+ and 3.2+):\n\n.. code-block:: python\n\n >>> [prev for prev in [0] for el in range(5) for prev in [prev + el]]\n [0, 1, 3, 6, 10]\n\nWhose parts are:\n\n.. code-block:: python\n\n [prev for prev in [start]\n for target in iterable\n for prev in [func(prev, target)]]\n\nBut that's a kludge, it's hard to grasp, hard to change/update/maintain,\nfixed/locked in that \"for\" section order, and its behavior has some minor\ndetails whose control would need to be external (e.g. using the first value\nfrom the ``iterable`` as the ``start``). The ``prev`` variable appears at\nleast 4x in such structure and twice as a target. The first ``prev`` value is\n``start``, which is just seen/used by the last \"for\" section in its first\n``func`` call, whose result is assigned to ``prev`` before the whole list\ncomprehension appends/\"yields\" any output/result, since it's also the target\nvariable name in that \"for\" section. So ``start`` is never an output,\nalthough everything starts with ``prev for prev in [start]``.\n\nIt's not only about code aesthetics or readability, but also about\npattern memorization: knowledge about the scan abstraction\nand about the Python language is probably\nnot enough for one to remember that structure.\n\nAs ``func`` in the previous example was essentially ``operator.add``, let's do\nthe same cumulative sum with ``itertools.accumulate`` (Python 3.2+):\n\n.. code-block:: python\n\n >>> from itertools import accumulate\n >>> list(accumulate(range(5)))\n [0, 1, 3, 6, 10]\n\nIt seems the same, but here the first zero output is the\n``next(iter(range(5)))``,\nnot the result of a sum or any other ``func`` for that matter (i.e., it\ndoesn't depend on ``func`` at all). To be really equivalent to the\n3-for-sections list comprehension above, it would need to be something like:\n\n.. code-block:: python\n\n >>> list(accumulate([0, 0, 1, 2, 3, 4]))[1:]\n [0, 1, 3, 6, 10]\n\nWe had to prepend ``0`` to ``range(5)``. What's going on here is that\n``accumulate`` returns a generator that yields the values::\n\n [i0, i0+i1, i0+i1+i2, i0+i1+i2+i3, i0+i1+i2+i3+i4, ...]\n\nWhere \"i\\ :sub:`n`\" is the n-th value from the ``iterable`` input.\nEvery step obviously re-uses the previous step result instead\nof summing all the previous inputs again,\nand that's what the scan is all about. On the other hand, the 3-for-sections\nlist comprehension does this when ``func`` is the sum/add::\n\n [s+i0, s+i0+i1, s+i0+i1+i2, s+i0+i1+i2+i3, s+i0+i1+i2+i3+i4, ...]\n\nWhere \"s\" is the ``start``. Since Python 3.3, itertools.accumulate has an\noptional second parameter, which should be a binary\noperator/function/callable. For a given ``func``, the resulting generator\nwould yield, in order:\n\n.. code-block:: python\n\n next(iterable), # result[0]\n func(result[0], next(iterable)), # result[1]\n func(result[1], next(iterable)), # result[2]\n func(result[2], next(iterable)), # result[3]\n ...\n\nWhere ``start`` is implicit as the first value from ``iterable``, and\n``result`` is that output iterable itself seen as a sequence. To grasp the\ndifference, let's see a cumulative sum of squares starting with 3 in the\naccumulator/register.\n\n.. code-block:: python\n\n >>> list(accumulate([3, 5, 1, 1, 2], lambda x, y: x + y ** 2))\n [3, 28, 29, 30, 34]\n\nTo get the same result with a list comprehension, one would do:\n\n.. code-block:: python\n\n >>> [3] + [x for x in [3]\n ... for y in [5, 1, 1, 2]\n ... for x in [x + y ** 2]]\n [3, 28, 29, 30, 34]\n\nThere's also a really old package in PyPI called functional_\\ ,\nwhose last update was in 2006. Besides the distinction between\nnon-strict and \"prime\"/strict counterparts, it mimics all the\n`4 scan and 4 fold Haskell functions`_\\ , including their names\nand their parameter order. The ``functional.scanl1`` and\nthe ``itertools.accumulate`` functions are almost the\nsame, the difference is that ``scanl1`` needs the function to be the first\nargument and it isn't optional. On the other hand, ``functional.scanl`` needs\nan extra \"start\" parameter. Both functions return a generator:\n\n.. code-block:: python\n\n >>> import functional, operator\n\n >>> # scanl (+) 0 [0..4]\n >>> list(functional.scanl(operator.add, 0, range(5)))\n [0, 0, 1, 3, 6, 10]\n\n >>> # scanl1 (+) [0..4]\n >>> list(functional.scanl1(operator.add, range(5)))\n [0, 1, 3, 6, 10]\n\n >>> # scanl1 (\\x y -> x + y^2) [3, 5, 1, 1, 2]\n >>> list(functional.scanl1(lambda x, y: x + y ** 2, [3, 5, 1, 1, 2]))\n [3, 28, 29, 30, 34]\n\nBoth ``scanl`` and ``scanl1`` have a behavior different from that\n3-for-sections list comprehension.\n\nPython ``functools.reduce``, ``functional.foldl`` and\n``functional.foldl1``, as fold/reduce implementations,\nshare a core idea: they return the last value of the scan\nresulting from the same given inputs to ``functional.scanl`` and\n``functional.scanl1``. The ``reduce`` function can have an optional ``start``\nas the 3rd and last argument, which gives to it both the behavior of both\n``foldl``, that requires the ``start`` as the 2nd parameter, and ``foldl1``,\nwhich uses the first iterable value as the start value. If there's a way to\nmodify generator expressions so that ``scanl/scanl1/accumulate`` can be\nimplemented with them with a good readability, the same would apply to reduce.\n\nBut, even for developers who like to think on these concepts as ready to use\nabstractions stored in first class objects, here we got a parameter hell!\nTheir order is a mess:\n\n* (iterable, func) -> ``itertools.accumulate``\n* (func, start, iterable) -> ``functional.scanl``\n* (func, iterable) -> ``functional.scanl1``, ``map``, ``filter``\n* (func, iterable, [start]) -> ``functools.reduce``\n\nThe higher-order functions scan and fold appears respectively in\n``itertools.accumulate`` and ``functools.reduce`` first-class objects\n(functions are first-class objects in Python), which are quite easy for people\ncoming from a functional programming background to grasp, and far easier to\nread/remember than the 3-for-sections list comprehension. One just neet to\nknow these two have their 2 parameters reversed, and that accumulate doesn't\nhave an optional external start value. It would be great to have an optional\nstart parameter on ``itertools.accumulate``, as well as a function signature\nstandardization, but the main purpose of this is just to get a cleaner\nalternative to that 3-for-sections list comprehension.\n\n.. _`functional`:\n https://pypi.python.org/pypi/bytecode\n.. _`4 scan and 4 fold Haskell functions`:\n https://hackage.haskell.org/package/base/docs/Data-List.html\n\n----\n\nCopyright (C) 2016 Danilo de Jesus da Silva Bellini", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/danilobellini/pyscanprev", "keywords": null, "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "pyscanprev", "package_url": "https://pypi.org/project/pyscanprev/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/pyscanprev/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/danilobellini/pyscanprev" }, "release_url": "https://pypi.org/project/pyscanprev/0.1.0/", "requires_dist": null, "requires_python": null, "summary": "Scan and reduce/fold as generator expressions and list/set comprehensions that can access the previous iteration output.", "version": "0.1.0" }, "last_serial": 2418217, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "82368001e32dc0945481916a74e1ce7f", "sha256": "6ca5dd1167239729275c20cc183f42f6d0423654ea61879ad46d704e957cc600" }, "downloads": -1, "filename": "pyscanprev-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "82368001e32dc0945481916a74e1ce7f", "packagetype": "bdist_wheel", "python_version": "3.5", "requires_python": null, "size": 14957, "upload_time": "2016-10-23T13:05:01", "url": "https://files.pythonhosted.org/packages/92/94/28a13ede2df4665ad3882fbd8ed82afdafb9ebc8b4b99d7cbb6d852fc7a3/pyscanprev-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4d69fb369e06da6feac58c560ac82f42", "sha256": "96e366a43c33801f4641a8a2b640da700992369304b7e12915981fd66ca45a33" }, "downloads": -1, "filename": "pyscanprev-0.1.0.tar.gz", "has_sig": false, "md5_digest": "4d69fb369e06da6feac58c560ac82f42", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30205, "upload_time": "2016-10-23T13:04:41", "url": "https://files.pythonhosted.org/packages/72/b1/322ea052f2cae116c110687b057304e5d6d484ef860765aa0089d3832a4e/pyscanprev-0.1.0.tar.gz" }, { "comment_text": "", "digests": { "md5": "193c28d5e499a2c1b4a3e8b8c32cb019", "sha256": "f011a1fb37d7c800e05ca47f2ffe13a5cf1d10f4c763278748881bf74ee22160" }, "downloads": -1, "filename": "pyscanprev-0.1.0.zip", "has_sig": false, "md5_digest": "193c28d5e499a2c1b4a3e8b8c32cb019", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 40480, "upload_time": "2016-10-23T13:04:51", "url": "https://files.pythonhosted.org/packages/92/1c/da27b0105929d81aeac68840dbba277a50fb15348e8d8b64c9fec81ae8fd/pyscanprev-0.1.0.zip" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "82368001e32dc0945481916a74e1ce7f", "sha256": "6ca5dd1167239729275c20cc183f42f6d0423654ea61879ad46d704e957cc600" }, "downloads": -1, "filename": "pyscanprev-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "82368001e32dc0945481916a74e1ce7f", "packagetype": "bdist_wheel", "python_version": "3.5", "requires_python": null, "size": 14957, "upload_time": "2016-10-23T13:05:01", "url": "https://files.pythonhosted.org/packages/92/94/28a13ede2df4665ad3882fbd8ed82afdafb9ebc8b4b99d7cbb6d852fc7a3/pyscanprev-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4d69fb369e06da6feac58c560ac82f42", "sha256": "96e366a43c33801f4641a8a2b640da700992369304b7e12915981fd66ca45a33" }, "downloads": -1, "filename": "pyscanprev-0.1.0.tar.gz", "has_sig": false, "md5_digest": "4d69fb369e06da6feac58c560ac82f42", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30205, "upload_time": "2016-10-23T13:04:41", "url": "https://files.pythonhosted.org/packages/72/b1/322ea052f2cae116c110687b057304e5d6d484ef860765aa0089d3832a4e/pyscanprev-0.1.0.tar.gz" }, { "comment_text": "", "digests": { "md5": "193c28d5e499a2c1b4a3e8b8c32cb019", "sha256": "f011a1fb37d7c800e05ca47f2ffe13a5cf1d10f4c763278748881bf74ee22160" }, "downloads": -1, "filename": "pyscanprev-0.1.0.zip", "has_sig": false, "md5_digest": "193c28d5e499a2c1b4a3e8b8c32cb019", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 40480, "upload_time": "2016-10-23T13:04:51", "url": "https://files.pythonhosted.org/packages/92/1c/da27b0105929d81aeac68840dbba277a50fb15348e8d8b64c9fec81ae8fd/pyscanprev-0.1.0.zip" } ] }