20

I am trying to make a function that rounds other functions for my university degree . For example I would like to call the round_sqrt = round(sqrt) and when i call the round_sqrt(5) it has to shows me 2 instead of 2.23606797749979. What I am trying is this:

def rounding(funct):
    return round(funct)

but this doesn't work.

EDIT: The function should have only one parameter. For example the start of the function should be

def rounding(func):

so in this function the funct function needs to be rounded. so when I call rounding(abs)(3.2) it shows me 3.

Alex
  • 18,484
  • 8
  • 60
  • 80
php kubrick
  • 227
  • 1
  • 5
  • 5
    You are looking for function composition. Mathematically, `h = f ∘ g` is defined as `h(x) = f(g(x))`. While it would be nice if Python a composition operator were available in Python, practical concerns require you to define the composed function yourself. – chepner Nov 07 '18 at 17:20
  • 7
    playing with composition is one of the reasons I quickly fell in love with (then hated, then fell in love with, then hated, then...) Haskell. – Adam Smith Nov 07 '18 at 17:25
  • 1
    Haskell is easy because everything fits into a theoretically sound mathematical framework. Haskell is hard because we are aren't use to conforming to said framework. :) – chepner Nov 07 '18 at 17:35
  • 2
    You can either leave the question to help other people or delete it, but please don't just remove all the content. – Alex Nov 07 '18 at 17:36
  • 3
    @AdamSmith Sounds like you were composing `hate . love . hate . love` – Mateen Ulhaq Nov 07 '18 at 23:56
  • @AdamSmith You will love, hate J (http://www.jsoftware.com/) then. – Micha Wiedenmann Nov 08 '18 at 06:46
  • @chepner i‘d say the composition function fits the mathematical definition of „operator“ – fpbhb Nov 14 '18 at 17:27
  • @fpbhb Of course it does. That doesn't mean there will ever be a *Python* operator that creates a new functions. Let's say `f` returns a 2-tuple, with `def g(a, b=3)`. Now, should `(g ∘ f)(x)` be the same as `g(f(x))` or `g(*f(x))`? Or should there be separate operators for each? There are lots of reasonable ways to extend composition to Python's concept of a function. And what should metadata like name and docstring for the new function be set to? Should the metadata from the original functions be used in some way? – chepner Nov 14 '18 at 17:52
  • @chepner Now I see what you mean: you basically say that an operator (syntax-wise) simply wouldn't be expressive enough to cover composition for Python's function semantics, right? – fpbhb Nov 14 '18 at 18:50
  • More or less :) There is a whole list of combinations for which you might want something described as composition: `g(f(x))`, `g(*f(x))`, `g(**f(x))`, `h(f(x), **g(x))`, `h(*f(x), **g(x))` come to mind. (Things like `h(f(x), g(x))` could be handled with a separate operator or syntax, perhaps `h ∘ (f, g)`.) And even assuming you could convince enough people that any such proposal (or subset thereof) is worth adding to the language, what token would you actually choose? There is strong resistance to adding Unicode operators, and acceptable ASCII operators are nearly exhausted. – chepner Nov 14 '18 at 18:58

3 Answers3

27

You should check out closures:

def rounder(func):
    def inner(*args, **kwargs):
        return round(func(*args, **kwargs))
    return inner

Then you can decorate functions using the @ character:

@rounder
def adder(x, y):
    return x + y

print(adder(1.1, 2.2))

outputs 3

Supplementary:

  1. You can use functools.wraps in your closure so you don't lose information (e.g. docstring, function name) about the original function.
  2. There are a bunch of resources for learning about closures (e.g. 1, 2) and decorators (e.g. 1, 2) that you can find by Googling those terms.
Alex
  • 18,484
  • 8
  • 60
  • 80
  • 13
    Hey look, a decorator! – Adam Smith Nov 07 '18 at 17:18
  • 1
    I cant understand anything. The answer should be more easier I think – php kubrick Nov 07 '18 at 17:21
  • 6
    @phpkubrick what? It's 3 lines of code and a function header? How much more simple would you like it? You mentioned you're in university -- you can understand this. – Adam Smith Nov 07 '18 at 17:21
  • 1
    I cant understand the *args, **kwargs – php kubrick Nov 07 '18 at 17:22
  • 6
    @phpkubrick I guess that's the next bit of research you should do then, because they're used *all over the place* in Python and are incredibly useful. https://stackoverflow.com/questions/36901/what-does-double-star-asterisk-and-star-asterisk-do-for-parameters – Adam Smith Nov 07 '18 at 17:23
  • So for example If I copy paste this on my idle will work? Because I thought that those was some parameters that I have to change – php kubrick Nov 07 '18 at 17:24
  • @Alex Making the fully general `rounder` function is really more complicated than needed to answer the question. You could start with `rounder` assuming `func` is a single argument function. – chepner Nov 07 '18 at 17:25
  • Could you also write it as returning a lambda function, i.e. `return (lambda *args, **kwargs: round(func(*args, **kwargs)))` ? – Daniel Schepler Nov 07 '18 at 18:37
  • @DanielSchepler You could, but there's little reason to do so. Usually, using `def` where possible makes code easier to read. – chepner Nov 07 '18 at 19:37
15

For your specific example, you can write

def round_sqrt(x):
    return round(sqrt(x))

Alex's answer generalizes this; he defines a function that creates round_sqrt for you. If the function is already defined, you just pass it as an argument to rounder:

round_sqrt = rounder(sqrt)

Of course, you don't need to define round_sqrt if you don't want to. rounder(sqrt)(3.2) can be called directly, although it's far more efficient to safe the return value of rounder if you expect to use it multiple times, rather than redefining it each time.

Otherwise, the decorator syntax is just short for (using Alex's example)

def adder(x, y):
    return x + y

adder = rounder(adder)

As I said in my comment, this is an example of implementing composition. Mathematically, composition is simple, because mathematical functions always take a single argument and return a single argument. As such, the composition of two functions f and g could always be defined simply as

def compose(f, g):
    def h(x):   # The name doesn't matter
        return f(g(x))
    return h

Then

round_sqrt = compose(round, sqrt)

(Ignoring all sorts of practical concerns around the implementation, Python could in theory even provide a Unicode operator for functions: round_sqrt = round ∘ sort. Explaining why this won't happen is beyond the scope of this answer.)

In Python, though, functions are far more complicated. They can take multiple arguments, they can accept arbitrary numbers of arguments and arbitrary keyword arguments, and while each technically returns a single value, that value can be a tuple which is thought of as multiple values or a dict. As a result, there may be many ways you might expect to pass the return value of g to a function f, more than can easily be accommodated in a simple compose function.

chepner
  • 497,756
  • 71
  • 530
  • 681
5

Function composition isn't supported natively in Python. You can use a decorator as per @Alex's solution. You can define a new function explicitly as per @chepner's solution.

Or you can use a 3rd party library. For example, via toolz.compose:

from toolz import compose

def adder(x, y):
    return x + y

round_adder = compose(round, adder)

round_adder(1.1, 2.2)  # 3
jpp
  • 159,742
  • 34
  • 281
  • 339