Вопрос относительно уникальности экземпляра строки в python - PullRequest
3 голосов
/ 20 июля 2009

Я пытался выяснить, какие целые числа Python создает только один раз (кажется, от -6 до 256), и в процессе наткнулся на некоторое строковое поведение, в котором я не вижу образец. Иногда одинаковые строки создаются разными способами использовать один и тот же идентификатор, иногда нет. Этот код:

A = "10000"
B = "10000"
C = "100" + "00"
D = "%i"%10000
E = str(10000)
F = str(10000)
G = str(100) + "00"
H = "0".join(("10","00"))

for obj in (A,B,C,D,E,F,G,H):
    print obj, id(obj), obj is A

печать:

10000 4959776 True
10000 4959776 True
10000 4959776 True
10000 4959776 True
10000 4959456 False
10000 4959488 False
10000 4959520 False
10000 4959680 False

Я даже не вижу шаблон - за исключением того факта, что первые четыре не имеют явного вызова функции - но, конечно, это не может быть, так как "+" в C, например, подразумевает вызов функции для add . Я особенно не понимаю, почему C и G различаются, так как это означает, что идентификаторы компонентов сложения важнее, чем результат.

Итак, каков особый режим, которым подвергается A-D, заставляя их выступать как один и тот же экземпляр?

Ответы [ 4 ]

10 голосов
/ 20 июля 2009

С точки зрения спецификации языка, любой совместимый компилятор Python и среда выполнения полностью разрешены для любого экземпляра неизменяемого типа создать новый экземпляр ИЛИ найти существующий экземпляр того же типа, который равен требуемому значению, и использовать новая ссылка на тот же экземпляр. Это означает, что всегда некорректно использовать is или сравнение по идентификатору неизменяемых, и любой незначительный выпуск может изменить или изменить стратегию в этом вопросе для улучшения оптимизации.

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

Как решить эти компромиссы реализации, не совсем очевидно - если вы можете идентифицировать эвристики, которые указывают на то, что поиск подходящего существующего экземпляра вероятен и поиск (даже если он потерпит неудачу) будет быстрым, тогда вы можете попытаться ищите и используйте повторно, когда эвристика предложит это, но пропустите это иначе.

В ваших наблюдениях вы, кажется, нашли конкретную реализацию с выпуском точек, которая выполняет некоторую оптимизацию глазка, когда это совершенно безопасно, быстро и просто, поэтому все назначения от A до D сводятся к тому же, что и A ( но от E до F нет, так как они включают в себя именованные функции или методы, которые авторы оптимизатора могли обоснованно считать не на 100% безопасными для принятия семантики - и с низким ROI, если это было сделано - так что они не выглядят как глазок. оптимизировано).

Таким образом, повторное использование одного и того же экземпляра от A до D сводится к тому, что A и B делают это (так как C и D оптимизируются для глазка точно в одну и ту же конструкцию).

Это повторное использование, в свою очередь, четко указывает на тактику компилятора / эвристику оптимизатора, в соответствии с которой идентичные литеральные константы неизменяемого типа в локальном пространстве имен той же функции свернуты для ссылки только на один экземпляр в .func_code.co_consts функции (для использования терминологии текущего CPython). для атрибутов функций и объектов кода) - разумная тактика и эвристика, так как повторное использование одного и того же неизменяемого константы в одной функции встречается довольно часто, и цена выплачивается только один раз (во время компиляции), тогда как преимущество накапливается много раз ( каждый раз, когда функция выполняется, может быть внутри циклов и т. д.).

(Так сложилось, что эти конкретные тактики и эвристики, учитывая их явно положительные компромиссы, были распространены во всех последних версиях CPython, а также, я полагаю, IronPython, Jython и PyPy; -).

Это несколько достойно и интересно изучить, если вы планируете писать компиляторы, среды выполнения, оптимизаторы глазков и т. Д. Для самого Python или аналогичных языков. Я предполагаю, что глубокое изучение внутренних компонентов (в идеале, конечно, множества различных правильных реализаций, чтобы не зацикливаться на причудах конкретного) - хорошая вещь, в настоящее время Python имеет как минимум 4 отдельные реализации, достойные производства, не говоря уже о несколько версий каждой!) могут также косвенно помочь сделать одного из лучших программистов на Python, но особенно важно сосредоточиться на том, что гарантирует самим языком, что несколько меньше, чем вы найдете общие для отдельных реализаций, потому что части, которые «просто случайно» являются общими в данный момент (без необходимости требуется в соответствии со спецификациями языка), вполне могут измениться под вами на следующей стадии выпуска та или иная реализация, и, если ваш производственный код ошибочно полагался на такие детали, это может вызвать неприятные сюрпризы ;-). Плюс - вряд ли когда-либо необходимо или даже особенно полезно полагаться на такие переменные детали реализации, а не на поведение, предписанное языком (если, конечно, вы не кодируете что-то вроде оптимизатора, отладчика, профилировщика или тому подобное; - ).

4 голосов
/ 20 июля 2009

Python разрешен для встроенных строковых констант; A, B, C, D на самом деле являются одинаковыми литералами (если Python видит постоянное выражение, он обрабатывает его как константу).

str на самом деле является классом, поэтому str(whatever) вызывает конструктор этого класса, который должен привести к появлению нового объекта. Это объясняет E, F, G (обратите внимание, что каждый из них имеет отдельную идентичность).

Что касается H, я не уверен, но я бы хотел объяснить, что это выражение слишком сложно для Python, чтобы понять, что оно на самом деле является константой, поэтому оно вычисляет новую строку.

1 голос
/ 20 июля 2009

в ответ на предложение С. Лотта о проверке байт-кода:

import dis
def moo():
    A = "10000"
    B = "10000"
    C = "100" + "00"
    D = "%i"%10000
    E = str(10000)
    F = str(10000)
    G = "1000"+str(0)
    H = "0".join(("10","00"))
    I = str("10000")

    for obj in (A,B,C,D,E,F,G,H, I):
        print obj, id(obj), obj is A
moo()
print dis.dis(moo)

Выходы:

10000 4968128 True
10000 4968128 True
10000 4968128 True
10000 4968128 True
10000 2840928 False
10000 2840896 False
10000 2840864 False
10000 2840832 False
10000 4968128 True
  4           0 LOAD_CONST               1 ('10000')
              3 STORE_FAST               0 (A)

  5           6 LOAD_CONST               1 ('10000')
              9 STORE_FAST               1 (B)

  6          12 LOAD_CONST              10 ('10000')
             15 STORE_FAST               2 (C)

  7          18 LOAD_CONST              11 ('10000')
             21 STORE_FAST               3 (D)

  8          24 LOAD_GLOBAL              0 (str)
             27 LOAD_CONST               5 (10000)
             30 CALL_FUNCTION            1
             33 STORE_FAST               4 (E)

  9          36 LOAD_GLOBAL              0 (str)
             39 LOAD_CONST               5 (10000)
             42 CALL_FUNCTION            1
             45 STORE_FAST               5 (F)

 10          48 LOAD_CONST               6 ('1000')
             51 LOAD_GLOBAL              0 (str)
             54 LOAD_CONST               7 (0)
             57 CALL_FUNCTION            1
             60 BINARY_ADD          
             61 STORE_FAST               6 (G)

 11          64 LOAD_CONST               8 ('0')
             67 LOAD_ATTR                1 (join)
             70 LOAD_CONST              12 (('10', '00'))
             73 CALL_FUNCTION            1
             76 STORE_FAST               7 (H)

 12          79 LOAD_GLOBAL              0 (str)
             82 LOAD_CONST               1 ('10000')
             85 CALL_FUNCTION            1
             88 STORE_FAST               8 (I)

 14          91 SETUP_LOOP              66 (to 160)
             94 LOAD_FAST                0 (A)
             97 LOAD_FAST                1 (B)
            100 LOAD_FAST                2 (C)
            103 LOAD_FAST                3 (D)
            106 LOAD_FAST                4 (E)
            109 LOAD_FAST                5 (F)
            112 LOAD_FAST                6 (G)
            115 LOAD_FAST                7 (H)
            118 LOAD_FAST                8 (I)
            121 BUILD_TUPLE              9
            124 GET_ITER            
        >>  125 FOR_ITER                31 (to 159)
            128 STORE_FAST               9 (obj)

 15         131 LOAD_FAST                9 (obj)
            134 PRINT_ITEM          
            135 LOAD_GLOBAL              2 (id)
            138 LOAD_FAST                9 (obj)
            141 CALL_FUNCTION            1
            144 PRINT_ITEM          
            145 LOAD_FAST                9 (obj)
            148 LOAD_FAST                0 (A)
            151 COMPARE_OP               8 (is)
            154 PRINT_ITEM          
            155 PRINT_NEWLINE       
            156 JUMP_ABSOLUTE          125
        >>  159 POP_BLOCK           
        >>  160 LOAD_CONST               0 (None)
            163 RETURN_VALUE        

так что может показаться, что на самом деле компилятор понимает, что A-D означает одно и то же, и поэтому он экономит память, генерируя ее только один раз (как предположили Алекс, Мацей и Грег). (добавленный случай I кажется просто str (), понимающим, что он пытается создать строку из строки, и просто пропускает ее.)

Спасибо всем, теперь все намного понятнее.

1 голос
/ 20 июля 2009

Я считаю, что короткие строки, которые можно оценить во время компиляции, будут автоматически интернированы. В последних примерах результат не может быть оценен во время компиляции, поскольку str или join могут быть переопределены.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...