STINNER Victor <vstin...@python.org> added the comment:

Guido: "I'm still worried about the change in semantics where 
globals["__builtins__"] is assigned a different dict after the function object 
has been created (...)"

Well, there is a semantics change of Python 3.10 documented at:
https://docs.python.org/dev/whatsnew/3.10.html#other-language-changes

"Functions have a new __builtins__ attribute which is used to look for builtin 
symbols when a function is executed, instead of looking into 
__globals__['__builtins__']. (Contributed by Mark Shannon in bpo-42990.)"

And the function __builtins__ attribute is read-only.


Your example is not affected by PR 24564 because the globals has the 
"__builtins__" key.


In Python 3.10, you can modify func.__builtins__ (new attribute):
---
def foo(s): return len(s)
code = foo.__code__
FunctionType = type(foo)
f = FunctionType(code, {"__builtins__": {"len": len}})
print(f("abc"))
f.__builtins__.clear()
print(f("abc"))
---

Output:
---
3
Traceback (most recent call last):
  (...)
NameError: name 'len' is not defined
---


Mark: "Because globals['__builtins__'] is cached for each function activation, 
executing functions don't see updates."

In Python 3.10, if someone wants to hack builtins while the function is 
running, modifying the builtins namespace in-place works as expected:
---
def f():
    print(len("test"))
    builtins_ns = f.__globals__['__builtins__'].__dict__
    #builtins_ns = f.__builtins__
    builtins_ns['len'] = lambda x: 7
    print(len("test"))

f()
---

Output:
---
4
7
---

It also works with "builtins_ns = f.__builtins__".


Guido: "I realize this is a pretty esoteric, but it does show the change in 
semantics (from later to earlier binding). Should we care? I like early binding 
because it allows more optimizations[1], but traditionally Python's semantics 
use late binding."

Modifying built-in functions/types is commonly done in tests. Example:
---
import unittest.mock

def func():
    with unittest.mock.patch('builtins.chr', return_value='mock'):
        return chr(65)

print(func())
---

The expected output is: "mock". Overriding an attribute of the builtins module 
immediately updates func.__builtins__. It works because func.__builtins__ is 
builtins.__dict__.

In FAT Python, I implemented an optimization which copies builtin functions to 
constants, replace LOAD_GLOBAL with LOAD_CONST:
https://fatoptimizer.readthedocs.io/en/latest/optimizations.html#copy-builtin-to-constant

This optimization breaks this Python semantics, it is no longer possible to 
override builtin functions in tests:
https://fatoptimizer.readthedocs.io/en/latest/semantics.html#builtin-functions-replaced-in-the-middle-of-a-function

----------

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue42990>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to