Python Forum
In this function y initially has no value, but a call to foo() gives no error. Why?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
In this function y initially has no value, but a call to foo() gives no error. Why?
#1
This is from Reuven Lerner's book Python Workout.

I don't really understand why I don't get an error when no value is passed to y.
Or why, the second time around, I get the expected result.
(I added the print()s to show what is happening.

def foo(x):
    def bar(y):
        print(f'y is {y}')
        print(f'x is {x}')
        print(f'x * y is {x*y}')
        return x * y
    print(f'bar is {bar}')
    return bar
I call the function with g = foo(2), but y has no value. I expected an error, but I get:

Quote:g = foo(2) # returns: bar is <function foo.<locals>.bar at 0x7ff37b475268>

Why is there no error when y has no value??

Normally, if a function requires an argument and none is supplied, you will get an error.

Now I try:

print(g(20))
but I don't get:

Quote:foo(20) # returns: bar is <function foo.<locals>.bar at 0x7ff37b475268>

I get the intended result:

Quote:y is 20
x is 2
x * y is 40
40

How did 20 get passed to y??
Reply
#2
The function foo returns the function bar, it does not call the function. You then use that return value to call function bar(). So when you called g(20) you were actually calling foo.bar(20).

The more interesting thing in this example is where does bar get a value for x? Why is x hanging around after foo is done executing? What is keeping garbage collection from throwing x away? Ponder that for a bit.

And if you are under the impression that each Python function has some static namespace where it keeps all this info, look at this example:
def foo(x):
    def bar(y):
        return x*y
    return bar

a = foo(10)
b = foo(20)
print(a(2), b(2))
Output:
20, 40
a is the function bar() with a context for bar() that remembers x == 10. b is also the function bar() with a context that remembers x == 20. Interesting.
Pedroski55 likes this post
Reply
#3
Well, "where does bar get a value for x?"

Reuven Lerner talked about LEGB: Python looks in: Local, Enclosing, Global, then Builtin for x

Still not sure I understand this, but I am a slow learner!
Reply
#4
This is a closure. A function inside a function.
The inner function has access to objects of the outer function.
The function bar access x from foo's scope.

def foo(x):
    def bar(y):
        return x*y
    return bar
Calling foo(), will return a function object:
Output:
<function __main__.foo.<locals>.bar(y)>
You've to call the returned function bar to get the return value from bar.

func = foo(13)
# or in one line
# result = foo(13)(42)
result = func(42)
print(result)
Output:
546
Pedroski55 likes this post
Almost dead, but too lazy to die: https://sourceserver.info
All humans together. We don't need politicians!
Reply
#5
(Dec-16-2020, 10:09 AM)Pedroski55 Wrote: Well, "where does bar get a value for x?"

Reuven Lerner talked about LEGB: Python looks in: Local, Enclosing, Global, then Builtin for x

Still not sure I understand this, but I am a slow learner!
LEGB describes why bar() can see x (though it cannot change x), but it does not explain why in my example a sees x=10 and b sees x=20. In C it is easy to pass s function pointer around, but that pointer does not come with it's own little piece of frozen space time. This is a very cool, and slightly frightening, demonstration of a feature that may be uniquely Python.
Reply
#6
Thanks a lot!

I'll let this sink in for a while, maybe I'll have a "Eureka!"

All I know is, try calling a function that requires 1 or more parameters, but don't pass a parameter, and you get this:

Quote:>>> foo()
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
foo()
TypeError: foo() missing 1 required positional argument: 'x'

Apparently, if the function is inside another function, you're OK.

Don't know when I might need this fuctionality, but I will remember it!
Reply
#7
Remember that calling a function and defining a function are two completely different things. The latter creates a function that can later be called and declares what parameters must be given at call time. Since there is no call to the function then, there's no checking of what parameters are declared (how could there be?). That is the same regardless of whether that definition is nested inside another function or not. Calling a function necessitates passing the right number of arguments and obviously checking against the definition only happens at call time. This happens regardless of what scope that call is in (whether inside a function or at a global level, for example).

Let's say you want to measure execution times of function calls and record those somewhere. Of course, you could just change all your functions to do that, but you'd be repeating similar code everywhere. Another approach is to write a function that takes as its argument a function f and produces a new one that calls f and does the measurement and recording of the time:

def measure_time(f):
  def measured():
     start_time = calculate_time()
     result = f()
     end_time = calculate_time()
     record_time(end_time - start_time)
     return result
  return measured
(I've used fictional functions and just taken the case that f takes no arguments, for the sake of simplicity).

This is known as a decorator and Python has syntax to decorate functions:

@measure_time
def do_something_useful():
  ...
Here, basically, with the presence of the @measure_time decorator, do_something_useful will really be the function that measure_time returns (i.e. one that does exactly the same as do_something_useful but with the additional recording of the execution time.
Pedroski55 likes this post
Reply
#8
Thanks!

A little further in my book, Reuven Lerner notes:

Quote:Working with inner functions and closures can be quite surprising and
confusing at first. That’s particularly true because our instinct is to believe
that when a function returns, its local variables and state all go away. Indeed,
that’s normally true—but remember that in Python, an object isn’t released
and garbage-collected if there’s at least one reference to it. And if the inner
function is still referring to the stack frame in which it was defined, then the
outer function will stick around as long as the inner function exists.

Then he gives this example of "function in function":

#! /usr/bin/python3
# password generator

import random

def create_password_generator(characters):
    def create_password(length):
        output = []
        for i in range(length):
            output.append(random.choice(characters))
        return ''.join(output)
    return create_password

alpha_password = create_password_generator('abcdef')
symbol_password = create_password_generator('!@#$%')

print(alpha_password(5))
print(alpha_password(10))

print(symbol_password(5))
print(symbol_password(10))
Things become clearer!
Reply
#9
You don't necessarily need the nested function for that; you can use partial application with partial from the functools module. Have create_password take both parameters, characters and length:

def create_password(characters, length):
  # Code as above
partial allows you to freeze the arguments of a function, returning a function with a lower arity:

from functools import partial

alpha_password = partial(create_password, characters='abcdef')
symbol_password = partial(create_password, characters='!@#$%')
Both alpha_password and symbol_password are functions that just take the length, as in your example.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  How do I call sys.argv list inside a function, from the CLI? billykid999 3 752 May-02-2023, 08:40 AM
Last Post: Gribouillis
  UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92 error from Mysql call AkaAndrew123 1 3,383 Apr-28-2021, 08:16 AM
Last Post: AkaAndrew123
  how to call an object in another function in Maya bstout 0 2,042 Apr-05-2021, 07:12 PM
Last Post: bstout
  Struggling for the past hour to define function and call it back godlyredwall 2 2,156 Oct-29-2020, 02:45 PM
Last Post: deanhystad
  list call problem in generator function using iteration and recursive calls postta 1 1,862 Oct-24-2020, 09:33 PM
Last Post: bowlofred
  function call at defined system time? Holon 5 3,154 Oct-06-2020, 03:58 PM
Last Post: snippsat
  How to call/read function for all elements in my list in python johnny_sav1992 1 2,038 Jul-27-2020, 04:19 PM
Last Post: buran
  Python: Call function with variabele? Ending in error. efclem 5 2,877 Apr-22-2020, 02:35 PM
Last Post: buran
  How to mock an object that is created during function call? Schlangenversteher 0 1,946 Jan-31-2020, 01:36 PM
Last Post: Schlangenversteher
  Is there a way to search for function call? mtran 2 2,220 Jan-14-2020, 02:07 AM
Last Post: mtran

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020