Skip to content

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

프로퍼티 어트리뷰트를 돌려줍니다.

class property(fget=None, fset=None, fdel=None, doc=None)
  • 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 관련 에러가 발생할 수 있다.

NameError: name 'List' is not defined

첫 번째 방법은 Annotations Future 를 Enable 하는 코드를 exec()로 넘겨줄 코드에 추가한다.

from __future__ import annotations

또는 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가 발생할 수 있다.

AttributeError: 'PosixPath' object has no attribute 'endswith'

전체 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 발생

exec() globals 와 locals 인자를 동일하게 사용하면 된다.

exec 에서 dataclass 사용시 AttributeError 에러

dataclass 사용시 다음과 같은 에러가 발생될 수 있다:

AttributeError: 'NoneType' object has no attribute '__dict__'

전체 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

References


  1. Jump_to_Python_-_Builtin_Functions.pdf