Python 강좌 : 제 39강 - 특수 속성
특수 속성(Special attributes)
특수 속성(Special attributes)은 실제 코드의 프로세스에는 거의 포함되지 않지만,
개발 과정이나 디버깅 과정 등에서 유용하게 사용할 수 있는 속성입니다.
정의된 클래스의 상황이나 상태 등을 점검할 수 있습니다.
특수 속성은 매직 메서드처럼 이름의 앞뒤에 이중 밑줄(__)이 붙어 있지만, 호출하는 메서드가 아니라 값입니다. 함수나 클래스를 정의하면 Python이 이름, 설명, 정의된 위치 같은 정보를 이 속성에 자동으로 채워 넣습니다.
그러므로 소스 코드를 직접 열어 보지 않아도, 실행 중에 “이 함수의 이름은 무엇이고, 어느 모듈에 있으며, 어떤 매개변수를 요구하는가”를 코드로 확인할 수 있습니다. help() 함수나 IDE의 자동 완성, 테스트 도구 등도 내부적으로 이 속성들을 읽어 동작합니다.
이번 강좌에서는 함수와 인스턴스의 대표적인 특수 속성을 먼저 살펴보고, 클래스의 상속 정보와 정의 정보를 담은 특수 속성을 다룹니다.
Special attributes
class Daeheeyun:
""" Daeheeyun CLASS
Callable types example.
blah blah..
To use:
>>> instance = Daeheeyun(value)
Args:
value : int
Returns:
null
"""
def __init__(self, value: int):
self.value = value
def func(self) -> int:
"""func : Execute value * 2"""
return self.value * 2
instance = Daeheeyun(5)
print(instance.__doc__)
print("---------")
print(instance.func.__doc__)
print("---------")
print(instance.func.__name__)
print(instance.func.__qualname__)
print(instance.func.__module__)
print(instance.func.__annotations__)
print(instance.__dict__)- 결과
- Daeheeyun CLASS
Callable types example.
blah blah..
To use:
>>> instance = Daeheeyun(value)
Args:
value : int
Returns:
null
———
func : Execute value * 2
———
func
Daeheeyun.func
__main__
{‘return’: <class ‘int’>}
{‘value’: 5}
하나의 클래스로 여러 특수 속성을 한 번에 확인하는 예제입니다. 결과는 위에서부터 다음과 같이 읽습니다.
instance.__doc__은 클래스에 작성한 여러 줄의 설명 문자열을 반환합니다. 인스턴스에는 설명 문자열이 없으므로, 클래스의__doc__이 반환됩니다.instance.func.__doc__은func메서드에 작성한 한 줄짜리 설명 문자열을 반환합니다.__name__,__qualname__,__module__은 메서드의 이름, 클래스를 포함한 이름, 정의된 모듈 이름을 반환합니다.__annotations__는 반환값에 작성한 주석-> int를 사전으로 반환합니다.instance.__dict__는__init__에서 할당한 인스턴스 속성value를 사전으로 반환합니다.
__doc__ : 설명 주석 호출
__doc__는 클래스나 함수를 설명하는 데 사용된 설명 문자열(docstring)을 반환합니다.
클래스나 함수의 첫 번째 문장으로 작성된 문자열이 docstring이 되며, 작성하지 않았다면 None을 반환합니다.
디버깅 과정에서 직접 모듈을 열어 확인하지 않고도 설명 주석을 확인할 수 있습니다. help() 함수도 이 값을 사용해 도움말을 출력합니다.
- Tip :
Python 3.13이상부터docstring의 공통 들여쓰기가 컴파일할 때 제거됩니다. 첫 줄 앞의 공백과 공백만 있는 줄도 함께 정리되므로, Python 3.12 이하에서는Callable types example.같은 줄 앞에 소스 코드의 들여쓰기 공백 네 칸이 그대로 붙어 출력되었지만, 3.13 이상에서는 위와 같이 공백 없이 출력됩니다. 이전 버전에서 같은 결과를 얻으려면inspect.cleandoc()이나inspect.getdoc()을 사용합니다.
def first():
"""첫 번째 문장의 문자열"""
return 1
def second():
value = 1
"""첫 번째 문장이 아닌 문자열"""
return value
def third():
# 주석은 docstring이 아닙니다.
return 3
print(first.__doc__)
print(second.__doc__)
print(third.__doc__)- 결과
- 첫 번째 문장의 문자열
None
None
docstring이 되는 조건은 함수 본문의 첫 번째 문장이 문자열이어야 한다는 것입니다.
first는 첫 문장이 문자열이므로 그 값이 __doc__에 저장됩니다.
second는 value = 1이 먼저 실행되므로, 그 뒤의 문자열은 아무 일도 하지 않는 단순한 값일 뿐 docstring이 되지 않아 None이 반환됩니다.
third처럼 #으로 작성한 주석은 실행 시점에 사라지므로, __doc__으로 읽을 수 없습니다. 실행 중에 확인해야 하는 설명은 # 주석이 아닌 docstring으로 작성해야 합니다.
이름과 위치 속성
함수와 클래스는 자신의 이름과 정의된 위치를 특수 속성으로 기억합니다.
변수에 다른 이름으로 할당하거나 다른 모듈로 가져와도 이 값은 바뀌지 않으므로, 로그나 오류 메시지에서 원래 어떤 함수였는지 확인할 때 사용합니다.
__name__ : 함수명 호출
__name__은 함수나 클래스의 이름 자체를 반환합니다.
모듈에도 __name__ 속성이 있으며, if __name__ == '__main__':의 __name__이 바로 현재 모듈의 이름입니다.
모듈을 직접 실행하면 '__main__', 다른 곳에서 import하면 모듈명이 됩니다.
import json
def func():
pass
alias = func
square = lambda x: x ** 2
print(__name__)
print(json.__name__)
print(alias.__name__)
print(square.__name__)
print(int.__name__, type(3).__name__)- 결과
- __main__
json
func
<lambda>
int int
print(__name__)은 직접 실행한 파일이므로 __main__을, json.__name__은 가져온 모듈의 이름인 json을 반환합니다.
alias = func는 함수에 이름을 하나 더 붙였을 뿐이므로, alias.__name__은 처음 def로 정의한 이름인 func를 반환합니다.
lambda로 만든 함수는 이름이 없으므로 __name__이 <lambda>입니다. 변수 square에 할당했더라도 함수의 이름이 square가 되는 것은 아닙니다.
type(3).__name__처럼 type()과 함께 사용하면 값의 형식 이름을 문자열로 얻을 수 있어, 오류 메시지를 만들 때 자주 사용합니다.
__qualname__ : 함수명과 경로 호출
__qualname__은 함수의 경로를 포함한 정규화된 이름(Qualified Name)을 반환합니다.
예제의 func는 Daeheeyun 클래스 안에 정의되었으므로 Daeheeyun.func가 됩니다.
이를 통해 실행되는 함수나 클래스가 어디서부터 실행되는지 확인할 수 있습니다.
def outer():
def inner():
pass
return inner
class Daeheeyun:
class Config:
def load(self):
pass
print(outer().__name__, outer().__qualname__)
print(Daeheeyun.Config.load.__name__, Daeheeyun.Config.load.__qualname__)- 결과
- inner outer.<locals>.inner
load Daeheeyun.Config.load
__name__은 마지막 이름만 반환하지만, __qualname__은 모듈 최상위부터의 경로를 마침표로 이어 반환합니다.
inner는 outer 함수 안에서 정의되었으므로 outer.<locals>.inner가 됩니다. <locals>는 함수의 지역 영역 안에 정의되었다는 의미입니다.
load는 Daeheeyun 클래스 안의 Config 클래스 안에 정의되었으므로 Daeheeyun.Config.load가 됩니다.
서로 다른 클래스에 같은 이름의 메서드가 있을 때, __name__만으로는 구분할 수 없지만 __qualname__으로는 구분할 수 있습니다.
__module__ : 정의된 모듈명 호출
__module__은 함수가 정의된 모듈의 이름을 반환합니다.
현재 코드에서 실행시키면, __main__을 반환하며, 다른 곳에서 import하는 경우 모듈의 이름을 반환합니다.
모듈을 직접 실행했을 때, 실행되는 코드인 if __name__ == '__main__':에서 '__main__'과 의미가 같습니다.
from json import dumps
from collections import OrderedDict
class Daeheeyun:
pass
print(dumps.__module__)
print(OrderedDict.__module__)
print(Daeheeyun.__module__, Daeheeyun().__module__)
print(len.__module__)- 결과
- json
collections
__main__ __main__
builtins
from json import dumps로 가져와 현재 파일에서 dumps라는 이름으로 사용하더라도, dumps.__module__은 함수가 정의된 모듈인 json을 반환합니다.
클래스도 __module__을 가지며, 인스턴스에서 접근하면 클래스의 __module__이 반환됩니다.
len 같은 내장 함수는 builtins 모듈에 정의되어 있습니다.
__module__과 __qualname__을 함께 사용하면 json.dumps처럼 어느 모듈의 어떤 객체인지 정확하게 표현할 수 있습니다.
주석과 속성 사전
__annotations__ : 함수의 주석 호출
__annotations__은 함수의 매개변수와 반환값에 정의된 주석(Annotations)을 반환합니다.
함수가 어떤 매개변수와 반환값을 요구하는지 확인할 수 있습니다.
예제의 func는 반환값에만 주석을 작성했으므로 {'return': <class 'int'>}가 반환됩니다.
self처럼 주석을 작성하지 않은 매개변수는 사전에 포함되지 않습니다. __init__의 value: int는 Daeheeyun.__init__.__annotations__로 확인할 수 있습니다.
Python 3.14 이상부터 주석은 지연 평가되므로, __annotations__에 처음 접근할 때 주석이 계산됩니다. 주석에 아직 정의되지 않은 이름이 있다면 이 시점에 NameError가 발생할 수 있습니다.
- Tip : Python 3.14 이상에서는
annotationlib.get_annotations()로 주석을 문자열(Format.STRING)이나ForwardRef(Format.FORWARDREF) 형식으로 안전하게 가져올 수 있습니다. 자세한 내용은 제 34강에서 확인할 수 있습니다.
__dict__ : 인스턴스 속성 호출
__dict__는 현재 인스턴스에 할당된 인스턴스 속성을 출력합니다.
인스턴스의 key 값과 value 값을 확인할 수 있습니다.
__dict__는 인스턴스 속성이 실제로 저장되는 사전이므로, 이 사전을 직접 수정해도 속성이 바뀝니다. 반면 클래스의 __dict__는 읽기 전용입니다.
class Daeheeyun:
kind = "class"
def __init__(self, value):
self.value = value
instance = Daeheeyun(5)
instance.__dict__["extra"] = 10
print(instance.extra)
print(type(Daeheeyun.__dict__))
print(Daeheeyun.__dict__["kind"])
try:
Daeheeyun.__dict__["kind"] = "변경"
except TypeError as e:
print("TypeError:", e)- 결과
- 10
<class ‘mappingproxy’>
class
TypeError: ‘mappingproxy’ object does not support item assignment
instance.__dict__["extra"] = 10처럼 인스턴스의 사전에 값을 넣으면, instance.extra로 바로 읽을 수 있습니다.
클래스의 __dict__는 일반 사전이 아닌 mappingproxy 형식으로, 클래스 속성과 메서드를 읽을 수만 있는 사전입니다. 그러므로 값을 할당하면 TypeError가 발생하며, 클래스 속성을 바꾸려면 Daeheeyun.kind = "변경"처럼 클래스에 직접 할당해야 합니다.
- Tip :
vars(객체)는객체.__dict__를 반환합니다.__slots__를 사용한 클래스의 인스턴스는__dict__가 없으므로 사용할 수 없습니다. (제 37강 참고)
형식과 상속 속성
class Animal:
pass
class Dog(Animal):
pass
dog = Dog()
print(dog.__class__)
print(dog.__class__ is type(dog))
print(Dog.__bases__)
print(Dog.__mro__)
print(Animal.__bases__)- 결과
- <class ‘__main__.Dog’>
True
(<class ‘__main__.Animal’>,)
(<class ‘__main__.Dog’>, <class ‘__main__.Animal’>, <class ‘object’>)
(<class ‘object’>,)
인스턴스와 클래스는 자신이 어떤 클래스에서 만들어졌고, 어떤 클래스를 상속받았는지를 특수 속성으로 가지고 있습니다.
__class__ : 인스턴스의 클래스 호출
__class__는 인스턴스를 만든 클래스를 반환합니다.
dog.__class__는 Dog 클래스이며, type(dog)와 같은 객체이므로 is 비교가 True입니다. 일반적으로는 type() 함수를 사용합니다.
__bases__ : 부모 클래스 호출
__bases__는 클래스가 직접 상속받은 부모 클래스를 튜플로 반환합니다.
Dog는 Animal을 상속받았으므로 (Animal,)을 반환합니다. 부모 클래스를 작성하지 않은 Animal은 자동으로 object를 상속받으므로 (object,)를 반환합니다.
__mro__ : 메서드 결정 순서 호출
__mro__는 메서드 결정 순서(Method Resolution Order)로, 속성이나 메서드를 찾을 때 클래스를 확인하는 순서를 튜플로 반환합니다.
__bases__가 바로 위의 부모만 보여주는 것과 달리, __mro__는 자기 자신부터 object까지 모든 조상 클래스를 순서대로 보여줍니다.
dog.name처럼 속성에 접근하면, 인스턴스에서 찾지 못했을 때 Dog → Animal → object의 순서로 확인합니다. 속성 조회 순서는 제 37강에서 자세히 다룹니다.
함수의 기본값 속성
def connect(host, port=8080, *, timeout=3.0, retry=True):
pass
print(connect.__defaults__)
print(connect.__kwdefaults__)
print(connect.__code__.co_varnames)- 결과
- (8080,)
{‘timeout’: 3.0, ‘retry’: True}
(‘host’, ‘port’, ‘timeout’, ‘retry’)
함수는 매개변수의 기본값도 특수 속성에 저장합니다.
__defaults__는 위치 인자로도 전달할 수 있는 매개변수의 기본값을 튜플로 반환합니다. host는 기본값이 없으므로 port의 8080만 포함됩니다.
__kwdefaults__는 * 뒤에 작성한 키워드 전용 매개변수의 기본값을 사전으로 반환합니다. 키워드 전용 매개변수는 제 33강에서 확인할 수 있습니다.
__code__는 함수의 컴파일된 코드 객체이며, co_varnames로 매개변수와 지역 변수의 이름을 확인할 수 있습니다.
기본값은 함수를 정의할 때 한 번만 계산되어 __defaults__에 저장됩니다. 그러므로 def func(items=[])처럼 리스트를 기본값으로 사용하면, 호출할 때마다 __defaults__에 저장된 같은 리스트가 재사용되므로 주의해야 합니다.
- Tip : 매개변수의 이름, 기본값, 주석을 한 번에 확인하려면
inspect.signature(함수)를 사용하는 것이 편리합니다.
클래스의 특수 속성
class Daeheeyun:
def __init__(self, value: int):
self.value = value
self._cache = None
def func(self) -> int:
self.result = self.value * 2
return self.result
print(Daeheeyun.__name__)
print(Daeheeyun.__module__)
print(Daeheeyun.__firstlineno__)
print(Daeheeyun.__static_attributes__)- 결과
- Daeheeyun
__main__
1
(‘_cache’, ‘result’, ‘value’)
클래스에도 함수와 같은 __name__, __module__이 있으며, Python 3.13 이상부터는 클래스의 정의 정보를 담은 특수 속성이 추가되었습니다.
__firstlineno__ : 클래스 정의 위치 호출
__firstlineno__는 클래스 정의가 시작되는 소스 코드의 줄 번호를 반환합니다. Python 3.13 이상에서 사용할 수 있습니다.
예제는 파일의 첫 번째 줄에서 class Daeheeyun:을 정의했으므로 1을 반환합니다. 데코레이터가 있다면 데코레이터가 시작되는 줄 번호를 반환합니다.
오류가 발생한 클래스를 소스 코드에서 찾거나, 문서화 도구가 소스 위치를 표시할 때 사용합니다.
__static_attributes__ : 인스턴스 속성 이름 호출
__static_attributes__는 클래스의 메서드 안에서 self.이름 = 값의 형태로 할당되는 속성의 이름을 튜플로 반환합니다. Python 3.13 이상에서 사용할 수 있습니다.
__init__뿐만 아니라 func처럼 다른 메서드에서 할당되는 result도 포함됩니다.
인스턴스를 생성하지 않아도, 코드를 컴파일할 때 확인되므로 클래스에서 바로 확인할 수 있습니다.
func를 한 번도 호출하지 않으면 실제 인스턴스에는 result 속성이 없지만, __static_attributes__에는 포함됩니다. 반대로 setattr()처럼 실행 중에 문자열로 만든 속성은 소스 코드에 self.이름 = 형태로 나타나지 않으므로 포함되지 않습니다.
- Tip :
__dict__는 실행 중 실제로 할당된 값을 반환하며,__static_attributes__는 소스 코드에 작성된 할당문을 기준으로 이름만 반환합니다. 결과의 순서는 이름순입니다.
특수 속성 활용
def describe(obj):
print(f"이름 : {obj.__qualname__} ({obj.__module__})")
print(f"설명 : {obj.__doc__}")
print(f"주석 : {obj.__annotations__}")
def area(width: int, height: int = 1) -> int:
"""직사각형의 넓이를 반환합니다."""
return width * height
describe(area)- 결과
- 이름 : area (__main__)
설명 : 직사각형의 넓이를 반환합니다.
주석 : {‘width’: <class ‘int’>, ‘height’: <class ‘int’>, ‘return’: <class ‘int’>}
특수 속성을 조합하면, 함수의 정보를 요약해 출력하는 간단한 도움말 함수를 만들 수 있습니다.
describe()는 전달받은 함수의 __qualname__, __module__, __doc__, __annotations__를 읽어 출력합니다. area 함수의 코드를 직접 보지 않아도 이름, 설명, 매개변수의 형식을 확인할 수 있습니다.
help() 함수나 문서 자동 생성 도구도 이와 같은 방식으로 특수 속성을 읽어 도움말을 만듭니다.
정리
| 특수 속성 | 대상 | 반환값/특징 |
|---|---|---|
__doc__ |
함수, 클래스, 모듈 | 첫 문장의 설명 문자열, 없으면 None |
__name__ |
함수, 클래스, 모듈 | 정의할 때의 이름, 직접 실행한 모듈은 '__main__' |
__qualname__ |
함수, 클래스 | 클래스·함수 경로를 포함한 이름 |
__module__ |
함수, 클래스 | 정의된 모듈의 이름 |
__annotations__ |
함수, 클래스, 모듈 | 주석 사전, Python 3.14 이상은 처음 접근할 때 계산 |
__dict__ |
인스턴스, 클래스, 모듈 | 속성 사전, 클래스는 읽기 전용 mappingproxy |
__class__ |
인스턴스 | 인스턴스의 클래스, type()과 같음 |
__bases__ |
클래스 | 직접 상속받은 부모 클래스 튜플 |
__mro__ |
클래스 | 속성을 찾는 클래스 순서 |
__defaults__ |
함수 | 위치 매개변수 기본값 튜플 |
__kwdefaults__ |
함수 | 키워드 전용 매개변수 기본값 사전 |
__firstlineno__ |
클래스 | 정의 시작 줄 번호 (Python 3.13 이상) |
__static_attributes__ |
클래스 | self.이름 =으로 할당되는 이름 튜플 (Python 3.13 이상) |
특수 속성은 Python이 자동으로 채워 주는 정보이며, 직접 값을 바꾸기보다는 디버깅·로그·문서화를 위해 읽는 용도로 사용합니다.
댓글 남기기