Python:builtins
Python에 내장된(builtins) 타입, 함수 등
exec
 오작동이 가장 적은 격리된 exec 호출 방법은 다음과 같다: 
from importlib import import_module
def get_annotations_compiler_flag() -> int:
    future = import_module("__future__")
    assert future is not None
    annotations = getattr(future, "annotations")
    assert annotations is not None
    compiler_flag = getattr(annotations, "compiler_flag")
    assert isinstance(compiler_flag, int)
    return compiler_flag
COMPILE_MODE_EXEC = "exec"
COMPILE_FLAGS = get_annotations_compiler_flag()
path = '/python/file/path.py'
global_variables = {}
with open(path, "r") as f:
    source = f.read()
ast = compile(source, path, COMPILE_MODE_EXEC, COMPILE_FLAGS)
exec(ast, global_variables, global_variables)
property
프로퍼티 어트리뷰트를 돌려줍니다.
-  
fget: 어트리뷰트 값을 얻는 함수입니다. -  
fset: 은 어트리뷰트 값을 설정하는 함수입니다. -  
fdel: 은 어트리뷰트 값을 삭제하는 함수입니다. -  
doc: 은 어트리뷰트의 독스트링을 만듭니다. 
class C:
    def __init__(self):
        self._x = None
    def getx(self):
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")
Troubleshooting
exec 에서 typing 패키지의 NameError 발생
exec() 함수 실행시 다음과 같은 Type Annotation 관련 에러가 발생할 수 있다.
 첫 번째 방법은 Annotations Future 를 Enable 하는 코드를 exec()로 넘겨줄 코드에 추가한다. 
 또는 compile() 호출 시 flags 변수에 위의 플래그를 추가하면 된다. 다음은 해당 플래그를 획득하는 방법이다. (자세한 내용은 관련 문서 참조): 
from importlib import import_module
def get_annotations_compiler_flag() -> int:
    future = import_module("__future__")
    assert future is not None
    annotations = getattr(future, "annotations")
    assert annotations is not None
    compiler_flag = getattr(annotations, "compiler_flag")
    assert isinstance(compiler_flag, int)
    return compiler_flag
exec 이후 coverage.py 오작동 발생
다음과 같은 AttributeError가 발생할 수 있다.
전체 Traceback은 다음과 같다:
Traceback (most recent call last):
  File "/home/yourid/Project/answer-dev/core/recc/http/http_app.py", line 178, in _runner
    await _run_app(
  File "/home/yourid/.pyenv/versions/opy-yourid-3.8.9/lib/python3.8/site-packages/aiohttp/web.py", line 319, in _run_app
    await runner.setup()
  File "/home/yourid/.pyenv/versions/opy-yourid-3.8.9/lib/python3.8/site-packages/aiohttp/web_runner.py", line 275, in setup
    self._server = await self._make_server()
  File "/home/yourid/.pyenv/versions/opy-yourid-3.8.9/lib/python3.8/site-packages/aiohttp/web_runner.py", line 375, in _make_server
    await self._app.startup()
  File "/home/yourid/.pyenv/versions/opy-yourid-3.8.9/lib/python3.8/site-packages/aiohttp/web_app.py", line 416, in startup
    await self.on_startup.send(self)
  File "/home/yourid/.pyenv/versions/opy-yourid-3.8.9/lib/python3.8/site-packages/aiohttp/signals.py", line 34, in send
    await receiver(*args, **kwargs)  # type: ignore
  File "/home/yourid/Project/answer-dev/core/recc/http/http_app.py", line 141, in on_startup
    await self._context.open()
  File "/home/yourid/Project/answer-dev/core/recc/core/context.py", line 85, in open
    self._plugins.create(self, *plugin_scripts)
  File "/home/yourid/Project/answer-dev/core/recc/plugin/plugin_manager.py", line 13, in create
    plugin = Plugin(file)
  File "/home/yourid/Project/answer-dev/core/recc/plugin/plugin.py", line 49, in __init__
    self._global_variables = exec_python_plugin(path)
  File "/home/yourid/Project/answer-dev/core/recc/plugin/plugin.py", line 40, in exec_python_plugin
    exec(ast, global_variables, local_variables)
  File "/home/yourid/.pyenv/versions/opy-yourid-3.8.9/lib/python3.8/site-packages/coverage/control.py", line 323, in _should_trace
    disp = self._inorout.should_trace(filename, frame)
  File "/home/yourid/.pyenv/versions/opy-yourid-3.8.9/lib/python3.8/site-packages/coverage/inorout.py", line 237, in should_trace
    filename = source_for_file(dunder_file)
  File "/home/yourid/.pyenv/versions/opy-yourid-3.8.9/lib/python3.8/site-packages/coverage/python.py", line 106, in source_for_file
    if filename.endswith(".py"):
AttributeError: 'PosixPath' object has no attribute 'endswith'
 이것은 #compile시, 파일명(filename)을 넘겨줄 때 pathlib.Path를 넘겨줬기 때문에 발생한다. #compile의 파일명(filename) 인자는 str으로 넘겨야 한다. 
exec 에서 Annotated assignment statements 사용시 NameError 발생
- Glossary — Python 3.9.7 documentation # variable annotation
 - 7. Simple statements — Python 3.9.7 documentation # 7.2.2. Annotated assignment statements
 
 exec() globals 와 locals 인자를 동일하게 사용하면 된다. 
exec 에서 dataclass 사용시 AttributeError 에러
dataclass 사용시 다음과 같은 에러가 발생될 수 있다:
전체 Traceback 은 다음과 같다:
Traceback (most recent call last):
  ...
  File "/home/yourid/Project/answer-dev/core/recc/plugin/plugin.py", line 39, in __init__
    exec_python_plugin(path, global_variables, global_variables)
  File "/home/yourid/Project/answer-dev/core/recc/plugin/plugin.py", line 30, in exec_python_plugin
    exec(ast, global_variables, local_variables)
  File "/home/yourid/Project/answer-dev/core/storage/plugin/a4y.py", line 517, in <module>
    class A4yA:
  File "/home/yourid/.pyenv/versions/3.8.9/lib/python3.8/dataclasses.py", line 1019, in dataclass
    return wrap(cls)
  File "/home/yourid/.pyenv/versions/3.8.9/lib/python3.8/dataclasses.py", line 1011, in wrap
    return _process_class(cls, init, repr, eq, order, unsafe_hash, frozen)
  File "/home/yourid/.pyenv/versions/3.8.9/lib/python3.8/dataclasses.py", line 861, in _process_class
    cls_fields = [_get_field(cls, name, type)
  File "/home/yourid/.pyenv/versions/3.8.9/lib/python3.8/dataclasses.py", line 861, in <listcomp>
    cls_fields = [_get_field(cls, name, type)
  File "/home/yourid/.pyenv/versions/3.8.9/lib/python3.8/dataclasses.py", line 712, in _get_field
    and _is_type(f.type, cls, typing, typing.ClassVar,
  File "/home/yourid/.pyenv/versions/3.8.9/lib/python3.8/dataclasses.py", line 658, in _is_type
    ns = sys.modules.get(cls.__module__).__dict__
AttributeError: 'NoneType' object has no attribute '__dict__'
파이썬 코드는 다음과 같다:
global_variables = {
    "__name__": "__recc_plugin__",
    "__file__": path,
}
with open(path, "r") as f:
    source = f.read()
ast = compile(source, path, COMPILE_MODE_EXEC, COMPILE_FLAGS)
exec(ast, global_variables, global_variables)
문제는, 다음과 같은 순서로, 연쇠반응한다:
-  전역 변수의 
__name__가 지정되면, 해당 이름의 모듈을 사용한다. -  
@dataclass로 넘겨지는cls아규먼트는 {모듈}.{클래스} 로 전달된다. -  따라서 위의 경우, 
cls는__recc_plugin__.A4yA이다. -  
cls.__module__은'__recc_plugin__'이다. -  
sys.modules.get(cls.__module__)를 사용하면__recc_plugin__이름의 모듈을 찾는다. - 결과, 모듈이 없으므로 None 이다.
 
 해결 방법은, global_variables로 넘겨지는 모든 값을 제거하면 적절한 값이 반영된다. (모듈은 builtins으로 지정되는듯 함) 
수정된 코드는 다음과 같다:
global_variables = {}
with open(path, "r") as f:
    source = f.read()
ast = compile(source, path, COMPILE_MODE_EXEC, COMPILE_FLAGS)
exec(ast, global_variables, global_variables)
참고로, 모듈 로드 방법은 Python:importlib 항목 참조.
 exec 에서 획득한 함수형 심볼의 __annotations__의 인스턴스가 Dict[str, type]가 아닌, Dict[str, str]인 현상 
 이 경우, #eval 함수를 사용하여 __annotations__를 재설정 하면 된다.
    def update_routes(self) -> None:
        routes = list()
        for method, path, guest_route in self._call_get_routes():
            host_route = deepcopy(guest_route)
            if hasattr(host_route, "__annotations__"):
                assert isinstance(host_route.__annotations__, dict)
                update_annotations: Dict[str, Any] = dict()
                for key, annotation in host_route.__annotations__.items():
                    if isinstance(annotation, type):
                        update_annotations[key] = annotation
                    else:
                        type_origin = eval(
                            annotation,
                            self._global_variables,
                            self._global_variables,
                        )
                        if type_origin is not None:
                            update_annotations[key] = type_origin
                        else:
                            update_annotations[key] = annotation
                host_route.__annotations__ = update_annotations
            routes.append(Route(method, path, host_route))
        self._routes = routes
See also
Favorite site
- [추천] 점프 투 파이썬 - 내장함수 1
 
References
-  
Jump_to_Python_-_Builtin_Functions.pdf ↩