2016-10-20 5 views
3

документы говорят, что:Как реализован метод __subclasses__ в CPython?

Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive.

Но как каждый класс получить список слабых ссылок на его подклассов, в первую очередь? Другими словами, когда я создаю

class B(A): 
    pass 

как же A узнать, что B просто подклассы это? И этот механизм достаточно прочный, чтобы выжить на кромках (пользовательские метаклассы, присвоение __bases__ и т. Д.)?

+0

Будьте осторожны с этим, я узнал, что вы не должны использовать '__subclasses__', пока не будут импортированы все объявления классов, в противном случае некоторые элементы могут отсутствовать. – wim

+0

@wim Хотя, возможно, немного сложно понять, что все импорт уже произошел, кажется очень разумным, что он работает именно так. В конце концов, это механизм выполнения (по необходимости, поскольку создание класса является динамическим в python). Поэтому он может только рассказать нам о классах, которые в настоящее время живы в данном процессе python. – max

+0

Это правда - следствие состоит в том, что вы не можете использовать его во время импорта, если у вас нет хорошего контроля над импортом. Это сложнее, чем кажется, и хрупкой для изменений при реорганизации. – wim

ответ

5

В качестве части инициализации нового класса к объекту к каждому из его базовых классов добавляется слабая ссылка на этот класс. Вы можете увидеть это в исходном коде Python в Objects/typeobject.c:

int 
PyType_Ready(PyTypeObject *type) 
{ 
    ... 
    /* Link into each base class's list of subclasses */ 
    bases = type->tp_bases; 
    n = PyTuple_GET_SIZE(bases); 
    for (i = 0; i < n; i++) { 
     PyObject *b = PyTuple_GET_ITEM(bases, i); 
     if (PyType_Check(b) && 
      add_subclass((PyTypeObject *)b, type) < 0) 
      goto error; 
    } 
    ... 
} 

static int 
add_subclass(PyTypeObject *base, PyTypeObject *type) 
{ 
    int result = -1; 
    PyObject *dict, *key, *newobj; 

    dict = base->tp_subclasses; 
    if (dict == NULL) { 
     base->tp_subclasses = dict = PyDict_New(); 
     if (dict == NULL) 
      return -1; 
    } 
    assert(PyDict_CheckExact(dict)); 
    key = PyLong_FromVoidPtr((void *) type); 
    if (key == NULL) 
     return -1; 
    newobj = PyWeakref_NewRef((PyObject *)type, NULL); 
    if (newobj != NULL) { 
     result = PyDict_SetItem(dict, key, newobj); 
     Py_DECREF(newobj); 
    } 
    Py_DECREF(key); 
    return result; 
} 

setter для __bases__ также обновляет подклассов списки каждого из старых и новых баз:

static int 
type_set_bases(PyTypeObject *type, PyObject *new_bases, void *context) 
{ 
    ... 
    if (type->tp_bases == new_bases) { 
     /* any base that was in __bases__ but now isn't, we 
      need to remove |type| from its tp_subclasses. 
      conversely, any class now in __bases__ that wasn't 
      needs to have |type| added to its subclasses. */ 

     /* for now, sod that: just remove from all old_bases, 
      add to all new_bases */ 
     remove_all_subclasses(type, old_bases); 
     res = add_all_subclasses(type, new_bases); 
     update_all_slots(type); 
    } 
    ... 
} 

Обратите внимание, что если метаклассом делает что-то, чтобы настроить значение отношения подкласса, __subclasses__ не будет отражать это. Например, issubclass(list, collections.abc.Iterable) - True, но list не будет отображаться при поиске дерева __subclasses__, начиная с collections.abc.Iterable.

+0

Значит, это означает, что присвоение 'cls .__ bases__' нарушит способность находить подклассы через' __subclasses__'? – max

+0

Я получаю 'TypeError: __bases__ присваивание: 'D' deallocator отличается от« объекта »при попытке назначить' __bases__' –

+1

@max: Нет, установщик для '__bases__' будет поддерживать актуальный список. – user2357112

Смежные вопросы