> dynamically modify some of the scopes that names refer to
You can do some fairly disgusting things to name resolution in class bodies, but names within functions are resolved statically nowadays.
> as well as changing the actual prototype chain itself
You can change a class's MRO, if that's the closest analogue.
class Foo:
x = 'foo'
class Bar:
x = 'bar'
class Baz(Foo):
pass
print(Baz.x)
Baz.__bases__ = (Bar,)
print(Baz.x)
In Python you can also hook your own entire custom import system into importlib, or just arbitrarily change the meaning of the `import` statement by replacing builtins.__import__: >>> import builtins
>>> builtins.__import__ = lambda *a: "Too bad"
>>> import foo
>>> foo
'Too bad'
You can use sys._getframe() to look in the current call stack and poke at variables: import sys
def f():
x = 3
return g()
def g():
return sys._getframe().f_back.f_locals['x']
print(f()) # 3
You can make your own class that inherits from types.ModuleType and use it to replace an existing module's class and add interesting new behaviors to its object: # foo.py
import sys, types
class MyMod(types.ModuleType):
def __call__(self):
return "Hello!"
sys.modules['foo'].__class__ = MyMod
>>> import foo
>>> foo()
'Hello!'
You can replace sys.ps1 by an object with a __str__ implementation to make a dynamic prompt in the REPL: import datetime, sys
>>> class Prompt: __str__ = lambda self: str(datetime.datetime.now()) + ' >>>'
>>> sys.ps1 = Prompt()
2019-09-12 18:33:48.303692 >>>
Python has a lot of exposed detail.