fluentpy
To use this module just import it with a short custom name. I recommend:
>>> import fluentpy as _ # for scripts / projects that don't use gettext
>>> import fluentpy as _f # for everything else
If you want / need this to be less magical, you can import the main wrapper normally
>>> from fluentpy import wrap # or `_`, if you're not using gettext
Then to use the module, wrap any value and start chaining off of it. To get started lets try to introspect fluentpy using its own fluent interface:
$ python3 -m fluentpy '_(_).dir().print()'
$ python3 -m fluentpy '_(_).help()'
This is incidentally the second way to use this module, as a helper that makes it easier to write short shell filters quickly in python.:
$ echo "foo\nbar\nbaz" \
| python3 -m fluentpy "lib.sys.stdin.readlines().map(each.call.upper()).map(print)"
Try to rewrite that in classical python (as a one line shell filter) and see which version spells out what happens in which order more clearly.
For further documentation and development see this documentation or the source at https://github.com/dwt/fluent
Functions
- fluentpy.wrap(wrapped, *, previous=None)[source]
Factory method, wraps anything and returns the appropriate
Wrapper
subclass.This is the main entry point into the fluent wonderland. Wrap something and everything you call off of that will stay wrapped in the appropriate wrappers.
It is usually imported in one of the following ways:
>>> import fluentpy as _ >>> import fluentpy as _f >>> from fluentpy import wrap
wrap
is the original name of the function, though I rarely recommend to use it by this name.
Classes
Wrapper
: Universal wrapper.ModuleWrapper
: TheWrapper
for modules transforms attribute accesses into pre-wrapped imports of sub-modules.CallableWrapper
: TheWrapper
for callables adds higher order methods.IterableWrapper
: TheWrapper
for iterables adds iterator methods to any iterable.MappingWrapper
: TheWrapper
for mappings allows indexing into mappings via attribute access.SetWrapper
: TheWrapper
for sets is mostly likeIterableWrapper
.TextWrapper
: TheWrapper
for str adds regex convenience methods.EachWrapper
: TheWrapper
for expressions (see documentation foreach
).
- class fluentpy.Wrapper(wrapped, *, previous)[source]
Universal wrapper.
This class ensures that all function calls and attribute accesses (apart from such special CPython runtime accesses like
object.__getattribute__
, which cannot be intercepted) will be wrapped with the wrapper again. This ensures that the fluent interface will persist and everything that is returned is itself able to be chained from again.All returned objects will be wrapped by this class or one of its sub classes, which add functionality depending on the type of the wrapped object. I.e. iterables will gain the collection interface, mappings will gain the mapping interface, strings will gain the string interface, etc.
If you want to access the actual wrapped object, you will have to unwrap it explicitly using
.unwrap
or._
Please note: Since most of the methods on these objects are actual standard library methods that are simply wrapped to rebind their (usually first) parameter to the object they where called on. So for example:
repr(something)
becomes_(something).repr()
. This means that the (unchanged) documentation (often) still shows the original signature and refers to the original arguments. A little bit of common sense might therefore be required.Inheritance
- property unwrap
Returns the underlying wrapped value of this wrapper instance.
All other functions return wrappers themselves unless explicitly stated.
Alias:
_
- property previous
Returns the previous wrapper in the chain of wrappers.
This allows you to walk the chain of wrappers that where created in your expression. Mainly used internally but might be useful for introspection.
- property self
Returns the previous wrapped object. This is especially usefull for APIs that return None.
For example
_([1,3,2]).sort().self.print()
will print the sorted list, even thoughsort()
did returnNone
.This is simpler than using .previous as there are often multiple wrappers involved where you might expect only one. E.g.
_([2,1]).sort().self._ == [1,2]
but_([2,1]).sort().previous._
will return the functionlist.sort()
as the attrget and call are two steps of the call chain.This eases chaining using APIs that where not designed with chaining in mind. (Inspired by SmallTalk’s default behaviour)
- property proxy
Allow access to shadowed attributes.
Breakout that allows access to attributes of the wrapped object that are shadowed by methods on the various wrapper classes. Wrapped of course.
>>> class UnfortunateNames(object): >>> def previous(self, *args): >>> return args
This raises TypeError, because Wrapper.previous() shadows UnfortunateNames.previous():
>>> _(UnfortunateNames()).previous('foo'))
This works as expected:
>>> _(UnfortunateNames()).proxy.previous('foo')._) == ('foo',)
- call(function, *args, **kwargs)[source]
Call function with self as its first argument.
>>> _('foo').call(list)._ == list('foo') >>> _('fnord').call(textwrap.indent, prefix=' ')._ == textwrap.indent('fnord', prefix=' ')
Call is mostly usefull to insert normal functions that express some algorithm into the call chain. For example like this:
>>> seen = set() >>> def havent_seen(number): ... if number in seen: ... return False ... seen.add(number) ... return True >>> ( ... _([1,3,1,3,4,5,4]) ... .dropwhile(havent_seen) ... .print() ... )
Less obvious, it can also be used as a decorator, however the result can be confusing, so maybe not as recomended:
>>> numbers = _(range(5)) >>> @numbers.call ... def items(numbers): ... for it in numbers: ... yield it ... yield it >>> items.call(list).print()
Note the difference from
.__call__()
. This appliesfunction(self, …)
instead ofself(…)
.
- to(function, *args, **kwargs)[source]
Like .call() but returns an unwrapped result.
This makes
.to()
really convenient to terminate a call chain by converting to a type that perhaps itself con.
- setattr(name, value, /)
Sets the named attribute on the given object to the specified value.
setattr(x, ‘y’, v) is equivalent to ``x.y = v’’
- getattr(object, name[, default]) value
Get a named attribute from an object; getattr(x, ‘y’) is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn’t exist; without it, an exception is raised in that case.
- hasattr(name, /)
Return whether the object has an attribute with the given name.
This is done by calling getattr(obj, name) and catching AttributeError.
- delattr(name, /)
Deletes the named attribute from the given object.
delattr(x, ‘y’) is equivalent to ``del x.y’’
- isinstance(class_or_tuple, /)
Return whether an object is an instance of a class or of a subclass thereof.
A tuple, as in
isinstance(x, (A, B, ...))
, may be given as the target to check against. This is equivalent toisinstance(x, A) or isinstance(x, B) or ...
etc.
- issubclass(class_or_tuple, /)
Return whether ‘cls’ is a derived from another class or is the same class.
A tuple, as in
issubclass(x, (A, B, ...))
, may be given as the target to check against. This is equivalent toissubclass(x, A) or issubclass(x, B) or ...
etc.
- dir([object]) list of strings
If called without an argument, return the names in the current scope. Else, return an alphabetized list of names comprising (some of) the attributes of the given object, and of attributes reachable from it. If the object supplies a method named __dir__, it will be used; otherwise the default dir() logic is used and returns:
for a module object: the module’s attributes. for a class object: its attributes, and recursively the attributes
of its bases.
- for any other object: its attributes, its class’s attributes, and
recursively the attributes of its class’s base classes.
- vars([object]) dictionary
Without arguments, equivalent to locals(). With an argument, equivalent to object.__dict__.
- print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default. Optional keyword arguments: file: a file-like object (stream); defaults to the current sys.stdout. sep: string inserted between values, default a space. end: string appended after the last value, default a newline. flush: whether to forcibly flush the stream.
- pprint(stream=None, indent=1, width=80, depth=None, *, compact=False)
Pretty-print a Python object to a stream [default is sys.stdout].
- help
Define the built-in ‘help’. This is a wrapper around pydoc.help (with a twist).
- type = <function type>
- str = <function str>
- repr()
Return the canonical string representation of the object.
For many object types, including most builtins, eval(repr(obj)) == obj.
- class fluentpy.ModuleWrapper(wrapped, *, previous)[source]
The
Wrapper
for modules transforms attribute accesses into pre-wrapped imports of sub-modules.Inheritance
- reload()
Reload the module and return it.
The module must have been successfully imported before.
- class fluentpy.CallableWrapper(wrapped, *, previous)[source]
The
Wrapper
for callables adds higher order methods.Inheritance
- curry(*default_args, **default_kwargs)[source]
Like functools.partial, but with a twist.
If you use
wrap
or_
as a positional argument, upon the actual call, arguments will be left-filled for those placeholders.>>> _(operator.add).curry(_, 'foo')('bar')._ == 'barfoo'
If you use wrap._$NUMBER (with $NUMBER < 10) you can take full control over the ordering of the arguments.
>>> _(a_function).curry(_._0, _._0, _.7)
This will repeat the first argument twice, then take the 8th and ignore all in between.
You can also mix numbered with generic placeholders, but since it can be hard to read, I would not advise it.
There is also
_._args
which is the placeholder for the*args
variable argument list specifier. (Note that it is only supported in the last position of the positional argument list.)>>> _(lambda x, y=3: x).curry(_._args)(1, 2)._ == (1, 2) >>> _(lambda x, y=3: x).curry(x=_._args)(1, 2)._ == (1, 2)
- class fluentpy.IterableWrapper(wrapped, *, previous)[source]
The
Wrapper
for iterables adds iterator methods to any iterable.Most iterators in Python 3 return an iterator by default, which is very interesting if you want to build efficient processing pipelines, but not so hot for quick and dirty scripts where you have to wrap the result in a list() or tuple() all the time to actually get at the results (e.g. to print them) or to actually trigger the computation pipeline.
Thus all iterators on this class are by default immediate, i.e. they don’t return the iterator but instead consume it immediately and return a tuple. Of course if needed, there is also an i{map,zip,enumerate,…} version for your enjoyment that returns the iterator.
Iterating over wrapped iterators yields unwrapped elements by design. This is neccessary to make
fluentpy
interoperable with the standard library. This means you will have to rewrap occasionally in handwritten iterator methods or when iterating over a wrapped iteratorWhere methods return infinite iterators, the non i-prefixed method name is skipped. See
icycle
for an an example.Inheritance
- iter(iterable) iterator
- iter(callable, sentinel) iterator
Get an iterator from an object. In the first form, the argument must supply its own iterator, or be a sequence. In the second form, the callable is called until it returns the sentinel.
- star_call(function, *args, **kwargs)[source]
Calls
function(*self)
, but allows to prepend args and add kwargs.
- get(target_index, default=<object object>)[source]
Like
`dict.get()
but for IterableWrappers and able to deal with generators
- join(with_what='')[source]
Like str.join, but the other way around. Bohoo!
Also calls str on all elements of the collection before handing it off to str.join as a convenience.
- freeze = <function tuple>
- len()[source]
Just like len(), but also works for iterators.
Beware, that it has to consume the iterator to compute its length
- max(iterable, *[, default=obj, key=func]) value
- max(arg1, arg2, *args, *[, key=func]) value
With a single iterable argument, return its biggest item. The default keyword-only argument specifies an object to return if the provided iterable is empty. With two or more arguments, return the largest argument.
- min(iterable, *[, default=obj, key=func]) value
- min(arg1, arg2, *args, *[, key=func]) value
With a single iterable argument, return its smallest item. The default keyword-only argument specifies an object to return if the provided iterable is empty. With two or more arguments, return the smallest argument.
- sum(start=0, /)
Return the sum of a ‘start’ value (default: 0) plus an iterable of numbers
When the iterable is empty, return the start value. This function is intended specifically for use with numeric values and may reject non-numeric types.
- any()
Return True if bool(x) is True for any x in the iterable.
If the iterable is empty, return False.
- all()
Return True if bool(x) is True for all values x in the iterable.
If the iterable is empty, return True.
- reduce(function, sequence[, initial]) value
Apply a function of two arguments cumulatively to the items of a sequence, from left to right, so as to reduce the sequence to a single value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). If initial is present, it is placed before the items of the sequence in the calculation, and serves as a default when the sequence is empty.
- ieach(a_function)[source]
call
a_function
on each elment in self purely for the side effect, then yield the input element.
- each(a_function)
call
a_function
on each elment in self purely for the side effect, then yield the input element.
- imap = <function map>
- map = <function map>
- istar_map = <function starmap>
- istarmap = <function starmap>
- star_map = <function starmap>
- starmap = <function starmap>
- ifilter = <function filter>
- filter = <function filter>
- ienumerate = <function enumerate>
- enumerate = <function enumerate>
- ireversed = <function reversed>
- reversed = <function reversed>
- isorted(*, key=None, reverse=False)
Return a new list containing all items from the iterable in ascending order.
A custom key function can be supplied to customize the sort order, and the reverse flag can be set to request the result in descending order.
- sorted(*, key=None, reverse=False)
Return a new list containing all items from the iterable in ascending order.
A custom key function can be supplied to customize the sort order, and the reverse flag can be set to request the result in descending order.
- igrouped(group_length)[source]
Cut self into tupels of length group_length s -> (s0,s1,s2,…sn-1), (sn,sn+1,sn+2,…s2n-1), (s2n,s2n+1,s2n+2,…s3n-1), …
- grouped(group_length)
Cut self into tupels of length group_length s -> (s0,s1,s2,…sn-1), (sn,sn+1,sn+2,…s2n-1), (s2n,s2n+1,s2n+2,…s3n-1), …
- izip = <function zip>
- zip = <function zip>
- iflatten(level=inf, stop_at_types=(<class 'str'>, <class 'bytes'>))[source]
Modeled after rubys array.flatten @see http://ruby-doc.org/core-1.9.3/Array.html#method-i-flatten
Calling flatten on string likes would lead to infinity recursion, thus @arg stop_at_types. If you want to flatten those, use a combination of @arg level and @arg stop_at_types.
- flatten(level=inf, stop_at_types=(<class 'str'>, <class 'bytes'>))
Modeled after rubys array.flatten @see http://ruby-doc.org/core-1.9.3/Array.html#method-i-flatten
Calling flatten on string likes would lead to infinity recursion, thus @arg stop_at_types. If you want to flatten those, use a combination of @arg level and @arg stop_at_types.
- ireshape(*spec)[source]
Creates structure from linearity.
Allows you to turn
(1,2,3,4)
into((1,2),(3,4))
. Very much inspired by numpy.reshape. @see https://docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html@argument spec integer of tuple of integers that give the spec for the dimensions of the returned structure. The last dimension is inferred as needed. For example:
>>> _([1,2,3,4]).reshape(2)._ == ((1,2),(3,4))
Please note that
>>> _([1,2,3,4]).reshape(2,2)._ == (((1,2),(3,4)),)
The extra tuple around this is due to the specification being, two tuples of two elements which is possible exactly once with the given iterable.
This iterator will not ensure that the shape you give it will generate fully ‘rectangular’. This means that the last element in the generated sequnce the number of elements can be different! This tradeoff is made, so it works with infinite sequences.
- reshape(*spec)
Creates structure from linearity.
Allows you to turn
(1,2,3,4)
into((1,2),(3,4))
. Very much inspired by numpy.reshape. @see https://docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html@argument spec integer of tuple of integers that give the spec for the dimensions of the returned structure. The last dimension is inferred as needed. For example:
>>> _([1,2,3,4]).reshape(2)._ == ((1,2),(3,4))
Please note that
>>> _([1,2,3,4]).reshape(2,2)._ == (((1,2),(3,4)),)
The extra tuple around this is due to the specification being, two tuples of two elements which is possible exactly once with the given iterable.
This iterator will not ensure that the shape you give it will generate fully ‘rectangular’. This means that the last element in the generated sequnce the number of elements can be different! This tradeoff is made, so it works with infinite sequences.
- igroupby = <function groupby>
- groupby(*args, **kwargs)[source]
See igroupby for most of the docs.
Correctly consuming an itertools.groupby is surprisingly hard, thus this non tuple returning version that does it correctly.
- itee()
tee(iterable, n=2) –> tuple of n independent iterators.
- islice = <function islice>
- slice = <function islice>
- icycle = <function cycle>
- iaccumulate = <function accumulate>
- accumulate = <function accumulate>
- idropwhile = <function dropwhile>
- dropwhile = <function dropwhile>
- ifilterfalse = <function filterfalse>
- filterfalse = <function filterfalse>
- ipermutations = <function permutations>
- permutations = <function permutations>
- icombinations = <function combinations>
- combinations = <function combinations>
- icombinations_with_replacement = <function combinations_with_replacement>
- combinations_with_replacement = <function combinations_with_replacement>
- iproduct = <function product>
- product = <function product>
- class fluentpy.MappingWrapper(wrapped, *, previous)[source]
The
Wrapper
for mappings allows indexing into mappings via attribute access.Indexing into dicts like objects. As JavaScript can.
>>> _({ 'foo': 'bar: }).foo == 'bar
Inheritance
- class fluentpy.SetWrapper(wrapped, *, previous)[source]
The
Wrapper
for sets is mostly likeIterableWrapper
.Mostly like IterableWrapper
Inheritance
- freeze = <function frozenset>
- class fluentpy.TextWrapper(wrapped, *, previous)[source]
The
Wrapper
for str adds regex convenience methods.Supports most of the regex methods as if they where native str methods
Inheritance
- search(string, flags=0)
Scan through string looking for a match to the pattern, returning a match object, or None if no match was found.
- match(string, flags=0)
Try to apply the pattern at the start of the string, returning a match object, or None if no match was found.
- fullmatch(string, flags=0)
Try to apply the pattern at the start of the string, returning a match object, or None if no match was found.
- split(string, maxsplit=0, flags=0)
Split the source string by the occurrences of the pattern, returning a list containing the resulting substrings. If capturing parentheses are used in pattern, then the text of all groups in the pattern are also returned as part of the resulting list. If maxsplit is nonzero, at most maxsplit splits occur, and the remainder of the string is returned as the final element of the list.
- findall(string, flags=0)
Return a list of all non-overlapping matches in the string.
If one or more capturing groups are present in the pattern, return a list of groups; this will be a list of tuples if the pattern has more than one group.
Empty matches are included in the result.
- finditer(string, flags=0)
Return an iterator over all non-overlapping matches in the string. For each match, the iterator returns a match object.
Empty matches are included in the result.
- sub(repl, string, count=0, flags=0)
Return the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in string by the replacement repl. repl can be either a string or a callable; if a string, backslash escapes in it are processed. If it is a callable, it’s passed the match object and must return a replacement string to be used.
- subn(repl, string, count=0, flags=0)
Return a 2-tuple containing (new_string, number). new_string is the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in the source string by the replacement repl. number is the number of substitutions that were made. repl can be either a string or a callable; if a string, backslash escapes in it are processed. If it is a callable, it’s passed the match object and must return a replacement string to be used.
- int = <function int>
- float = <function float>
- ord()
Return the Unicode code point for a one-character string.
Variables
- fluentpy.lib
Imports as expressions. Already pre-wrapped.
All attribute accesses to instances of this class are converted to an import statement, but as an expression that returns the wrapped imported object.
Example:
>>> lib.sys.stdin.read().map(print)
Is equivalent to
>>> import sys >>> wrap(sys).stdin.read().map(print)
But of course without creating the intermediate symbol ‘stdin’ in the current namespace.
All objects returned from lib are pre-wrapped, so you can chain off of them immediately.
fluentpy.wrap('virtual root module')
- fluentpy.each
Create functions from expressions.
Use
each.foo._
to create attrgetters,each['foo']._
to create itemgetters,each.foo()._
to create methodcallers oreach == 'foo'
(with pretty much any operator) to create callable operators.Many operations can be chained, to extract a deeper object by iterating a container. E.g.:
>>> each.foo.bar('baz')['quoox']._
creates a callable that will first get the attribute
foo
, then call the methodbar('baz')
on the result and finally applies gets the item ‘quoox’ from the result. For this reason, all callables need to be unwrapped before they are used, as the actual application would just continue to build up the chain on the original callable otherwise.Apply an operator like
each < 3
to generate a callable that applies that operator. Different to all other cases, applying any binary operator auto terminates the expression generator, so no unwrapping is neccessary.Note: The generated functions never wrap their arguments or return values.
fluentpy.wrap(each)
- fluentpy._0
Placeholder for
CallableWrapper.curry()
to access argument at index 0.fluentpy.wrap(0)
- fluentpy._1
Placeholder for
CallableWrapper.curry()
to access argument at index 1.fluentpy.wrap(1)
- fluentpy._2
Placeholder for
CallableWrapper.curry()
to access argument at index 2.fluentpy.wrap(2)
- fluentpy._3
Placeholder for
CallableWrapper.curry()
to access argument at index 3.fluentpy.wrap(3)
- fluentpy._4
Placeholder for
CallableWrapper.curry()
to access argument at index 4.fluentpy.wrap(4)
- fluentpy._5
Placeholder for
CallableWrapper.curry()
to access argument at index 5.fluentpy.wrap(5)
- fluentpy._6
Placeholder for
CallableWrapper.curry()
to access argument at index 6.fluentpy.wrap(6)
- fluentpy._7
Placeholder for
CallableWrapper.curry()
to access argument at index 7.fluentpy.wrap(7)
- fluentpy._8
Placeholder for
CallableWrapper.curry()
to access argument at index 8.fluentpy.wrap(8)
- fluentpy._9
Placeholder for
CallableWrapper.curry()
to access argument at index 9.fluentpy.wrap(9)