Сканирование членов класса Python-почему рекурсивное сканирование баз требуется в get-членах?


Я нашел следующую функцию, но я понятия не имею, почему требуется дополнительное __bases__ сканирование:

def getMembersWithBases(classType):
    members = set(dir(classType))
    # recursive bases scan
    for baseClassType in classType.__bases__:
        members.update(getMembersWithBases(baseClassType))
    return members

Следующая функция быстрее и дает те же результаты - так почему же требуется дополнительное __bases__ сканирование в al?

def getMembers(classType):
    members = set(dir(classType))
    return members

Некоторый тестовый код с классами как нового, так и старого стиля:

class Father(object):
    def testFather():
        pass

class Mother(object):
    def testMother():
        pass

class Child(Father, Mother):
    def testChild():
        pass

print type(Child)
print getMembers(Child) == getMembersWithBases(Child)                                             

class Father:
    def testFather():
        pass

class Mother:
    def testMother():
        pass

class Child(Father, Mother):
    def testChild():
        pass

print type(Child)
print getMembers(Child) == getMembersWithBases(Child)

Результат:

<type 'type'>
True
<type 'classobj'>
True
1 2

1 ответ:

Действительно,dir() функция для классов уже включает все классы, перечисленные в __bases__:

Если объект является объектом типа или класса, список содержит имена его атрибутов и рекурсивно атрибутов его баз.

Однако класс может переопределить это поведение, указав метод __dir__ (через метакласс, classmethod недостаточно):

Если объект имеет метод с именем __dir__(), Этот метод будет вызван и должен вернуть список атрибутов.

Когда метод __dir__() присутствует, __bases__ не рекурсируется:

>>> class Foo(object):
...     def spam(self): pass
... 
>>> class Bar(object):
...     def ham(self): pass
... 
>>> class CustomDirMetaclass(type):
...     def __dir__(cls):
...         return ['eggs']
... 
>>> class Baz(Foo, Bar):
...     __metaclass__ = CustomDirMetaclass
...     def eggs(self): pass
... 
>>> dir(Baz)
['eggs']
>>> getMembers(Baz)
set(['eggs'])
>>> getMembersWithBases(Baz)
set(['__module__', '__getattribute__', 'eggs', '__reduce__', '__subclasshook__', '__dict__', '__sizeof__', '__weakref__', '__init__', 'ham', '__setattr__', '__reduce_ex__', '__new__', 'spam', '__format__', '__class__', '__doc__', '__delattr__', '__repr__', '__hash__', '__str__'])
Таким образом, явная рекурсия над __bases__ в методе класса getMembersWithBases() может быть попыткой обойти любые пользовательские реализации __dir__().

В противном случае рекурсия над __bases__ является полностью избыточной.

По моему личному мнению, рекурсия над __bases__ избыточна даже , Если есть ]} __dir__() методы, присутствующие в иерархии классов. В таких случаях переопределение метода __dir__() является ошибкой, так как метод должен рекурсировать над классами, перечисленными в __bases__, Чтобы правильно имитировать поведение функции dir().

Честно говоря, я подозреваю, что автор функции, которую вы нашли, не знал о рекурсивной природе dir(), и добавил рекурсию __bases__ игольчато, а не как средство обойти пользовательские методы __dir__().