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 ↩