0

I'm learning the concepts of first class functions and closures in Python and I was curious to know:

Given a higher-order function:

def html_tag(tag):
    def wrap_text(text):
        print("<{0}>{1}</{0}>".format(tag, text))
    return wrap_text
  1. What's the difference between these two approaches of executing higher-order functions.
  2. Are there any advantages of using one over the other.
  3. What is the best way to execute a higher-order function in a program.

1.

print_h1 = html_tag('h1')
print_h1('Test Headline')
print_h1('Another Headline')

2.

html_tag('h1')('Test Headline')
html_tag('h1')('Another Headline')

Dave Kalu
  • 1,520
  • 3
  • 19
  • 38

3 Answers3

1

The only advantage I can think of is that by assigning the return value of html_tag to a variable in the first example, you prevent the code from having to execute again and return a new function each time. Whereas in your second example, you are calling html_tag directly and it will produce a new function reference each time, which will result in a decrease in performance.

Depends on your usage, but if you're passing in the same argument to html_tag, then I would go with your first example. But if you need to use a different tag, then obviously you would have to re-call html_tag again with a different argument.

In terms of the references to the function, this could be important, for example, if you were for some reason storing the function in a dict, so you wouldn't be able to lookup the function as a key unless you keep around a reference to the same function (as in your first example)

awarrier99
  • 3,628
  • 1
  • 12
  • 19
  • 1
    Are there any performance benefits though? Thanks for the quick response. – Dave Kalu May 15 '20 at 01:37
  • 1
    I would say that there is the performance benefit of not having to create a new function object each time in the first example, though I'm not sure how significant this would be. The more you reuse the returned function though, this benefit would increase – awarrier99 May 15 '20 at 01:39
1

In the example you give, there's no difference in the result. The second way is less efficient, since you're creating an equivalent function multiple times, which is redundant.

It becomes more relevant when you have functions that keep state between runs.

def counting_tag(tag):
    counter = 0
    def wrap_text(text):
        nonlocal counter
        counter += 1
        print("<{0}>{1}. {2}</{0}>".format(tag, counter, text))

Every time you call counting_tag() it will return a function with the counter reset back to 0.

print_h1 = counting_tag('h1')
print_h1('Test Headline')
print_h1('Another Headline')

This will print

1. Test Headline
2. Another Headline

But if you do it the second way:

counting_tag('h1')('Test Headline')
counting_tag('h1')('Another Headline')

you'll get

1. Test Headline
1. Another Headline
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Oh wow. Makes a lot of sense. So would you say the first approach is the preferred way to go owing to the fact that it has some added advantage over the second one. But if the second approach is in any way useful, could you please give an example use-case where it makes sense to use it. Thanks. – Dave Kalu May 15 '20 at 01:45
  • The first way is the normal way to use high order functions. The second way is only interesting as examples. – Barmar May 15 '20 at 01:48
  • I tried executing the code example you gave and I got an error `UnboundLocalError: local variable 'counter' referenced before assignment`. How can I fix this? Thanks. – Dave Kalu May 15 '20 at 05:29
  • 1
    I forgot to add the `nonlocal` declaration for the variable. – Barmar May 15 '20 at 15:02
1

The reason you'd have a higher order function would typically be so that you could easily generate named helper functions like print_h1, as in your first example. This has two benefits:

  1. You avoid repeating the 'h1' string. Any time you have to copy and paste a string literal multiple places, it's an invitation for a bug to happen due to a typo!
  2. Less typing. (This is more like a secondary benefit of not repeating yourself.)

If you were going to re-invoke html_tag each time, as in your second example, making a higher order function offers no benefit over simply doing:

def html_tag(tag, text):
    print("<{0}>{1}</{0}>".format(tag, text))


html_tag('h1', 'Test Headline')
html_tag('h1', 'Another Headline')
Samwise
  • 68,105
  • 3
  • 30
  • 44