Ultimate benchmark пяти с половиной способов проверить наличие атрибута объекта в Python

    Вот тут подымался вопрос о том, как определить, есть ли у объекта атрибут и как это сделать максимально быстро, однако достаточно глубоко тема исследована не была.


    Это, собственно, и послужило причиной написания данной коротенькой статьи. Для тестирования я выбрал следующие (известные мне) способы определения наличия атрибута:
    1. Самый, пожалуй, очевидный — использовать встроенную функцию hasattr(obj, name).
    2. Другой распространенный способ — попытаться обратиться к атрибуту и, если не выйдет, принять меры, обработав AttributeError.
    3. Тоже использовать обработку AttributeError, однако обращаться к свойству не напрямую, а через getattr(obj, name). Ситуация выглядит надуманной, но в реальном применении возможны ситуации, когда имя атрибута для проверки формируется динамически и getattr там как нельзя кстати.
    4. Очень быстрый (см. чуть ниже результаты теста) метод — посмотреть в __dict__ объекта (если он у него есть, конечно). Проблема при применении этого метода заключается, пожалуй, лишь в том, что __dict__'ы раздельны для экземляра класса, самого класса и всех его предков. Если не знать точно, где находится нужный нам атрибут, этот метод не имеет для нас практической ценности. Следует заметить и то, что смотреть в __dict__-ы можно тоже двумя путями — используя методы dict.has_key(key_name) и dict.__contains__(key_name), который соответствует ключевому слову in (assert 'My Key Name' in my_dict). С учетом всех преимуществ, недостатков и двух вариантов реализации __dict__ на два отдельных метода не тянет, считаем его «полуторным».
    5. Последний, самый экзотический метод, заключается в просмотре dir(obj) на предмет имени нужного нам атрибута. Кстати, в процессе проверки вложенных __slots__-классов были обнаружены некоторые интересные моменты, связанные с dir(), но об этом в отдельной статье :)
    С методами, надеюсь, разобрались. Для того, чтобы получить более реальную ситуацию, я создал 3 класса, которые наследуются «цепочкой» — TestClass от TestClass2, который, в свою очередь, от TestClass3, предком которого является object. У каждого класса есть «instance attribute» с именем вида c3_ia, назначающийся в конструкторе класса, и «class attribute» c2_ca, определяемый на стадии компиляции класса.

    В каждом тесте я пытаюсь получить «instance attribute» и «class attribute» класса верхнего уровня TestClass, «instance attribute» и «class attribute», определенные в классе TestClass3 и какой-то несуществующий атрибут fake.

    Все тесты прогонялись 10'000'000 раз. Время, которое в сумме было потрачено на выполнение этих 10M одинаковых операций, и считается временем прохождения теста. Чтобы никому не было обидно, суммарное время высчитывается поровну, для существующих и несуществующих атрибутов.

    Вроде всё. Теперь результаты.

    Групповой зачет:
    dict_lookup_contains     :   5.800250 [2 subtests failed]
    dict_lookup              :   7.672500 [2 subtests failed]
    hasattr                  :  12.171750 [0 subtests failed]
    exc_direct               :  27.785500 [0 subtests failed]
    exc_getattr              :  32.088875 [0 subtests failed]
    dir                      : 267.500500 [0 subtests failed]

    Персональный зачет:
    test_dict_lookup_true_this_ca                     : FAILED [AssertionError()]
    test_dict_lookup_true_parent_ca                   : FAILED [AssertionError()]
    test_dict_lookup_contains_true_this_ca            : FAILED [AssertionError()]
    test_dict_lookup_contains_true_parent_ca          : FAILED [AssertionError()]
    test_exc_direct_true_this_ca                      :   5.133000
    test_exc_direct_true_parent_ca                    :   5.710000
    test_dict_lookup_contains_true_parent_ia          :   5.789000
    test_dict_lookup_contains_false                   :   5.804000
    test_dict_lookup_contains_true_this_ia            :   5.804000
    test_exc_direct_true_this_ia                      :   6.037000
    test_exc_direct_true_parent_ia                    :   6.412000
    test_hasattr_true_this_ca                         :   6.615000
    test_exc_getattr_true_this_ca                     :   7.144000
    test_hasattr_true_this_ia                         :   7.193000
    test_hasattr_true_parent_ca                       :   7.240000
    test_dict_lookup_false                            :   7.614000
    test_dict_lookup_true_this_ia                     :   7.645000
    test_exc_getattr_true_this_ia                     :   7.769000
    test_dict_lookup_true_parent_ia                   :   7.817000
    test_hasattr_true_parent_ia                       :   7.926000
    test_exc_getattr_true_parent_ca                   :   8.003000
    test_exc_getattr_true_parent_ia                   :   8.691000
    test_hasattr_false                                :  17.100000
    test_exc_direct_false                             :  49.748000
    test_exc_getattr_false                            :  56.276000
    test_dir_true_this_ia                             : 266.847000
    test_dir_true_this_ca                             : 267.053000
    test_dir_false                                    : 267.398000
    test_dir_true_parent_ca                           : 267.849000
    test_dir_true_parent_ia                           : 268.663000


    В принципе, тут особо комментировать нечего — таблица говорит сама за себя. Краткие итоги:
    • Поиск в __dict__ через in — оптимальное решение, если точно известно, где мы ищем.
    • hasattr показывает стабильно ровную работу при любых запросах, очень хорошо использовать тогда, когда вероятность того, что атрибута не будет, есть.
    • try/except + прямой запрос свойства быстро работает, когда никакого исключения не случается, иначе — сильно чихает (test_exc_direct_false работал аж 49.748 секунд!). Вывод — можно использовать тогда, когда вероятность того, что атрибут будет там, где ему положено быть, очень и очень велика.
    • dir — заслуженный слоупок Python-а. Использовать его для целей проверки наличия атрибута — расстрельная статья.
    Вот исходный код тестировщика:

    #!/usr/bin/env python
    # coding: utf8
     
    import time
     
    __times__ = 10000000
     
    def timeit(func, res):
            '''Check if 'func' returns 'res', if true, execute it '__times__' times (__times__ should be defined in parent namespace) measuring elapsed time.'''
            assert func() == res
           
            t_start = time.clock()
            for i in xrange(__times__):
                    func()
            return time.clock() - t_start
     
    # Define test classes and create instance of top-level class.
    class TestClass3(object):
            c3_ca = 1
           
            def __init__(self):
                    self.c3_ia = 1
     
    class TestClass2(TestClass3):
            c2_ca = 1
     
            def __init__(self):
                    TestClass3.__init__(self)
                    self.c2_ia = 2
     
    class TestClass(TestClass2):
            c1_ca = 1
           
            def __init__(self):
                    TestClass2.__init__(self)
                    self.c1_ia = 2
     
    obj = TestClass()
     
    # Legend:
    #
    # hasattr, exc_direct, exc_getattr, dict_lookup, dict_lookup_contains, dir - attribute accessing methods.
    # true, false - if 'true' we are checking for really existing attribute.
    # this, parent - if 'this' we are looking for attribute in the top-level class, otherwise in the top-level class' parent's parent.
    # ca, ia - test class attribute ('ca') or instance attribute ('ia') access.
    #
    # Note about __dict__ lookups: they are not suitable for generic attribute lookup because instance's __dict__ stores only instance's attributes. To look for class attributes we should query them from class' __dict__.
     
    # Test query through hasattr
    def test_hasattr_true_this_ca():
            return hasattr(obj, 'c1_ca')
     
    def test_hasattr_true_this_ia():
            return hasattr(obj, 'c1_ia')
     
    def test_hasattr_true_parent_ca():
            return hasattr(obj, 'c3_ca')
     
    def test_hasattr_true_parent_ia():
            return hasattr(obj, 'c3_ia')
     
    def test_hasattr_false():
            return hasattr(obj, 'fake')
     
    # Test direct access to attribute inside try/except
    def test_exc_direct_true_this_ca():
            try:
                    obj.c1_ca
                    return True
            except AttributeError:
                    return False
     
    def test_exc_direct_true_this_ia():
            try:
                    obj.c1_ia
                    return True
            except AttributeError:
                    return False
     
    def test_exc_direct_true_parent_ca():
            try:
                    obj.c3_ca
                    return True
            except AttributeError:
                    return False
     
    def test_exc_direct_true_parent_ia():
            try:
                    obj.c3_ia
                    return True
            except AttributeError:
                    return False
     
    def test_exc_direct_false():
            try:
                    obj.fake
                    return True
            except AttributeError:
                    return False
     
    # Test getattr access to attribute inside try/except
    def test_exc_getattr_true_this_ca():
            try:
                    getattr(obj, 'c1_ca')
                    return True
            except AttributeError:
                    return False
     
    def test_exc_getattr_true_this_ia():
            try:
                    getattr(obj, 'c1_ia')
                    return True
            except AttributeError:
                    return False
     
    def test_exc_getattr_true_parent_ca():
            try:
                    getattr(obj, 'c3_ca')
                    return True
            except AttributeError:
                    return False
     
    def test_exc_getattr_true_parent_ia():
            try:
                    getattr(obj, 'c3_ia')
                    return True
            except AttributeError:
                    return False
     
    def test_exc_getattr_false():
            try:
                    getattr(obj, 'fake')
                    return True
            except AttributeError:
                    return False
     
    # Test attribute lookup in dir()
    def test_dir_true_this_ca():
            return 'c1_ca' in dir(obj)
     
    def test_dir_true_this_ia():
            return 'c1_ia' in dir(obj)
     
    def test_dir_true_parent_ca():
            return 'c3_ca' in dir(obj)
     
    def test_dir_true_parent_ia():
            return 'c3_ia' in dir(obj)
     
    def test_dir_false():
            return 'fake' in dir(obj)
     
    # Test attribute lookup in __dict__
    def test_dict_lookup_true_this_ca():
            return obj.__dict__.has_key('c1_ca')
     
    def test_dict_lookup_true_this_ia():
            return obj.__dict__.has_key('c1_ia')
     
    def test_dict_lookup_true_parent_ca():
            return obj.__dict__.has_key('c3_ca')
     
    def test_dict_lookup_true_parent_ia():
            return obj.__dict__.has_key('c3_ia')
     
    def test_dict_lookup_false():
            return obj.__dict__.has_key('fake')
     
    # Test attribute lookup in __dict__ through __contains__
    def test_dict_lookup_contains_true_this_ca():
            return 'c1_ca' in obj.__dict__
     
    def test_dict_lookup_contains_true_this_ia():
            return 'c1_ia' in obj.__dict__
     
    def test_dict_lookup_contains_true_parent_ca():
            return 'c3_ca' in obj.__dict__
     
    def test_dict_lookup_contains_true_parent_ia():
            return 'c3_ia' in obj.__dict__
     
    def test_dict_lookup_contains_false():
            return 'fake' in obj.__dict__
     
    # TEST
    tests = {
            'hasattr': {
                    'test_hasattr_true_this_ca': True,
                    'test_hasattr_true_this_ia': True,
                    'test_hasattr_true_parent_ca': True,
                    'test_hasattr_true_parent_ia': True,
                    'test_hasattr_false': False,
            },
            'exc_direct': {
                    'test_exc_direct_true_this_ca': True,
                    'test_exc_direct_true_this_ia': True,
                    'test_exc_direct_true_parent_ca': True,
                    'test_exc_direct_true_parent_ia': True,
                    'test_exc_direct_false': False,
            },
            'exc_getattr': {
                    'test_exc_getattr_true_this_ca': True,
                    'test_exc_getattr_true_this_ia': True,
                    'test_exc_getattr_true_parent_ca': True,
                    'test_exc_getattr_true_parent_ia': True,
                    'test_exc_getattr_false': False,
            },
            'dict_lookup': {
                    'test_dict_lookup_true_this_ca': True,
                    'test_dict_lookup_true_this_ia': True,
                    'test_dict_lookup_true_parent_ca': True,
                    'test_dict_lookup_true_parent_ia': True,
                    'test_dict_lookup_false': False,
            },
            'dict_lookup_contains': {
                    'test_dict_lookup_contains_true_this_ca': True,
                    'test_dict_lookup_contains_true_this_ia': True,
                    'test_dict_lookup_contains_true_parent_ca': True,
                    'test_dict_lookup_contains_true_parent_ia': True,
                    'test_dict_lookup_contains_false': False,
            },
            'dir': {
                    'test_dir_true_this_ca': True,
                    'test_dir_true_this_ia': True,
                    'test_dir_true_parent_ca': True,
                    'test_dir_true_parent_ia': True,
                    'test_dir_false': False,
            },
    }
     
    # Perform tests
    results = {}
    results_exc = {}
     
    for (test_group_name, test_group) in tests.iteritems():
            results_group = results[test_group_name] = {}
            results_exc_group = results_exc[test_group_name] = {}
            for (test_name, test_expected_result) in test_group.iteritems():
                    test_func = locals()[test_name]
                    print '%s::%s...' % (test_group_name, test_name)
                    try:
                            test_time = timeit(test_func, test_expected_result)
                            results_group[test_name] = test_time
                    except Exception, exc:
                            results_group[test_name] = None
                            results_exc_group[test_name] = exc
     
    # Process results
    group_results = []
     
    for (group_name, group_tests) in results.iteritems():
            group_true_time = 0.0
            group_true_count = 0
            group_false_time = 0.0
            group_false_count = 0
            group_fail_count = 0
           
            for (test_name, test_time) in group_tests.iteritems():
                    if test_time is not None:
                            if tests[group_name][test_name]:
                                    group_true_count += 1
                                    group_true_time += test_time
                            else:
                                    group_false_count += 1
                                    group_false_time += test_time
                    else:
                            group_fail_count += 1
           
            group_time = (group_true_time / group_true_count + group_false_time / group_false_count) / 2
            group_results.append((group_name, group_time, group_fail_count))
     
    group_results.sort(key = lambda (group_name, group_time, group_fail_count): group_time)
     
    # Output results
    print
    print 'Групповой зачет:'
     
    for (group_name, group_time, group_fail_count) in group_results:
            print '%-25s: %10f [%d subtests failed]' % (group_name, group_time, group_fail_count)
     
    print 'Персональный зачет:'
    all_results = []
    for (group_name, group_tests) in results.iteritems():
            for (test_name, test_time) in group_tests.iteritems():
                    all_results.append((group_name, test_name, test_time))
    all_results.sort(key = lambda (group_name, test_name, test_time): test_time)
     
    for (group_name, test_name, test_time) in all_results:
            if test_time is not None:
                    print '%-50s: %10f' % (test_name, test_time)
            else:
                    print '%-50s: FAILED [%r]' % (test_name, results_exc[group_name][test_name])


    Чуть не забыл… Компьютер, на котором выполнялся тест: CPU: Intel Pentium D CPU 3.40GHz (2 ядра (но использовалось, очевидно, только одно)); RAM: 2Gb. Если это кому-то интересно, конечно.

    Update #1 (Результаты теста на __getattribute__ и сравнение с предыдущими результатами):
    Использовался следующий класс в качестве замены предыдущей цепочке:

    __attributes__ = ('c1_ca', 'c3_ca', 'c1_ia', 'c3_ia')
    class TestClass(object):
            def __getattribute__(self, name):
                    if name in __attributes__:
                            return 1
                    else:
                            raise AttributeError()


    Результаты замера (с учётом getattr(obj, name, None) is not None)

    Групповой зачет:
    dict_lookup              :        n/a [5 subtests failed]
    dict_lookup_contains     :        n/a [5 subtests failed]
    hasattr                  :  20.181182 [0 subtests failed]
    getattr                  :  26.283962 [0 subtests failed]
    exc_direct               :  41.779489 [0 subtests failed]
    exc_getattr              :  47.757879 [0 subtests failed]
    dir                      :  98.622183 [4 subtests failed]

    Персональный зачет:
    test_dir_true_parent_ia                           : FAILED [AssertionError()]
    test_dir_true_this_ia                             : FAILED [AssertionError()]
    test_dir_true_this_ca                             : FAILED [AssertionError()]
    test_dir_true_parent_ca                           : FAILED [AssertionError()]
    test_dict_lookup_true_parent_ia                   : FAILED [AttributeError()]
    test_dict_lookup_true_this_ia                     : FAILED [AttributeError()]
    test_dict_lookup_true_this_ca                     : FAILED [AttributeError()]
    test_dict_lookup_true_parent_ca                   : FAILED [AttributeError()]
    test_dict_lookup_false                            : FAILED [AttributeError()]
    test_dict_lookup_contains_true_this_ia            : FAILED [AttributeError()]
    test_dict_lookup_contains_true_parent_ia          : FAILED [AttributeError()]
    test_dict_lookup_contains_true_parent_ca          : FAILED [AttributeError()]
    test_dict_lookup_contains_true_this_ca            : FAILED [AttributeError()]
    test_dict_lookup_contains_false                   : FAILED [AttributeError()]
    test_exc_direct_true_this_ca                      :  13.346949
    test_exc_direct_true_parent_ca                    :  13.970407
    test_exc_direct_true_this_ia                      :  14.621696
    test_hasattr_true_this_ca                         :  15.077735
    test_exc_direct_true_parent_ia                    :  15.146182
    test_exc_getattr_true_parent_ca                   :  16.305500
    test_getattr_true_this_ia                         :  16.976973
    test_hasattr_true_parent_ia                       :  17.196719
    test_hasattr_true_parent_ca                       :  17.613231
    test_getattr_true_this_ca                         :  18.331266
    test_exc_getattr_true_parent_ia                   :  18.720518
    test_hasattr_false                                :  21.983571
    test_getattr_true_parent_ca                       :  22.087115
    test_exc_getattr_true_this_ca                     :  23.072045
    test_hasattr_true_this_ia                         :  23.627484
    test_getattr_true_parent_ia                       :  24.474635
    test_getattr_false                                :  32.100426
    test_exc_getattr_true_this_ia                     :  34.555669
    test_exc_direct_false                             :  69.287669
    test_exc_getattr_false                            :  72.352324
    test_dir_false                                    :  98.622183


    Теперь переходим к сравнению результатов…

    Ключ: имя группы время, потраченное на нормальных классах [количество сбоев на них же] | среднее между обоими показателями по времени -- соотношение __getattribute__-показателя к нормальному | время, потраченное на __getattribute__-классах [количество сбоев там же]
    Групповой зачет (сортировка по среднему времени):
    dict_lookup              :   7.672500 [2] |        n/a --        n/a |        n/a [5]
    dict_lookup_contains     :   5.800250 [2] |        n/a --        n/a |        n/a [5]
    hasattr                  :  12.171750 [0] |  16.176466 --   1.658035 |  20.181182 [0]
    getattr                  :  15.350072 [0] |  20.817017 --   1.712302 |  26.283962 [0]
    exc_direct               :  27.785500 [0] |  34.782495 --   1.503644 |  41.779489 [0]
    exc_getattr              :  32.088875 [0] |  39.923377 --   1.488300 |  47.757879 [0]
    dir                      : 267.500500 [0] | 183.061342 --   0.368680 |  98.622183 [4]

    Групповой зачет (сортировка по соотношению):
    dict_lookup              :   7.672500 [2] |        n/a --        n/a |        n/a [5]
    dict_lookup_contains     :   5.800250 [2] |        n/a --        n/a |        n/a [5]
    dir                      : 267.500500 [0] | 183.061342 --   0.368680 |  98.622183 [4]
    exc_getattr              :  32.088875 [0] |  39.923377 --   1.488300 |  47.757879 [0]
    exc_direct               :  27.785500 [0] |  34.782495 --   1.503644 |  41.779489 [0]
    hasattr                  :  12.171750 [0] |  16.176466 --   1.658035 |  20.181182 [0]
    getattr                  :  15.350072 [0] |  20.817017 --   1.712302 |  26.283962 [0]


    Ключ: имя теста время, потраченное на нормальных классах | среднее между обоими показателями по времени -- соотношение __getattribute__-показателя к нормальному | время, потраченное на __getattribute__-классах
    Персональный зачет (сортировка по среднему времени):
    test_dict_lookup_true_parent_ia      
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 16
    • +2
      Самый первый вопрос: стоит ли шкурка вычинки? Стоило ли проделывать такое количество работы ради получения очевидных результатов? Помоему достаточно иметь общее представление о затратах на каждый из методов.

      __dict__ — поиск значения в ассоциативном массиве. Что может быть быстрее? Только прямое обращение к атрибуту, если он есть (в противном случае получим «медленное» исключение).

      hasattr — реализован через getattr и исключения. Тогда очевидно, что по времени затраты будут примерно такие же как и на отдельную проверку getattr с отловом исключения (при наличие атрибута — быстро, при отсутствие — медленно).

      dir — достаточно оценить время выполнения этой функции и сравнить с поиском в ассоциативном массиве.

      Вот и получается, что прямой доступ (при наличии атрибута) и __dict__ — самые быстрые методы. dir — сама по себе медленная функция. В остальных случаях, будут выскакивать и ловиться исключения, а это «тяжелый» и медленный механизм. Поэтому, единственное, что заслуживает внимание — это сравнение dir и случаев с исключениями.

      Кстати, а как на счет getattr(obj, attr_name, None)? Имхо, если атрибут не найден, это должно работать быстрее, чем бросать исключение.

      Но в любом случае, наглядные результаты всегда надежней, чем сухая теория :)
      • 0
        Самый первый вопрос: стоит ли шкурка вычинки? Стоило ли проделывать такое количество работы ради получения очевидных результатов? Помоему достаточно иметь общее представление о затратах на каждый из методов.

        Думаю, стоит. Я не берусь утверждать, что результаты очевидны для всех. Работы в действительно было потрачено немного. А данный тест и даёт общее представление о затратах на каждый из методов — для получения более подробной статистики уже в (полу)готовом приложении можно использовать профайлер :)

        __dict__ — поиск значения в ассоциативном массиве. Что может быть быстрее? Только прямое обращение к атрибуту, если он есть...

        Хммм… А разве обращение к атрибуту «напрямую» не есть поиск в __dict__?

        hasattr — реализован через getattr и исключения. Тогда очевидно, что по времени затраты будут примерно такие же как и на отдельную проверку getattr с отловом исключения (при наличие атрибута — быстро, при отсутствие — медленно).

        Если бы hasattr был реализован как getattr с проверкой исключения, то тогда тесты exc_getattr и hasattr показали бы одинаковые результаты — а это не так. Скорее, hasattr проходит по всем __dict__-ам, начиная от экземпляра класса и заканчивая самым «глубоким» объектом класса, осуществляя lookup во всех классах. Возможно, я ошибаюсь — нужно смотреть в исходники Python.

        dir — достаточно оценить время выполнения этой функции и сравнить с поиском в ассоциативном массиве.

        Я взял эту функцию единственно потому, что в одном проекте, написанном доблестными индусскими программистами, dir() используется именно с этой целью :)

        Кстати, а как на счет getattr(obj, attr_name, None)? Имхо, если атрибут не найден, это должно работать быстрее, чем бросать исключение.

        Идея хороша, в принципе, но в общем случае неприменима — атрибут может иметь значение None, и тогда будет непонятно, есть ли атрибут или нет.

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

        Спасибо :)
        • +1
          Первую часть ответа добавил не туда :(

          getattr(obj, attr_name, None) удобно использовать когда нет разницы между отсутствием атрибута и равенством атрибута None.

          А вообще, практика показывает, что очень часто, оптимизация кода типа проверка наличия атрибута через getattr и Exception или поиск в __dict__, практически не дает выигрыша в скорости, так как проблемы кроются в самописных алгоритмах или кривости архитектуры :)
          • 0
            Ой, а таки щито ви имеете пготив самописных алгоритмов? :)

            Если серьезно, не вижу в «самописности» ничего плохого — хотя бы потому, что для каждой отдельно взятой задачи можно создать такой алгоритм, который будет решать её максимально оптимально, вместо того, чтобы использовать какие-либо алгоритмы для общих случаев и накручивать «обёртки» для достижения своей цели.

            А getattr(obj, name, None) is not None я проверил:

            Групповой зачет:
            getattr                  :  15.350072 [0 subtests failed]

            Персональный зачет:
            test_getattr_true_this_ca                         :   7.596199
            test_getattr_true_parent_ca                       :   7.865653
            test_getattr_true_this_ia                         :   8.003450
            test_getattr_true_parent_ia                       :   8.811715
            test_getattr_false                                :  22.630889


            Таким образом, видно, что в общем случае getattr лучше прямого запроса свойства и отработку AttributeError, но хуже hasattr. Удивительно, но этот результат противоречит документации, потому что если hasattr реализован через getattr, то они должны были поменяться местами в общем рейтинге.

            В персональном зачете getattr показывает себя с лучшей стороны, чем обработка исключения, и, думаю, его можно использовать там, где его можно использовать :) — в отличии от hasattr, он возвращает значение элемента.
            • +1
              Я не против самописных алгоритмов :) Я к тому, что они не всегда сразу получаются оптимальными, поэтому если что-то тормозит, то это в первую очередь будет алгоритм, а не метод доступа к атрибутам (про использование dir я даже не думаю :)
      • +1
        Да, согласен, обращение к атрибуту «напрямую» и есть поиск в __dict__. Но в первом случае должно работать быстрее из-за меньшего количества дополнительных действий, как то получение самого __dict__. Я не утверждаю, а предполагаю.

        То, что hasattr реализован через getattr и исключения можно увидеть в доке: «This is implemented by calling getattr(object, name) and seeing whether it raises an exception or not.» При наличие атрибута это видно из тестов test_exc_getattr_true_this_ca и test_hasattr_true_this_ia. Но непонятно, почему так отличается результат в случае отсутствия атрибута (test_hasattr_false и test_hasattr_false).

        • 0
          А насчет получения самого __dict__ — я не помню точно, где и как, но мне встречалась ситуация, где методу __getattribute__(self, name) передавался в качестве name '__dict__'.
          • +1
            Угу, __getattribute__ вызывается при обращении к любым атрибутам, __dict__ в том числе, поэтому первая попытка собственной реализации __getattribute__ обычно заканчивается бесконечной рекурсией :)
        • +1
          OFFTOP: Подскажите, чем делали подсветку кода? Пробовал в своей статье популярный Source Code Highlighter, но он перепахал так, что только маленькие кусочки можно использовать, а большой блок даже не запускается.
          • 0
            Видимо, это повод для следующей статьи. :)
          • +1
            Главный вопрос: а эквивалентны ли эти методы, чтобы можно было сравнивать их скорость?
            Как быть когда у вас атрибуты реально не существуют, а эмулируются через __getattr__ / __setattr__?
            То есть получится, что в одном случае это будет работать быстрее, в другом существенно медленнеее, а в третьем вообще не будет.
            • 0
              Ммм… я тестировал именно «рафинированные случаи» — производительность самих методов, если считать, что объект не желает странного. Методы — эквивалентны, они всегда, везде, при любых условиях (кроме dir, но это отдельный разговор) возвращают булево значение — существует атрибут или нет. Даже при наличии __getattr__/__getattribute__ эти методы будут работать нормально, если «перехватчики» умеют сваливаться с AttributeError() при эмуляции отсутствии атрибута.

              Да, переделал тест на работу с __getattribute__, запустил, жду, пока результаты будут.
              • 0
                Обновил статью.
              • 0
                перенесите, пожалуйста, в блог Python, кармы добавил
                а то искал минут 10: как раз понадобилось, и ведь помню, что что-то проскакивало такое, а где искать — непонятно
                • 0
                  Перенес, спасибо за карму :-)
                • 0
                  спасибо!
                  положил к себе в закладки, на будущее.

                  в мозг положил вывод:
                  иногда можно по разному, но до стадии оптимизации, т.е. почти всегда надо юзать hasattr, но оптимизировать его при желании потом иногда можно
                  за упоминание dir() в коде реального приложения для целей отличных от дебуга — расстреливать

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.