4

It's fairly straightforward to write a function that composes two other functions. (For simplicity, assume they are one parameter each.)

def compose(f, g):
    fg = lambda x: f(g(x))
    return fg

def add1(x):
    return x + 1

def add2(x):
    return x + 2

print(compose(add1, add2)(5))  # => 8

I would like to do composition using an operator, e.g., (add1 . add2)(5).

Is there a way to do that?

I tried various decorator formulations, but I couldn't get any of them to work.

def composable(f):
  """
    Nothing I tried worked. I won't clutter up the question 
    with my failed attempts.
  """

@composable
def add1(x):
    return x + 1
martineau
  • 119,623
  • 25
  • 170
  • 301
RussAbbott
  • 2,660
  • 4
  • 24
  • 37
  • You mean you want to use an infix operator instead of standard prefix-calling notation? – Carcigenicate Jan 11 '19 at 20:22
  • Possible duplicate of [Python: defining my own operators?](https://stackoverflow.com/questions/932328/python-defining-my-own-operators) – Eric Jan 11 '19 at 20:28
  • Related, maybe dupe: [How to multiply functions in python?](https://stackoverflow.com/q/30195045/674039) – wim Jan 12 '19 at 18:01

1 Answers1

9

First only a certain amount of operator symbols are allowed in Python syntax. Dot "." is not a valid operator.

This page (the page is actually about the Python operator module, but the naming convention are the same to datamodel and the content is more organized) listed all available operators and the corresponding instance methods. For example, if you want to use "@" as the operator, you can write a decorator like this:

import functools

class Composable:

    def __init__(self, func):
        self.func = func
        functools.update_wrapper(self, func)

    def __matmul__(self, other):
        return lambda *args, **kw: self.func(other.func(*args, **kw))

    def __call__(self, *args, **kw):
        return self.func(*args, **kw)

To test:

@Composable
def add1(x):
    return x + 1

@Composable
def add2(x):
    return x + 2

print((add1 @ add2)(5))
# 8
Philip Tzou
  • 5,926
  • 2
  • 18
  • 27
  • Thanks. My confusion -- at least one of them -- was that dot was treated as an operator and that, __getattribute__ was the associated function. Also, I was trying to do it with a decorator function, rather than a decorator class. I'm going to leave your solution unchecked as *the answer* for now to see if another approach is suggested. – RussAbbott Jan 11 '19 at 20:50
  • @RussAbbott I see what you mean. I don't think you can reload the `__getattribute__` method to access another function without passing the other function directly. It is true you can access a global function through `globals()`, but it is not recommended and you can not handle anonymous functions at all. – Philip Tzou Jan 11 '19 at 21:42
  • One of the problems I was having was the __getattribute__ seemed to get its argument as a string -- even when I was passing it a function. Don't know how to get around that. – RussAbbott Jan 12 '19 at 03:57
  • I just tried a decorator function version of your solution. When it got to `print((add1 @ add2)(5))` I got an error message: `unsupported operand type(s) for @: 'function' and 'function'`. How does your approach avoid running into that problem? – RussAbbott Jan 12 '19 at 05:23
  • Your approach works successfully if I replace `@` with `*` and `__matmul__` with `__mul__` or use `>` and .`__gt__` (It runs successfully even though PyCharm complains that a boolean isn't callable.) But as I said earlier, using `.` and `__getattribute__` fails because the argument to `__getattribute__` is converted to a string before being passed to `__getattribute__`. – RussAbbott Jan 12 '19 at 06:26
  • I've continued this discussion as a separate question: https://stackoverflow.com/questions/54162257/a-function-composition-operator-in-python, – RussAbbott Jan 12 '19 at 17:43
  • Hello, is there an idea how to use ```@``` in a longer composition like ```f_1 @ f_2 @ f_3 (x)```. – Uwe.Schneider Sep 16 '21 at 10:27
  • Is there a way to achieve this without having to decorate all functions that you want to compose? E.g., by overriding `__lt__` in the class `function` globally? – wstomv Apr 27 '22 at 11:39