PyFunctional Documentation
repository·master·Indexed 25 days ago
https://github.com/entilzha/pyfunctionalA Python library for creating data pipelines using chained functional operators. It provides an expressive API inspired by Scala, Spark, and LINQ, supporting lazy execution, parallel processing via pseq, and integration with various data sources including JSON, CSV, and SQLite3. The library categorizes operations into Streams (entry points), Transformations (lazy modifiers like map and filter), and Actions (terminal evaluations like reduce and sum).
What's inside PyFunctional
- Once a stream is initialized, you can manipulate the data using the Transformations and Actions API. Transformations modify the stream (e.g., mapping, filtering) and return a new stream, while Actions perform computations on the stream (e.g., reducing, counting) and typically return a concrete value.
How streams, transformations, and actions work together
masterPyFunctional operations are categorized into three types that form a data pipeline:
- Streams: The entry point that reads data for use by the collections API (e.g.,
seq(data)orseq.json('file.json')). - Transformations: Functions that transform data within a stream without evaluating it immediately (e.g.,
map,flat_map,filter,where,select). - Actions: Functions that trigger the evaluation of the transformation chain to produce a concrete value (e.g.,
to_list,reduce,to_dict,sum,max_by).
Example pipeline:
seq(1, 2, 3).map(lambda x: x * 2).reduce(lambda x, y: x + y)seqis the streammapis the transformationreduceis the action.
seq(1, 2, 3).map(lambda x: x * 2).reduce(lambda x, y: x + y)- Streams: The entry point that reads data for use by the collections API (e.g.,
How lazy execution and lineage work
masterPyFunctionaluses lazy execution by tracking the list of transformations applied to a sequence (called its lineage). Computations are only evaluated when an action is called.This lineage tracking allows for caching results to prevent expensive re-computation. For example, calling
size()orrepr()will cache the underlying sequence so that subsequent operations don'|n't re-run the entire pipeline.To inspect the current lineage, you can access the
_lineageattribute.def times_2(x): return 2 * x elements = ( seq(1, 1, 2, 3, 4) .map(times_2) .peek(print) .distinct() ) elements._lineage # Lineage: sequence -> map(times_2) -> peek(print) -> distinct l_elements = elements.to_list() # Prints: 1 # Prints: 1 # Prints: 2 # Prints: 3 # Prints: 4 elements._lineage # Lineage: sequence -> map(times_2) -> peek(print) -> distinct -> cache l_elements = elements.to_list() # The cached result is returned so times_2 is not called and nothing is printedInstall PyFunctional via pip
masterInstall the
pyfunctionalpackage using pip from your command line.$ pip install pyfunctionalInitialize a stream with seq()
masterTo start using PyFunctional, import theseqfunction fromfunctional. Theseqfunction returns an instance offunctional.streams.Stream, which serves as the entry point for all data processing. You can useseqto wrap existing Python collections or use specialized stream methods like.csv()to read data from files.Get started with PyFunctional
masterFor a comprehensive tutorial on how to use PyFunctional, visit the official tutorial site at http://pyfunctional.pedro.ai/.Import PyFunctional in Python
masterTo use the library, import the
seqfunction from thefunctionalmodule.from functional import seqEnable Parallel Execution with pseq
masterTo parallelize "embarrassingly parallel" operations, import
pseqinstead ofseq. This uses Python'smultiprocessingto run operations in parallel.Supported parallel operations:
map/selectfilter/filter_not/whereflat_map
PyFunctional automatically squashes chains of these operations to reduce multiprocessing overhead.
Write to compressed files
masterWhen using
to_functions to write files, you can specify compression. PyFunctional supportsgzip(orgz),lzma(orxz), andbz2.Set the
compressionparameter to one of these values. If not set, it defaults toNone(no compression).Use Scala/Spark or LINQ inspired APIs
masterPyFunctional provides multiple ways to express the same logic, allowing you to choose the syntax that fits your background:
- Scala/Spark style: Uses
filterandmap. - LINQ style: Uses
whereandselect. fnstyle: Uses thefnlibrary's underscore_syntax for more concise lambdas.
Example filtering transactions:
from functional import seq from collections import namedtuple from fn import _ Transaction = namedtuple('Transaction', 'reason amount') transactions = [ Transaction('github', 7), Transaction('food', 10), Transaction('coffee', 5), # ... other transactions ] # Scala/Spark inspired food_cost = seq(transactions)\n .filter(lambda x: x.reason == 'food')\n .map(lambda x: x.amount).sum() # LINQ inspired food_cost = seq(transactions)\n .where(lambda x: x.reason == 'food')\n .select(lambda x: x.amount).sum() # Using fn underscore syntax food_cost = seq(transactions).filter(_.reason == 'food').map(_.amount).sum()- Scala/Spark style: Uses
Perform Aggregates and Joins
masterPyFunctional excels at complex data manipulations like grouping, reducing by key, and joining different data sources.
reduce_by_key: Useful for word counts or frequency analysis.group_by: Groups elements by a key.inner_join: Joins two streams based on common keys.
# Word count example words = 'I dont want to believe I want to know'.split(' ') seq(words).map(lambda word: (word, 1)).reduce_by_key(lambda x, y: x + y) # Joining JSON users with JSONL messages users = seq.json('examples/users.json') messages = seq.jsonl('examples/chat_logs.jsonl') message_tuples = messages.group_by(lambda m: m['user']) data = users.inner_join(message_tuples)Use the `no_wrap` option to prevent Sequence wrapping
masterBy default, functions like
first()orlast()wrap their result in aSequenceif the returned element is itself an iterable. To get the raw element instead, use theno_wrap=Trueoption.This option can be passed to:
first(no_wrap=True)last(no_wrap=True)head(no_wrap=True)head_option(no_wrap=True)last_option(no_wrap=True)seq(..., no_wrap=True)- The
Sequence()constructor
>>> s = seq(list(), list()) >>> type(s.first()) <class 'functional.pipeline.Sequence'> >>> type(s.first(no_wrap=True)) <class 'list'> >>> type(seq([list(), list()], no_wrap=True).last()) <class 'list'>