Почему [0] - это другая функция, а 0 - нет? - PullRequest
5 голосов
/ 16 июня 2020

Я проверил .__code__ объектов для двух функций, которые я считал разными, но обнаружил, что они идентичны, для множества выражений. Если объекты кода идентичны, насколько я понимаю, они компилируются в один и тот же байт-код и, таким образом, являются «одинаковыми» функциями.

Таблица ниже содержит элементы, вставленные перед ; pass, что делает g разными __code__. Поскольку f является функцией «ничего не делать», это предполагает, что все, что находится под «одинаковым», до никогда не выполняет , включая длинную арифметику c. Кроме того, кортеж является «одинаковым», но список и строка являются «diff», поэтому мы можем заключить, что неназначенные выражения, содержащие неизменяемые литералы , не оцениваются. Но есть еще 1/0, которое может быть «исключением» из-за возникновения исключения - тогда что с 10**99 против 10**9? 10**99 не вызывает исключения и может быть назначен.

Я не мог сказать многого из профилирования; и «same», и «diff» имели неразличимое время выполнения. Когда они могли быть , однако, это всегда было с «diff».

Если «same» никогда не выполняются, то как Python определяет, что выполнять, а что нет? Если они выполняются, как выглядят их объекты кода?


То же :

  • 0, (0,), True, False, None
  • 10 ** 9
  • ()
  • -314159.265358 ** (1/12345) / 2.718281828 + 500 - 7j

Diff :

  • [0], {0: 0}
  • 10 ** 99
  • [], {}, ""

Код сравнения :

def compare(fn1, fn2):
    for name in dir(fn1.__code__):
        if (name.startswith("co_") and
            name not in ("co_filename", "co_name", "co_firstlineno")):
            v1 = getattr(fn1.__code__, name)
            v2 = getattr(fn2.__code__, name)
            if v1 == v2:
                print(name.ljust(18), "same")
            else:
                print(name.ljust(18), "diff", v1, v2)

def f():
    pass

def g():
    10 ** 99; pass

Различаются следующие значения: co_name (всегда), co_filename (I Python), co_firstlineno (из файла ) - но не влияет на то, что "выполняется", поправьте меня, если нет; от docs , co_code - вот что должно отличаться.


Примечание : принятый ответ пропускает важную часть интуиции: код неназначенных литералов может быть сохранен, если код, требуемый для хранения значения занимает больше памяти, чем код, необходимый для хранения выражения для вычисления значения ; так обстоит дело с 10 ** 99 (по крайней мере, так утверждалось в комментариях). См. Комментарии под ответом для получения дополнительной информации.

1 Ответ

3 голосов
/ 16 июня 2020

Все литералы группы "diff" либо не являются константами ([], {}), либо не подходят для оптимизации (например, 10 ** 99 меньше своего значения). Все выражения одной и той же группы вычисляют константы, которые можно отбросить. Проверка байт-кода показывает, что выражения полностью удалены:

>>> # CPython 3.7.4
>>> def g(): 10/1; pass
>>> dis.dis(g)
1           0 LOAD_CONST               0 (None)
            2 RETURN_VALUE

Примечательно, ни одно из удаленных выражений не меняет наблюдаемое поведение . Удаляет ли реализация Python ненаблюдаемое поведение или нет - это чисто деталь реализации. Выражения с побочными эффектами, такие как 1/0, не удаляются.

>>> # CPython 3.7.4
>>> def g(): 10/0; pass
>>> dis.dis(g)
1           0 LOAD_CONST               1 (10)
            2 LOAD_CONST               2 (0)
            4 BINARY_TRUE_DIVIDE
            6 POP_TOP
            8 LOAD_CONST               0 (None)
           10 RETURN_VALUE

Для показанных выражений байт-код такой же, как CPython 3.7.4, CPython 3.8.2, PyPy 3.6.9 [PyPy 7.3.0].

На CPython 3.4.3, CPython 2.7.10, PyPy 2.7.13 [PyPy 7.1.1] константное выражение 10/1 оценивается, но не отбрасывается.

>>> # CPython 3.4.3
>>> def g(): 10/1; pass
>>> dis.dis(g)
1           0 LOAD_CONST               3 (10.0)                                                                                  
            3 POP_TOP                                                                                                            
            4 LOAD_CONST               0 (None)                                                                                  
            7 RETURN_VALUE

Выражение "" отбрасывается в любой доступной мне реализации Python.


Поскольку эти оптимизации являются деталями реализации, формальной спецификации нет. Если требуется более глубокое понимание, следует проконсультироваться с самой реализацией. Для CPython хорошей отправной точкой является исходный код оптимизатора глазка .

Чтобы оптимизатор оставался простым, он не работает, когда таблица белье имеет сложное кодирование для пробелы> = 255.

Оптимизация ограничивается простыми преобразованиями, происходящими в пределах одного базового c блока. При всех преобразованиях размер кода остается прежним или меньшим. Для тех, кто уменьшает размер, пробелы изначально заполняются NOP. Позже эти NOP удаляются, а адреса перехода перенаправляются за один проход.

...