32
def sub3(n):
    return n - 3

def square(n):
    return n * n

It's easy to compose functions in Python:

>>> my_list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [square(sub3(n)) for n in my_list]
[9, 4, 1, 0, 1, 4, 9, 16, 25, 36]

Unfortunately, to use the composition as a key it's awkward, you have to use them in another function which calls both functions in turn:

>>> sorted(my_list, key=lambda n: square(sub3(n)))
[3, 2, 4, 1, 5, 0, 6, 7, 8, 9]

This should really just be sorted(my_list, key=square*sub3), because heck, function __mul__ isn't used for anything else anyway:

>>> square * sub3
TypeError: unsupported operand type(s) for *: 'function' and 'function'

Well let's just define it then!

>>> type(sub3).__mul__ = 'something'
TypeError: can't set attributes of built-in/extension type 'function'

D'oh!

>>> class ComposableFunction(types.FunctionType):
...     pass
...
TypeError: Error when calling the metaclass bases
    type 'function' is not an acceptable base type

D'oh!

class Hack(object):
    def __init__(self, function):
        self.function = function
    def __call__(self, *args, **kwargs):
        return self.function(*args, **kwargs)
    def __mul__(self, other):
        def hack(*args, **kwargs):
            return self.function(other(*args, **kwargs))
        return Hack(hack)

Hey, now we're getting somewhere..

>>> square = Hack(square)
>>> sub3 = Hack(sub3)
>>> [square(sub3(n)) for n in my_list]
[9, 4, 1, 0, 1, 4, 9, 16, 25, 36]
>>> [(square*sub3)(n) for n in my_list]
[9, 4, 1, 0, 1, 4, 9, 16, 25, 36]
>>> sorted(my_list, key=square*sub3)
[3, 2, 4, 1, 5, 0, 6, 7, 8, 9]

But I don't want a Hack callable class! The scoping rules are different in ways I don't fully understand, and it's arguably even uglier than just using the "lameda". Is it possible to get composition working directly with functions somehow?

10
  • 5
    @MalikBrahimi that isn't function composition, which is what wim wants. en.wikipedia.org/wiki/Function_composition Commented May 12, 2015 at 15:48
  • 4
    There is a long thread on adding function composition (using the upcoming matrix multiplication operator @, since function composition is more similar to matrix multiplication than regular multiplication) on the python-ideas mailing list. The short summary, though, is that it's not going to happen any time soon, if at all. Commented May 12, 2015 at 15:50
  • 3
    Here's a link to the archives for May. It's a bit of a mess to follow, since the initial message that started the thread had no subject, and it wound up as 2 or 3 parallel threads. One of the more interesting ideas to pop up (IMO) was rather than actually compose functions, just make a tuple of functions callable, so that (f, g, h)(x) == f(g(h(x))). Commented May 12, 2015 at 15:55
  • 3
    Anything that gets rid of the (stacks (of (parens (to (balance!))))) Commented May 12, 2015 at 16:02
  • 6
    I hate to say it, but I don't find anything wrong with the lambda. Although callable tuples would be cool. Commented May 12, 2015 at 16:12

4 Answers 4

24

You can use your hack class as a decorator pretty much as it's written, though you'd likely want to choose a more appropriate name for the class.

Like this:

class Composable(object):
    def __init__(self, function):
        self.function = function
    def __call__(self, *args, **kwargs):
        return self.function(*args, **kwargs)
    def __mul__(self, other):
        @Composable
        def composed(*args, **kwargs):
            return self.function(other(*args, **kwargs))
        return composed
    def __rmul__(self, other):
        @Composable
        def composed(*args, **kwargs):
            return other(self.function(*args, **kwargs))
        return composed

You can then decorate your functions like so:

@Composable
def sub3(n):
    return n - 3

@Composable
def square(n):
    return n * n

And compose them like so:

(square * sub3)(n)

Basically it's the same thing you've accomplished using your hack class, but using it as a decorator.

Sign up to request clarification or add additional context in comments.

1 Comment

Neat. I made a slight improvement, so now composition works with any other callables for example (sub3*int)("10") --> 7 and (str*sub3)(10) --> '7'
2

Python does not (and likely will never) have support for function composition either at the syntactic level or as a standard library function. There are various 3rd party modules (such as functional) that provide a higher-order function that implements function composition.

Comments

2

Maybe something like this:

class Composition(object):
    def __init__(self, *args):
        self.functions = args

    def __call__(self, arg):
        result = arg
        for f in reversed(self.functions):
            result = f(result)

        return result

And then:

sorted(my_list, key=Composition(square, sub3))

2 Comments

Why not use a closure instead?
Closure is fine too. I don't see much difference between those approaches (except that with class you can modify functions list after composition creation).
2

You can compose functions using SSPipe library:

from sspipe import p, px

sub3 = px - 3
square = px * px
composed = sub3 | square
print(5 | composed)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.