티스토리 뷰
def tag(name, *content, cls=None, **attrs):
"""HTML 테그 만들기"""
if cls is None:
attrs['class'] = cls
if attrs:
attr_str = ''.join(f' {attr}="{value}"'
for attr, value
in sorted(attrs.items()))
else:
attr_str = ''
if content:
return '\n'.join(f'<{name}{attr_str}>{c}</{name}>'
for c in content)
else:
return f'<{name}{attr_str}/>'
print(tag('h1', 'HelloWorld!', cls='navbar'))
>>><h1 class="navbar">HelloWorld!</h1>
위 코드는 책에서 본 HTML 코드를 만드는 함수인데
필자는 이 코드를 보고 한 번에 이해하지 못했다.
충격적인 사실이지만 난 아직도 가변 개수의 인수를 전달하는(*content, **attrs)에 대해서 헷갈려하고 있는지라
내가 보낸 인수가 어떤 매개변수에 대입되는지 정확하게 파악하지 못했다.
이런 불쌍한 나를 위해 지혜로운 책은
함수에 어떤 매개변수가 필요한지, 매개변수에 기본값이 있는지 없는지 등등
함수의 여러 가지 속성에 접근하는 방법에 대해 알려줬다.
code객체 읽기 전용 어트리뷰트
from test import tag # 테그 함수를 가져옴
print(tag.__code)
print(tag.__code__.co_varnames)
print(tag.__code__.co_argcount)
>>><code object tag at 0x1051...>
>>>('name', 'cls', 'content', 'attrs')
>>>1
먼저 __code__매직 메서드로 여러 속성을 담고 있는 code객체에 접근한 뒤
읽기 전용 어트리뷰트를(Special read-only attributes) 통해서 함수 내부 속성들에 접근할 수 있다.
첫 번째로 사용한 co_varnames는
tag함수가 가지고 있는 매개변수들의 이름과 local변수들을 튜플형으로 확인할 수 있다.
참고로 tag함수에는 local변수가 없기 때문에 매개변수들만 보인다.
('name', 'cls, 'content', 'attrs')
def tag(name, *content, cls=None, **attrs):
"""HTML 테그 만들기"""
hello = 0 # co_varnames테스트를 위해 만든 local변수
if cls is not None:
attrs['class'] = cls
if attrs:
attr_str = ''.join(f' {attr}="{value}"'
for attr, value
in sorted(attrs.items()))
else:
attr_str = ''
if content:
return '\n'.join(f'<{name}{attr_str}>{c}</{name}>'
for c in content)
else:
return f'<{name}{attr_str}/>'
print(tag.__code__.co_varnames)
>>>('name', 'cls', 'content', 'attrs', 'hello')
이렇게 tag함수에 hello라는 local변수를 만들고 나서
다시 co_varnames에 접근하면
('name', 'cls', 'content', 'attrs', 'hello')
기존에 있던 것들에 'hello'가 추가된 걸 확인할 수 있다.
★참고로 매개변수 이름들이 먼저 나오고 난 뒤에 local변수들이 나온다.★
from test import tag # 테그 함수를 가져옴
print(tag.__code__.co_varnames)
print(tag.__code__.co_argcount)
>>>('name', 'cls', 'content', 'attrs')
>>>1
두 번째로 사용한 co_argcount에 접근하면
함수의 위치 매개변수가 몇 개 있는지 알 수 있다.
(tag함수에는 'name'하나뿐이다.)
co_varnames, co_argcount 외에도
다양한 속성들에 접근할 수 있다.
print(tag.__code__.co_name) # >>>tag, 함수 이름
print(tag.__code__.co_kwonlyargcount) # >>>1, 키워드 전용 인자들의 갯수(디폴트값이 있는 인자도 포함)
print(tag.__code__.co_posonlyargcount) # >>>0, 위치 전용 인자들의 갯수(디폴트값이 있는 인자도 포함)
print(tag.__code__.co_filename) # >>> /users/antoliny/...., 코드(함수)를 제공한 파일 경로
print(tag.__code__.co_stacksize) # >>>6, 필요한 스택의 크기
code객체의 Special read-only attributes
더 자세한 내용이 궁금하다면.
공식문서를 참고하면 된다.
https://docs.python.org/ko/3/reference/datamodel.html
3. 데이터 모델 — Python 3.10.4 문서
A class can implement certain operations that are invoked by special syntax (such as arithmetic operations or subscripting and slicing) by defining methods with special names. This is Python’s approach to operator overloading, allowing classes to define
docs.python.org
(코드 객체(Code objects) 쪽을 확인하면 된다.)
inspect
함수의 여러 가지 속성에 접근하는 방법이 더 있다.
바로 inspect모듈을 사용하는 것
★이전 방법보다 훨씬 훌륭하다★
먼저 특정 함수의 파라미터에 어떤 매개변수가 있는지
확인하고 싶다면
inspect.signature의 parameters속성에 접근하면 된다.
import inspect
from test import tag
sig = inspect.signature(tag)
print(sig.parameters)
>>>OrderedDict([('name', <Parameter "name">), ('content', <Parameter "*content">),
('cls', <Parameter "cls=None">), ('attrs', <Parameter "**attrs">)])
먼저 inspect.signature에 함수를 전달해서
inspect.signature객체로 만들어줘야 한다.
그리고 그 객체에 parameters속성에 접근하면
우리가 넘겨줬던 함수의 매개변수 값들이 OrderedDict타입으로 정리되어있다.
사실 이 정도로는 아직 부족하다는 느낌이 든다.
OrderedDict에 있는 <Parameter "name">, <Parameter "*content">는 inspect.Parameter 객체다
inspect.Parameter객체의 여러 가지 속성에 접근하면
이 매개변수에 대해 더 깊게 파악할 수 있다.
inspect.Parameter
inspect.Parameter의 kind라는 속성이 있는데
이 속성에 접근하면 인수들이 매개변수에 접근하는 방법에 대해 알 수 있다.
import inspect
from test import tag
sig = inspect.signature(tag)
for x, parameter in sig.parameters.items():
print(parameter.kind)
>>>POSITIONAL_OR_KEYWORD # <Parameter "name">
>>>VAR_POSITIONAL # <Parameter "*content">
>>>KEYWORD_ONLY # <Parameter "cls=None">
>>>VAR_KEYWORD # <Parameter "**attrs">
POSITIONAL_OR_KEYWORD, VAR_POSITIONAL 등등
특이한 값들이 보이는데
이 값들이 의미는 공식문서에서 확인할 수 있었다.
이름 | 의미 |
POSITIONAL_ONLY | 위치 인자로만 값을 제공해야 합니다. 위치 전용 매개 변수는 파이썬 함수 정의에서 / 항목 (있다면) 앞에 나오는 매개 변수입니다. |
POSITIONAL_OR_KEYWORD | 값은 키워드나 위치 인자로 제공될 수 있습니다 (이것이 파이썬으로 구현된 함수의 표준 연결 동작입니다). |
VAR_POSITIONAL | 다른 매개 변수에 연결되지 않은 위치 인자의 튜플. 이것은 파이썬 함수 정의의 *args 매개 변수에 해당합니다. |
KEYWORD_ONLY | 키워드 인자로만 값을 제공해야 합니다. 키워드 전용 매개 변수는 파이썬 함수 정의에서 *나 *args 항목 다음에 나오는 매개 변수입니다. |
VAR_KEYWORD | 다른 매개 변수에 연결되지 않은 키워드 인자의 딕셔너리. 이것은 파이썬 함수 정의의 **kwargs 매개 변수에 해당합니다. |
kind속성 말고도 default, annotation, name 등등 다양한 속성이 있다.
import inspect
from test import tag
sig = inspect.signature(tag)
for x, parameter in sig.parameters.items():
print(parameter.annotation)
>>><class 'inspect._empty'>
>>><class 'inspect._empty'>
>>><class 'inspect._empty'>
>>><class 'inspect._empty'>
#--------------------------------------------------------------
# tag함수에 애너테이션을 추가한뒤 다시 실행
# test.py
def tag(name: str, *content: int, cls=None, **attrs: float):
...
>>><class 'str'>
>>><class 'int'>
>>><class 'inspect._empty'>
>>><class 'float'>
annotation속성은 말 그대로
매개변수에 annotaion이 있으면 그 해당 타입을 반환한다.
만약 타입 힌팅(annotation)이
없으면 <class 'inspect._empty'>를 반환한다.
마지막으로 볼 속성 default는
말 그대로 매개변수에 디폴트 값이 있냐 없냐를
알 수 있는 속성이다.
import inspect
from test import tag
sig = inspect.signature(tag)
for x, parameter in sig.parameters.items():
print(parameter.default)
>>><class 'inspect._empty'>
>>><class 'inspect._empty'>
>>>None
>>><class 'inspect._empty'>
디폴트 값이 없는 나머지(name, *content, **attrs)는
<class 'inspect._empty'>를 출력했고
cls만 디폴트 값이 None으로
설정되어있기 때문에 cls부분만 None으로 출력된 걸 확인할 수 있다.
inspect.Signature 메서드
필자에게 가장 신기했던 기능 중 하나인
bind메서드를 소개하고자 한다.
bind메서드는 inspect.Signature에 정의되어있다.
import inspect
from test import tag
sig = inspect.signature(tag)
print(sig.bind('a', '이동하기', href='www.naver.com', cls='link'))
>>><BoundArguments (name='a', content=('이동하기',), cls='link', attrs={'href': 'www.naver.com'})>
bind메서드에 여러 가지 인수를 전달해보면
그 인수가 tag함수의 어느 파라미터에 매핑되었는지 확인할 수 있다.
초반에 필자가 tag함수를 이해하지 못한 이유가
내가 보낸 인수들이 어떤 매개변수에 대입되는지 파악하지 못해서라고 말한 적이 있다.
이런 필자에게 bind메서드를 사용하게 하면
문제를 해결할 수 있다.
inspect.Signature에는 bind메서드 말고도
bind_partial, replace가 있다.
이 메서드들에 혹시나 관심이 있을 분들을 위해
마지막으로 inspect의 메서드 몇 개만 소개하고 공식문서 링크를 남겨두겠다.
inspect메서드
마지막으로 inspect메서드 몇 개를 소개하려고 한다.
inspect는 getdoc, getcomments, getfile, getsource... 등등
많은 메서드들을 가지고 있다.
이중 필자는 getfile과 getsource에 대해 설명하고자 한다.
먼저 getsource 메서드는
import inspect
def add(x, y):
"""더하기"""
return x + y
print(inspect.getsource(add))
>>> def add(x, y):
"""더하기"""
return x + y
인수로 넘긴 함수(add)의
함수 몸체 소스코드를 볼 수 있는 메서드다.
이 방법 말고도 소스코드를 볼 수 있는 방법은 많다.
공식문서를 보거나 맥북 같은 경우는
(command+해당 함수 클릭)을 통해 함수가 있는 파일로 이동해서
직접 볼 수 있다.
다음은 getfile메서드다.
import inspect
import openpyxl
print(inspect.getfile(openpyxl.Workbook))
>>>/Users/antoliny/.pyenv/versi ....
getfile메서드를 사용하면
인수로 넘겨준 객체가 정의된 파일의 경로를 알 수 있다.
참고로
객체가 내장 모듈, 클래스 또는 함수이면 TypeError가 발생한다.
(근데 왜 Workbook은 클래스인데 오류가 발생 안 하는지 모르겠다)
inspect모듈 공식문서
https://docs.python.org/ko/3/library/inspect.html
inspect — 라이브 객체 검사 — Python 3.10.4 문서
inspect — 라이브 객체 검사 소스 코드: Lib/inspect.py inspect 모듈은 모듈, 클래스, 메서드, 함수, 트레이스백, 프레임 객체 및 코드 객체와 같은 라이브 객체에 대한 정보를 얻는 데 도움이 되는 몇 가
docs.python.org
후기
필자는 항상
모듈을 임포트하고 그 모듈에 있는 추상화된 함수들을 사용하면
일어나는 마법 같은 일에 궁금증을 가지곤 했다.
그 궁금증을 해결하려면 결국 그 모듈에 있는 함수의 소스코드를
해석해보고 싶다는 생각이 들었다.
아직 소스코드를 보고 해석할 수 있는 단계는 아니지만
그래도 오늘 inspect모듈과 code객체 읽기 전용 속성 접근으로
그 함수를 사용하는 데에는 조금 더 쉽게 다가갈 수 있을 거 같다.
생각한 거보다 너무 길게 쓴 거 같다.(마치 내 코드 같다.)
다음에는 좀 짧게 써야지 ^^;
'Python' 카테고리의 다른 글
[파이썬] 코루틴 (0) | 2022.07.11 |
---|---|
[파이썬] 데코레이터 (0) | 2022.07.04 |
[파이썬]인터페이스, 덕타이핑, 프로토콜 (1) | 2022.06.25 |
[파이썬]이터레이터, 이터러블, 제너레이터 (0) | 2022.06.24 |
[파이썬] 클로저(Closure) (0) | 2022.05.31 |