fictive.transform package

Module contents

transform (i.e., function) objects supporting inverse, composition, & mapping

fictive.transform.compose(*functions)[source]

creates a function that is the composition of functions

NB: if a function’s return value is a tuple, that tuple will be used as the var-positional parameter to the next function in the composition. Otherwise, the entire return value of a function is used as the first positional argument to the next function.

import typing

def swap(left, right):
    return right, left

def xor(base, variable):
    return base, base ^ variable

def plus_one(iterable: typing.Iterable):
    return (item + 1 for item in iterable)

def times_two(iterable: typing.Iterable):
    return (item * 2 for item in iterable)
>>> from fictive.transform import compose
>>> # NB: functions are applied applied left-to-right
>>> compose(swap, xor)(2, 3)
(3, 1)
>>> compose(xor, swap)(2, 3)
(1, 2)
>>> # NB: if the return type of a function is not a `tuple`,
>>> # it will be provided as the first positional argument to
>>> # the next function
>>> compose(plus_one, times_two)([3, 1])
<generator object times_two.<locals>.<genexpr> ...
>>> compose(plus_one, times_two, tuple)([3, 1])
(8, 4)
>>> # NB: if the return type of a function is a `tuple` (e.g.,
>>> # `swap`), that `tuple` will be unpacked for the next
>>> # function in the compositon
>>> compose(swap, plus_one, tuple)(2, 3)
Traceback (most recent call last):
    ...
TypeError: plus_one() takes 1 positional argument but 2 were given
>>> # adding an explicit packing function to the composition
>>> # can "bridge" between functions that return `tuple`\s
>>> # and functions that expect e.g. a single iterable
>>> compose(swap, lambda *args: (args, ), plus_one, tuple)(2, 3)
(4, 3)
fictive.transform.pack(*args, **kwargs)[source]

pack the positional and keyword arguments to a (args, kwargs) tuple

>>> from fictive.transform import pack
>>> pack(1, 2, 3, four=4, five=5) == ((1, 2, 3), {'four': 4, 'five': 5})
True
fictive.transform.arg_slice(*args)[source]

perform a slice operation on args and return the sliced tuple

>>> from fictive.transform import arg_slice
>>> arg_slice(2, 5)(1, 2, 3, 4, 5, 6, 7)
(3, 4, 5)
class fictive.transform.Transform(function=None, method=None, inverse=None)[source]

Bases: object

interface for inverse, composition, and mapping operations on functions

Subclassing:

from fictive.transform import Transform

class AddN(Transform):
    def __init__(self, n):
        super().__init__()
        self.n = n
    def operation(self, args):
        return (arg + self.n for arg in args)
    def inverse_operation(self, args):
        return (arg - self.n for arg in args)
>>> add_5 = AddN(5)
>>> add_5(range(4))
<generator object AddN.operation...>
>>> list(add_5(range(4)))
[5, 6, 7, 8]
>>> list(add_5.inverse([5, 6, 7, 8]))
[0, 1, 2, 3]

Ad hoc transforms:

times_two = Transform(
    #: unpacked positional arguments to iterator
    method=lambda self, *args: (arg * self.base for arg in args),
    #: iterator to tuple
    inverse=lambda self, args: tuple(arg // self.base for arg in args),
)
times_two.base = 2
>>> times_two(range(4))
<generator object <lambda>...>
>>> list(times_two(range(4)))
Traceback (most recent call last):
    ...
TypeError: unsupported operand type(s) for *: 'range' and 'int'
>>> list(times_two(*range(4)))
[0, 2, 4, 6]
>>> times_two.inverse([0, 2, 4, 6])
(0, 1, 2, 3)

Applying map ahead of time:

int_to_str = Transform(str, inverse=int)
>>> multi = int_to_str.map
>>> list(multi([1, 2, 3]))
['1', '2', '3']
>>> list(multi.inverse(['1', '2', '3']))
[1, 2, 3]

Compopsition:

>>> transform = (times_two @ tuple @ int_to_str.map)
>>> list(transform([1, 2, 3]))
['11', '22', '33']
plus_one = Transform(
    function=lambda arg: arg + 1,
    inverse=lambda arg: arg - 1)
join_args = Transform(
    method=lambda self, *args: self.token.join(args),
    inverse=lambda self, arg: arg.split(self.token))
join_args.token = '.'
tuplefy = Transform(tuple, inverse=iter)
>>> transform = \
... join_args @ tuplefy @ (int_to_str @ plus_one).map @ tuplefy.inverse
>>> transform([1, 2, 3])
'2.3.4'
>>> transform.inverse('2.3.4')
(1, 2, 3)
BASE_TYPE

alias of Transform

__call__(*args)[source]

calling the transform applies the operation

__init__(function=None, method=None, inverse=None)[source]
Parameters
  • function – set the transform’s operation to a callable that will not receive the transform itself as a bound parameter (i.e., the transform instance will not be the self parameter). Exclusive with method.

  • method – set the transform’s operation to a callable that will receive the transform itself as a bound parameter (i.e., the transform instance **will* be the self parameter). Exclusive with function.

  • inverse – set the transform’s inverse operation to the callable. Will be treated as a “function” or a “method” (i.e., whether to provide the transform itself as the first bound parameter) based on which of function or method is also set. (Requires one of function or method to be set.)

__matmul__(transform)[source]

use the “at” operator (i.e., @) to compose the transform

NB: this operator uses the standard “infix” notation for function composition, e.g. (f @ g)(x) == (f(g(x))

>>> import operator
>>> from fictive.transform import pack, Transform
>>> int_to_str = Transform(str, inverse=int)
>>> join = Transform(''.join)
>>> add = Transform(operator.add)
>>> sum_of_strs = add @ tuple @ int_to_str.map
>>> sum_of_strs([3, 4])
'34'
>>> str_of_sum = int_to_str @ add @ tuple
>>> str_of_sum([3, 4])
'7'
classmethod compose(*transforms)[source]

create a new transform that is the composition of transforms

NB: transforms are applied “left-to-right” (i.e., postfix)

>>> import operator
>>> from fictive.transform import pack, Transform
>>> int_to_str = Transform(str, inverse=int)
>>> join = Transform(''.join)
>>> add = Transform(operator.add)
>>> sum_of_strs = Transform.compose(int_to_str.map, tuple, add)
>>> sum_of_strs([3, 4])
'34'
>>> str_of_sum = Transform.compose(tuple, add, int_to_str)
>>> str_of_sum([3, 4])
'7'
property inverse

return a transpose of self

from fictive.transform import Transform

add_five = Transform(
    function=lambda arg: arg + 5,
    inverse=lambda arg: arg - 5,
)
>>> subtract_five = add_five.inverse
>>> subtract_five(8)
3
>>> subtract_five.inverse_operation(3)
8
inverse_operation(*args)[source]

Reverse transform from args to original input

property map

a new transform that will “map” the current transform when called

from fictive.transform import Transform

int_to_str = Transform(str, inverse=int)
>>> int_to_str(5)
'5'
>>> int_to_str_map = int_to_str.map
>>> int_to_str_map([1, 2, 3])
<map object at ...>
>>> list(int_to_str.map([1, 2, 3]))
['1', '2', '3']
>>> list(int_to_str.inverse.map(['1', '2', '3']))
[1, 2, 3]
operation(*args)[source]

Transform from args to output

property starmap

a new transform that will “star map” the current transform

import operator
from fictive.transform import Transform

add = Transform(operator.add)
>>> add(3, 4)
7
>>> add([3, 4], [5, 6])
[3, 4, 5, 6]
>>> add.starmap(([3, 4], [5, 6]))
<itertools.starmap object at ...>
>>> list(add.starmap(([3, 4], [5, 6])))
[7, 11]
classmethod transpose(transform)[source]

create a new transform, swapping the function and inverse

from fictive.transform import Transform

add_five = Transform(
    function=lambda arg: arg + 5,
    inverse=lambda arg: arg - 5,
)
>>> add_five(3)
8
>>> add_five.inverse_operation(8)
3
>>> subtract_five = Transform.transpose(add_five)
>>> subtract_five(8)
3
>>> subtract_five.inverse_operation(3)
8