3

In "Learning Python 5th Edition" by Mark Lutz - (ISBN: 9781449355739, chapter 17: "Scopes", p. 518, sidebar: "Why You Will Care: Customizing open") - there is the following illustration:

import builtins def makeopen(id): original = builtins.open def custom(*kargs, **pargs): print('Custom open call %r:' % id , kargs, pargs) return original(*kargs, **pargs) builtins.open = custom makeopen('spam') F = open('script2.py') makeopen('eggs') F = open('script2.py') 

Expected output:

Custom open call 'spam': ('script2.py',) {} Custom open call 'eggs': ('script2.py',) {} 

Actual output:

Custom open call 'spam': ('script2.py',) {} Custom open call 'eggs': ('script2.py',) {} Custom open call 'spam': ('script2.py',) {} 

My understanding of closures is that they are supposed to return multiple copy per-call changeable data (ie. like instance variables in other languages).

So why is "spam" printed twice ?

I have stepped through the code with the PyCharm debugger and I still do not understand it.

Is it because the variable original points to an object in the built-in scope instead of an enclosing scope ?

UPDATE:

I think the problem was that on the 2nd call to makeopen(), the variable original recursively points to custom(). Maybe it was originally intended as a "feature" :/ ... but I am inclined to think it is a terrible example.

Here's a solution that works as expected:

import builtins def makeopen(id): def custom(*kargs, **pargs): print('Custom open call %r:' % id , kargs, pargs) return builtins.open(*kargs, **pargs) return custom file = 'script2.py' f = makeopen('spam') f(file) g = makeopen('eggs') g(file) 

Note: The above solution does not actually change builtins.open but instead acts as a wrapper.

3
  • "return multiple-copy data" – what's that mean? Commented Feb 4, 2019 at 10:27
  • @deceze - I have reworded it to: "multiple copy per-call changeable data" (as used in the book). I believe it means that local variables in Python's enclosing functions are like instance variables in other languages like Java. Commented Feb 4, 2019 at 10:46
  • One way to "fix" this is to write def makeopen(id, original=builtins.open). This will save the actual original open. Commented Feb 4, 2019 at 10:52

1 Answer 1

4

After makeopen('spam'), open is a function which prints "spam" and then opens a file. After makeopen('eggs'), open is now a function which prints "eggs" and then calls a function which prints "spam" and then opens a file.

You're successively wrapping the open function in more and more layers, ending up with:

print("eggs") ↳ print("spam") ↳ open(...) 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.