In this function y initially has no value, but a call to foo() gives no error. Why? - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: General Coding Help (https://python-forum.io/forum-8.html) +--- Thread: In this function y initially has no value, but a call to foo() gives no error. Why? (/thread-31506.html) |
In this function y initially has no value, but a call to foo() gives no error. Why? - Pedroski55 - Dec-16-2020 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 barI 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 How did 20 get passed to y?? RE: In this function y initially has no value, but a call to foo() gives no error. Why? - deanhystad - Dec-16-2020 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)) 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.
RE: In this function y initially has no value, but a call to foo() gives no error. Why? - Pedroski55 - Dec-16-2020 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! RE: In this function y initially has no value, but a call to foo() gives no error. Why? - DeaD_EyE - Dec-16-2020 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 barCalling foo(), will return a function object: 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)
RE: In this function y initially has no value, but a call to foo() gives no error. Why? - deanhystad - Dec-16-2020 (Dec-16-2020, 10:09 AM)Pedroski55 Wrote: Well, "where does bar get a value for x?"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. RE: In this function y initially has no value, but a call to foo() gives no error. Why? - Pedroski55 - Dec-16-2020 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() 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! RE: In this function y initially has no value, but a call to foo() gives no error. Why? - ndc85430 - Dec-17-2020 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.
RE: In this function y initially has no value, but a call to foo() gives no error. Why? - Pedroski55 - Dec-17-2020 Thanks! A little further in my book, Reuven Lerner notes: Quote:Working with inner functions and closures can be quite surprising and 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! RE: In this function y initially has no value, but a call to foo() gives no error. Why? - ndc85430 - Dec-19-2020 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.
|