====== Semana 49 de 2024 ======
===== Capacitação =====
* AWS
* Início do curso "AWS Cloud Technical Essentials".
* Criação de conta no https://repost.aws. O re:Post é um fórum para perguntas sobre o AWS.
===== Pesquisa =====
* Correção automática de programas
* Um problema que tive recentemente foi quanto a redefinição de módulos (e suas funções) da biblioteca básica do Python. As funções que estão no módulo ''builtins'' é simples redefinir (basta definir a função manualmente, no escopo global), mas um módulo é importado durante a execução do programa (e qualquer definição no escopo global anterior seria sobreescrita).
* Normalmente, poderíamos usar dublês (mocks) para sobrepor o comportamento de alguma função ou método de uma classe. No entanto, isso não é tecnicamente possível (ao menos com as ferramentas atuais) para os módulos e funções da biblioteca básica/padrão do Python (builtin).
* Mais especificamente, eu precisava sobreescrever o comportamento da função ''time'' do módulo ''time''.
* Existe mais um adendo de que, por ser para uma disciplina introdutório, eu não queria criar um objeto ou forçar uma abstração além daquilo que os estudantes, que estão aprendendo o básico da programação, precisam.
* Em tese, seria suficiente criar um arquivo '''time.py''' com a função em questão. Isso seria verdade se, no ambiente de avaliação automática que utilizo, o CodeRunner do Moodle, isso fosse permitido.
* Assim, resolvi ser criativo e verificar como ocorre o carregamento dos módulos e se existia algo que eu pudesse trabalhar naquele nível. Eis que os problemas surgem :-)
* A implementação padrão do Python, CPython, está disponível de forma aberta em https://github.com/python/cpython. Outro aspecto positivo é que, embora a implementação padrão do ''import'' seja em C (Python/import.c, em https://github.com/python/cpython), o carregamento de módulos também pode ser realizado por código escrito em Python. O módulo ''importlib'' possui tudo que é necessário: https://github.com/python/cpython/blob/main/Lib/importlib/. Não é um módulo bonito e fácil de ler, mas servirá como inspiração para uma solução.
* Um desenvolvedor criou um módulo [[https://pypi.org/project/module-found | ''module-found'']] que, usando o ''importlib'', faz algo bem legal: ele utiliza uma aplicação de modelo de linguagem (LLM), mais precisamente um modelo da OpenAI, para gerar automaticamente o código em Python, considerando uma descrição informada como parâmetro, referente a uma função de um módulo que não existem em Python. Ou seja, ao invés de lançar uma exceção de que o módulo ou função não foi encontrado, ele cria automaticamente o módulo e a função necessários. No caso, eu não preciso gerar automaticamente código, dado que eu sei exatamente o que eu preciso. No entanto, o mecanismo é o mesmo (com a diferença que eu quero sobreescrever um módulo ou função existente).
* Só para complementar, outro módulo Python que faz algo nessa linha, no caso instalando automaticamente (em tempo de execução) os módulos não encontrados é o [[https://pypi.org/project/pipimport | ''pipimport'']].
* A importação de pacotes do Python até que é simples de entender, sob certo aspecto. Existe uma classe para encontrar módulos e outra classe para carregar módulos.
* Para encontrar os módulos, são utilizadas as classes BuiltinImporter, FrozenImporter e PathFinder. As duas primeiras são para carregar os módulos padrões do Python e a terceira é para carregar os módulos disponíveis no sistema de arquivo (este último utiliza a variável ''sys.path'', amplamente empregada para configuração de ambientes virtuais).
* As classes a serem utilizadas estão definidas na variável ''sys.meta_path''. Basicamente, ao procurar por um nome (módulo, função ou variável), o Python percorre os objetos disponíveis nessa variável, utilizando-os para encontrar o que deseja.
* Entendido isso, bastaria criar uma nova classe Finder e colocá-la no início do caminho.
* Bom, nem tudo são flores. Por algum motivo muito estranho, **alguns** módulos builtins do Python não podem ser sobreescritos por esse mecanismo. Mais especificamente o módulo ''time'' não pode. No código de importação do módulo ''importlib'' é feito um grande esforço para não permitir a sobreescrita de módulos builtins. Na função ''_find_and_load()'' do ''_bootstrap.py'', antes de começar a busca pelo módulo é verificado se ele está presente em ''sys.modules'' (um dicionário de nome de módulo para o módulo em si). Por exemplo, hoje, em minha máquina, tenho os seguintes módulos nessa variável (e olha o ''time'' lá no fim!): _abc, abc, abrt_exception_handler3, _ast, ast, atexit, builtins, _codecs, codecs, _collections, collections, _collections_abc, collections.abc, contextlib, copyreg, _datetime, datetime, dis, _distutils_hack, encodings, encodings.aliases, encodings.utf_8, encodings.utf_8_sig, enum, errno, _frozen_importlib, _frozen_importlib_external, _functools, functools, __future__, genericpath, google, _imp, importlib, importlib._abc, importlib._bootstrap, importlib._bootstrap_external, importlib.machinery, importlib.util, inspect, _io, io, itertools, keyword, linecache, logging, __main__, marshal, _opcode, opcode, _operator, operator, os, os.path, paste, platform, posix, posixpath, re, readline, re._casefix, re._compiler, re._constants, re._parser, reprlib, rlcompleter, _signal, site, _sitebuiltins, _sre, _stat, stat, _string, string, sys, syslog, systemd, systemd.id128, systemd._journal, systemd.journal, systemd._reader, textwrap, _thread, threading, **time**, token, _tokenize, tokenize, traceback, types, _uuid, uuid, _warnings, warnings, _weakref, weakref, _weakrefset, zipimport.
* Minha primeira abordagem foi alterar o código da importação para que não ocorresse mais esse impedimento de sobreescrever elementos builtins. Isso funcionou! Colocarei o código a seguir, que sobreescreve o ''time.time()''. O código compreende a criação de duas classes, ''MissingNameFinder'' e ''MissingLoader'', responsáveis por procurar o nome e, ao não encontrá-lo, usar o ''MissingLoader'' para criar, em tempo de execução, o módulo (classe ''LazyModule''), constante ou função (classe ''LazyFunction''). O que sobrou é código aproveitado do ''importlib'':
from typing import Any, Callable, Dict, List, Optional
import importlib
import sys
import importlib.abc
import _imp
def _verbose_message(message, *args, verbosity=1):
"""Print the message to stderr if -v/PYTHONVERBOSE is turned on."""
if sys.flags.verbose >= verbosity:
if not message.startswith(('#', 'import ')):
message = '# ' + message
print(message.format(*args), file=sys.stderr)
def _find_spec(name, path, target=None):
"""Find a module's spec."""
meta_path = sys.meta_path
if meta_path is None:
# PyImport_Cleanup() is running or has been called.
raise ImportError("sys.meta_path is None, Python is likely shutting down")
if not meta_path:
_warnings.warn('sys.meta_path is empty', ImportWarning)
# We check sys.modules here for the reload case. While a passed-in
# target will usually indicate a reload there is no guarantee, whereas
# sys.modules provides one.
is_reload = name in sys.modules
for finder in meta_path:
try:
find_spec = finder.find_spec
except AttributeError:
continue
else:
spec = find_spec(name, path, target)
if spec is not None:
# The parent import may have already imported this module.
if not is_reload and name in sys.modules:
module = sys.modules[name]
try:
__spec__ = module.__spec__
except AttributeError:
# We use the found spec since that is the one that
# we would have used if the parent module hadn't
# beaten us to the punch.
return spec
else:
if __spec__ is None:
return spec
else:
return __spec__
else:
return spec
else:
return None
def _sanity_check(name, package, level):
"""Verify arguments are "sane"."""
if not isinstance(name, str):
raise TypeError(f'module name must be str, not {type(name)}')
if level < 0:
raise ValueError('level must be >= 0')
if level > 0:
if not isinstance(package, str):
raise TypeError('__package__ not set to a string')
elif not package:
raise ImportError('attempted relative import with no known parent package')
if not name and level == 0:
raise ValueError('Empty module name')
def _resolve_name(name, package, level):
"""Resolve a relative module name to an absolute one."""
bits = package.rsplit('.', level - 1)
if len(bits) < level:
raise ImportError('attempted relative import beyond top-level package')
base = bits[0]
return f'{base}.{name}' if name else base
def _init_module_attrs(spec, module, *, override=False):
# The passed-in module may be not support attribute assignment,
# in which case we simply don't set the attributes.
# __name__
if (override or getattr(module, '__name__', None) is None):
try:
module.__name__ = spec.name
except AttributeError:
pass
# __loader__
if override or getattr(module, '__loader__', None) is None:
loader = spec.loader
if loader is None:
# A backward compatibility hack.
if spec.submodule_search_locations is not None:
if _bootstrap_external is None:
raise NotImplementedError
NamespaceLoader = _bootstrap_external.NamespaceLoader
loader = NamespaceLoader.__new__(NamespaceLoader)
loader._path = spec.submodule_search_locations
spec.loader = loader
# While the docs say that module.__file__ is not set for
# built-in modules, and the code below will avoid setting it if
# spec.has_location is false, this is incorrect for namespace
# packages. Namespace packages have no location, but their
# __spec__.origin is None, and thus their module.__file__
# should also be None for consistency. While a bit of a hack,
# this is the best place to ensure this consistency.
#
# See # https://docs.python.org/3/library/importlib.html#importlib.abc.Loader.load_module
# and bpo-32305
module.__file__ = None
try:
module.__loader__ = loader
except AttributeError:
pass
# __package__
if override or getattr(module, '__package__', None) is None:
try:
module.__package__ = spec.parent
except AttributeError:
pass
# __spec__
try:
module.__spec__ = spec
except AttributeError:
pass
# __path__
if override or getattr(module, '__path__', None) is None:
if spec.submodule_search_locations is not None:
# XXX We should extend __path__ if it's already a list.
try:
module.__path__ = spec.submodule_search_locations
except AttributeError:
pass
# __file__/__cached__
if spec.has_location:
if override or getattr(module, '__file__', None) is None:
try:
module.__file__ = spec.origin
except AttributeError:
pass
if override or getattr(module, '__cached__', None) is None:
if spec.cached is not None:
try:
module.__cached__ = spec.cached
except AttributeError:
pass
return module
def module_from_spec(spec):
"""Create a module based on the provided spec."""
# Typically loaders will not implement create_module().
module = None
if hasattr(spec.loader, 'create_module'):
# If create_module() returns `None` then it means default
# module creation should be used.
module = spec.loader.create_module(spec)
elif hasattr(spec.loader, 'exec_module'):
raise ImportError('loaders that define exec_module() '
'must also define create_module()')
if module is None:
module = _new_module(spec.name)
_init_module_attrs(spec, module)
return module
def _load_unlocked(spec):
# A helper for direct use by the import system.
if spec.loader is not None:
# Not a namespace package.
if not hasattr(spec.loader, 'exec_module'):
msg = (f"{_object_name(spec.loader)}.exec_module() not found; "
"falling back to load_module()")
_warnings.warn(msg, ImportWarning)
return _load_backward_compatible(spec)
module = module_from_spec(spec)
# This must be done before putting the module in sys.modules
# (otherwise an optimization shortcut in import.c becomes
# wrong).
spec._initializing = True
try:
sys.modules[spec.name] = module
try:
if spec.loader is None:
if spec.submodule_search_locations is None:
raise ImportError('missing loader', name=spec.name)
# A namespace package so do nothing.
else:
spec.loader.exec_module(module)
except:
try:
del sys.modules[spec.name]
except KeyError:
pass
raise
# Move the module to the end of sys.modules.
# We don't ensure that the import-related module attributes get
# set in the sys.modules replacement case. Such modules are on
# their own.
module = sys.modules.pop(spec.name)
sys.modules[spec.name] = module
_verbose_message('import {!r} # {!r}', spec.name, spec.loader)
finally:
spec._initializing = False
return module
def _find_and_load_unlocked(name, import_):
path = None
parent = name.rpartition('.')[0]
parent_spec = None
if parent:
if parent not in sys.modules:
_call_with_frames_removed(import_, parent)
# Crazy side-effects!
if name in sys.modules:
return sys.modules[name]
parent_module = sys.modules[parent]
try:
path = parent_module.__path__
except AttributeError:
msg = f'{_ERR_MSG_PREFIX}{name!r}; {parent!r} is not a package'
raise ModuleNotFoundError(msg, name=name) from None
parent_spec = parent_module.__spec__
child = name.rpartition('.')[2]
spec = _find_spec(name, path)
if spec is None:
raise ModuleNotFoundError(f'{_ERR_MSG_PREFIX}{name!r}', name=name)
else:
if parent_spec:
# Temporarily add child we are currently importing to parent's
# _uninitialized_submodules for circular import tracking.
parent_spec._uninitialized_submodules.append(child)
try:
module = _load_unlocked(spec)
finally:
if parent_spec:
parent_spec._uninitialized_submodules.pop()
if parent:
# Set the module as an attribute on its parent.
parent_module = sys.modules[parent]
try:
setattr(parent_module, child, module)
except AttributeError:
msg = f"Cannot set an attribute on {parent!r} for child module {child!r}"
_warnings.warn(msg, ImportWarning)
return module
def _find_and_load(name, import_):
"""Find and load the module."""
# Optimization: we avoid unneeded module locking if the module
# already exists in sys.modules and is fully initialized.
# module = sys.modules.get(name, _NEEDS_LOADING)
return _find_and_load_unlocked(name, import_)
if module is None:
message = f'import of {name} halted; None in sys.modules'
raise ModuleNotFoundError(message, name=name)
return module
def _gcd_import(name, package=None, level=0):
"""Import and return the module based on its name, the package the call is
being made from, and the level adjustment.
This function represents the greatest common denominator of functionality
between import_module and __import__. This includes setting __package__ if
the loader did not.
"""
_sanity_check(name, package, level)
if level > 0:
name = _resolve_name(name, package, level)
return _find_and_load(name, _gcd_import)
def __import__(name, globals=None, locals=None, fromlist=(), level=0):
"""Import a module.
The 'globals' argument is used to infer where the import is occurring from
to handle relative imports. The 'locals' argument is ignored. The
'fromlist' argument specifies what should exist as attributes on the module
being imported (e.g. ``from module import ``). The 'level'
argument represents the package location to import from in a relative
import (e.g. ``from ..pkg import mod`` would have a 'level' of 2).
"""
if level == 0:
module = _gcd_import(name)
else:
globals_ = globals if globals is not None else {}
package = _calc___package__(globals_)
module = _gcd_import(name, package, level)
if not fromlist:
# Return up to the first dot in 'name'. This is complicated by the fact
# that 'name' may be relative.
if level == 0:
return _gcd_import(name.partition('.')[0])
elif not name:
return module
else:
# Figure out where to slice the module's name up to the first dot
# in 'name'.
cut_off = len(name) - len(name.partition('.')[0])
# Slice end needs to be positive to alleviate need to special-case
# when ``'.' not in name``.
return sys.modules[module.__name__[:len(module.__name__)-cut_off]]
elif hasattr(module, '__path__'):
return _handle_fromlist(module, fromlist, _gcd_import)
else:
return module
class ModuleSpec:
"""The specification for a module, used for loading.
A module's spec is the source for information about the module. For
data associated with the module, including source, use the spec's
loader.
`name` is the absolute name of the module. `loader` is the loader
to use when loading the module. `parent` is the name of the
package the module is in. The parent is derived from the name.
`is_package` determines if the module is considered a package or
not. On modules this is reflected by the `__path__` attribute.
`origin` is the specific location used by the loader from which to
load the module, if that information is available. When filename is
set, origin will match.
`has_location` indicates that a spec's "origin" reflects a location.
When this is True, `__file__` attribute of the module is set.
`cached` is the location of the cached bytecode file, if any. It
corresponds to the `__cached__` attribute.
`submodule_search_locations` is the sequence of path entries to
search when importing submodules. If set, is_package should be
True--and False otherwise.
Packages are simply modules that (may) have submodules. If a spec
has a non-None value in `submodule_search_locations`, the import
system will consider modules loaded from the spec as packages.
Only finders (see importlib.abc.MetaPathFinder and
importlib.abc.PathEntryFinder) should modify ModuleSpec instances.
"""
def __init__(self, name, loader, *, origin=None, loader_state=None,
is_package=None):
self.name = name
self.loader = loader
self.origin = origin
self.loader_state = loader_state
self.submodule_search_locations = [] if is_package else None
self._uninitialized_submodules = []
# file-location attributes
self._set_fileattr = False
self._cached = None
def __repr__(self):
args = [f'name={self.name!r}', f'loader={self.loader!r}']
if self.origin is not None:
args.append(f'origin={self.origin!r}')
if self.submodule_search_locations is not None:
args.append(f'submodule_search_locations={self.submodule_search_locations}')
return f'{self.__class__.__name__}({", ".join(args)})'
def __eq__(self, other):
smsl = self.submodule_search_locations
try:
return (self.name == other.name and
self.loader == other.loader and
self.origin == other.origin and
smsl == other.submodule_search_locations and
self.cached == other.cached and
self.has_location == other.has_location)
except AttributeError:
return NotImplemented
@property
def cached(self):
if self._cached is None:
if self.origin is not None and self._set_fileattr:
if _bootstrap_external is None:
raise NotImplementedError
self._cached = _bootstrap_external._get_cached(self.origin)
return self._cached
@cached.setter
def cached(self, cached):
self._cached = cached
@property
def parent(self):
"""The name of the module's parent."""
if self.submodule_search_locations is None:
return self.name.rpartition('.')[0]
else:
return self.name
@property
def has_location(self):
return self._set_fileattr
@has_location.setter
def has_location(self, value):
self._set_fileattr = bool(value)
def spec_from_loader(name, loader, *, origin=None, is_package=None):
"""Return a module spec based on various loader methods."""
if origin is None:
origin = getattr(loader, '_ORIGIN', None)
if not origin and hasattr(loader, 'get_filename'):
if _bootstrap_external is None:
raise NotImplementedError
spec_from_file_location = _bootstrap_external.spec_from_file_location
if is_package is None:
return spec_from_file_location(name, loader=loader)
search = [] if is_package else None
return spec_from_file_location(name, loader=loader,
submodule_search_locations=search)
if is_package is None:
if hasattr(loader, 'is_package'):
try:
is_package = loader.is_package(name)
except ImportError:
is_package = None # aka, undefined
else:
# the default
is_package = False
return ModuleSpec(name, loader, origin=origin, is_package=is_package)
def _spec_from_module(module, loader=None, origin=None):
# This function is meant for use in _setup().
try:
spec = module.__spec__
except AttributeError:
pass
else:
if spec is not None:
return spec
name = module.__name__
if loader is None:
try:
loader = module.__loader__
except AttributeError:
# loader will stay None.
pass
try:
location = module.__file__
except AttributeError:
location = None
if origin is None:
if loader is not None:
origin = getattr(loader, '_ORIGIN', None)
if not origin and location is not None:
origin = location
try:
cached = module.__cached__
except AttributeError:
cached = None
try:
submodule_search_locations = list(module.__path__)
except AttributeError:
submodule_search_locations = None
spec = ModuleSpec(name, loader, origin=origin)
spec._set_fileattr = False if location is None else (origin == location)
spec.cached = cached
spec.submodule_search_locations = submodule_search_locations
return spec
class MissingNameFinder(importlib.abc.MetaPathFinder):
def __init__(self) -> None:
self.forbidden_modules = []
self.builtin_modules = sys.builtin_module_names
self.allowed_builtin_modules_override = ["time"]
self.loader = MissingLoader()
def _spec_from_loader(self, name, loader, *, origin=None, is_package=None):
"""Return a module spec based on various loader methods."""
if origin is None:
origin = getattr(loader, '_ORIGIN', None)
if not origin and hasattr(loader, 'get_filename'):
if _bootstrap_external is None:
raise NotImplementedError
spec_from_file_location = _bootstrap_external.spec_from_file_location
if is_package is None:
return spec_from_file_location(name, loader=loader)
search = [] if is_package else None
return spec_from_file_location(name, loader=loader, submodule_search_locations=search)
if is_package is None:
if hasattr(loader, 'is_package'):
try:
is_package = loader.is_package(name)
except ImportError:
is_package = None # aka, undefined
else:
# the default
is_package = False
return ModuleSpec(name, loader, origin=origin, is_package=is_package)
def find_spec(self, fullname, path, target = None):
if fullname == "time" or fullname == "time30":
return self._spec_from_loader(fullname, self.loader)
else:
return None
if path:
return None
if '.' in fullname or fullname in self.forbidden_modules:
return None
if fullname in self.builtin_modules:
if fullname in self.allowed_builtin_modules_override:
return importlib.util.spec_from_loader(fullname, self.loader)
else:
return None
return self._spec_from_loader(fullname, self.loader)
class MissingLoader():
def __init__(self) -> None:
pass
def create_module(self, spec):
return LazyModule(spec.name)
def exec_module(self, _module):
pass
class LazyModule():
def __init__(self, name: str) -> None:
self.name = name
self._values: Dict[str, Any] = {}
self._existing_lines: List[str] = []
def __getattr__(self, name: str) -> Any:
existing = self._values.get(name, None)
if existing:
return existing
if name.upper() == name:
value = 1731593707.2131279
value = self._generate_constant(value, value)
else:
value = """def time():
return 1731593707.2131279"""
value = self._generate_function(name, value)
return value
def _generate_constant(self, constant_name: str, constant_value: Any) -> Any:
self._values[constant_name] = constant_value
self.add_code("%s = %s" % (constante_name, constant_string))
return constant_value
def _generate_function(self, function_name: str, function_code: str) -> Callable:
return LazyFunction(function_name, function_code, self)
@property
def code(self):
return '\n'.join(self._existing_lines)
def add_code(self, code: str):
exec(code, self._values)
self._existing_lines.extend(code.splitlines())
self._existing_lines.append('')
def get_value(self, name: str) -> Any:
return self._values[name]
class LazyFunction:
def __init__(self, name: str, code: str, parent_module: LazyModule) -> None:
self.name = name
self.code = code
self._parent_module = parent_module
self._generated_function: Optional[Callable] = None
def __call__(self, *args, **kwargs) -> Any:
if self._generated_function:
return self._generated_function(*args, **kwargs)
self._parent_module.add_code(self.code)
self._generated_function = self._parent_module.get_value(self.name)
return self._generated_function(*args, **kwargs)
def __iter__(self):
return self
def __next__(self):
raise StopIteration
# name_finder = MissingNameFinder()
# sys.meta_path.insert(0, name_finder)
# time = __import__("time")
* Minha segunda abordagem foi perceber que eu poderia apagar o módulo ''time'' da variável ''sys.modules''. Com isso eu tenho um código bem mais enxuto:
from typing import Any, Callable, Dict, List, Optional
import importlib
import sys
import importlib.abc
class MissingNameFinder(importlib.abc.MetaPathFinder):
def __init__(self) -> None:
self.forbidden_modules = []
self.builtin_modules = sys.builtin_module_names
self.allowed_builtin_modules_override = ["time"]
self.loader = MissingLoader()
def find_spec(self, fullname, path, target = None):
if fullname == "time" or fullname == "time30":
return importlib.util.spec_from_loader(fullname, self.loader)
else:
return None
if path:
return None
if '.' in fullname or fullname in self.forbidden_modules:
return None
if fullname in self.builtin_modules:
if fullname in self.allowed_builtin_modules_override:
return importlib.util.spec_from_loader(fullname, self.loader)
else:
return None
return importlib.util.spec_from_loader(fullname, self.loader)
class MissingLoader():
def __init__(self) -> None:
pass
def create_module(self, spec):
return LazyModule(spec.name)
def exec_module(self, _module):
pass
class LazyModule():
def __init__(self, name: str) -> None:
self.name = name
self._values: Dict[str, Any] = {}
self._existing_lines: List[str] = []
def __getattr__(self, name: str) -> Any:
existing = self._values.get(name, None)
if existing:
return existing
if name.upper() == name:
value = 1731593707.2131279
value = self._generate_constant(value, value)
else:
value = """def time():
return 1731593707.2131279"""
value = self._generate_function(name, value)
return value
def _generate_constant(self, constant_name: str, constant_value: Any) -> Any:
self._values[constant_name] = constant_value
self.add_code("%s = %s" % (constante_name, constant_string))
return constant_value
def _generate_function(self, function_name: str, function_code: str) -> Callable:
return LazyFunction(function_name, function_code, self)
@property
def code(self):
return '\n'.join(self._existing_lines)
def add_code(self, code: str):
exec(code, self._values)
self._existing_lines.extend(code.splitlines())
self._existing_lines.append('')
def get_value(self, name: str) -> Any:
return self._values[name]
class LazyFunction:
def __init__(self, name: str, code: str, parent_module: LazyModule) -> None:
self.name = name
self.code = code
self._parent_module = parent_module
self._generated_function: Optional[Callable] = None
def __call__(self, *args, **kwargs) -> Any:
if self._generated_function:
return self._generated_function(*args, **kwargs)
self._parent_module.add_code(self.code)
self._generated_function = self._parent_module.get_value(self.name)
return self._generated_function(*args, **kwargs)
def __iter__(self):
return self
def __next__(self):
raise StopIteration
name_finder = MissingNameFinder()
sys.meta_path.insert(0, name_finder)
del(sys.modules["time"])
time = __import__("time"!
print(1731593707.2131279)
print(time.time())
* Bom, em condições normais isso deveria funcionar. Agora já tenho um bom mecanismo para sobreescrever os módulos e suas funções em tempo de execução!