What happens when we get or set an attribute of a Python object? This question is not as simple as it may seem at first.

이번 글에서는 instance.attr처럼 attribute에 접근할 때 Python 구현체에서 실제로 어떤 일이 일어나는지 CPython을 예시로 살펴볼 것이다.

Python level에서 PyClass 클래스의 instance py_instance의 attribute attr에 접근(get/set)하는 과정을 살펴볼 것이다. CPython에서 PyClassPyTypeObject *class, py_instancePyObject *instance이다.

Getting an Attribute

CPython은 instance의 type인 class의 slot 중 tp_getattro, tp_getattr 순으로 slot을 호출하여 instance의 attribute를 구한다. tp_getattrotp_getattr의 차이는 tp_getattro가 Python string을 인자로 받는다는 차이점 뿐이며, tp_getattr은 더 이상 사용되지 않으니 고려하지 않겠다. py_instance.attrclass->tp_getattro(instance, PyStr("attr"))를 호출한다.

또한 PyClass__getattribute__, __getattr__ special method가 있다면 CPython은 이들을 tp_getattromapping한다.

Generic Get Attribute (Instance)

instance가 class object가 아니면 tp_getattroPyObject_GenericGetAttr()를 참조한다. 이 함수의 구현은 다음과 같다.

  1. MRO(Method Resolution Order)대로 classclass의 base들의 attribute dictionary에서 attr(class variable)을 찾는다. 만약 찾은 variable이 data descriptor이면 이 variable의 tp_descr_get slot을 호출한 결과를 return한다. 만약 data descriptor가 아니면 step 2가 우선순위이므로 기억만 해두고 2로 넘어간다.
  2. instance의 attribute dictionary에서 attr(instance variable)을 찾았다면 return한다.
  3. 1에서 찾은 결과를 return한다. 만약 descriptor라면 tp_descr_get slot을 호출한 결과를 return한다.

Type Get Attribute (Class)

instance가 class object인 경우, 즉 PyTypeObject *instance일 경우 tp_getattrotype_getattro()를 참조한다. 이 함수는 PyObject_GenericGetAttr과 큰 흐름은 같지만, step 2가 다르다.

  1. MRO대로 instanceinstance의 base들의 attribute dictionary에서 attr(class variable)을 찾아서 return한다. 단, 찾은 variable이 descriptor라면 tp_descr_get slot을 호출한 결과를 return한다.

이 차이는 찾은 attribute가 descriptor일 때, instance variable이면 tp_descr_get을 호출하지 않는다는 점과, base가 존재하면 MRO대로 base도 살펴본다는 규칙으로 요약할 수 있다.

Custom Get Attribute

만약 __getattribute__ 또는 __getattr__을 구현하는 Python 클래스라면 CPython은 해당 class object의 tp_getattro slot을 slot_tp_getattr_hook()를 참조시킨다. 이 함수는 __getattribute__ python function과 __getattr__ python function을 차례로 실행시킨다. 만약 __getattribute__만 구현한다면 slot_tp_getattro를 참조시키는데, 이 함수는 slot_tp_getattr_hook과 비교해보면 __getattr__을 실행하는 부분이 빠져있다.

Setting an Attribute

CPython은 instance의 type인 class의 slot 중 tp_setattro, tp_setattr 순으로 slot을 호출하여 instance의 attribute를 set한다. 마찬가지로 tp_setattr은 더이상 사용되지 않는다. py_instance.attr = valueclass->tp_setattro(instance, PyStr("attr"), value)를 호출한다.

또한 PyClass__setattr__, __delattr__ special method가 있다면 CPython은 이들을 tp_setattromapping한다.

Generic Set Attribute

instance가 class object가 아니면 tp_setattroPyObject_GenericSetAttr()를 참조한다. 이 함수의 구현은 다음과 같다.

  1. MRO대로 classclass의 base들의 attribute dictionary에서 attr(class variable)을 찾는다. 만약 찾은 variable이 tp_descr_set을 구현하는 descriptor라면 이 slot을 호출한 결과를 return한다. 그렇지 않다면 step 2로 넘어간다.
  2. instance의 attribute dictionary에 attr을 추가하거나 기존 attr을 update한다.

Type Set Attribute

instance가 class object인 경우 tp_setattrotype_setattro()를 참조한다. 이 함수는 내부적으로 PyObject_GenericSetAttr을 호출하지만, 그 외에도 이 class가 statically defined type(e.g, built-in type)이라면 익셉션을 발생시키고, attribute가 special method라면 대응하는 slot을 update한다.

Statically defined type은 Python 구현체를 실행시킬 때 이미 내장되어 있는 type object다. 이런 type들은 기본적으로 불변이다.

Custom Attribute Set

만약 __setattr__ 또는 __delattr__을 구현하는 Python 클래스라면 CPython은 해당 class object의 tp_setattro slot을 slot_tp_setattro()를 참조시킨다. 이 함수는 value가 NULL인지 아닌지에 따라 __setattr__ 또는 __delattr__ python function을 실행시킨다.


Back