Подтвердить что ты не робот

Когда Python выделяет новую память для идентичных строк?

Две строки Python с одинаковыми символами, a == b, может совместно использовать память, id (a) == id (b), или может быть в памяти дважды, id (a)!= id (b). Попробуйте

ab = "ab"
print id( ab ), id( "a"+"b" )

Здесь Python распознает, что вновь созданный "a" + "b" является тем же как "ab" уже в памяти - неплохо.

Теперь рассмотрим N-длинный список названий состояний   [ "Аризона", "Аляска", "Аляска", "Калифорния"...] (N ~ 500000 в моем случае).
Я вижу 50 разных id() s → каждая строка "Аризона"... хранится только один раз, отлично.
НО напишите список на диск и снова прочитайте его: "тот же самый" список теперь имеет N разных идентификаторов(), больше памяти, см. ниже.

Как получилось: может ли кто-нибудь объяснить выделение строки строки Python?

""" when does Python allocate new memory for identical strings ?
    ab = "ab"
    print id( ab ), id( "a"+"b" )  # same !
    list of N names from 50 states: 50 ids, mem ~ 4N + 50S, each string once
    but list > file > mem again: N ids, mem ~ N * (4 + S)
"""

from __future__ import division
from collections import defaultdict
from copy import copy
import cPickle
import random
import sys

states = dict(
AL = "Alabama",
AK = "Alaska",
AZ = "Arizona",
AR = "Arkansas",
CA = "California",
CO = "Colorado",
CT = "Connecticut",
DE = "Delaware",
FL = "Florida",
GA = "Georgia",
)

def nid(alist):
    """ nr distinct ids """
    return "%d ids  %d pickle len" % (
        len( set( map( id, alist ))),
        len( cPickle.dumps( alist, 0 )))  # rough est ?
# cf http://stackoverflow.com/info/2117255/python-deep-getsizeof-list-with-contents

N = 10000
exec( "\n".join( sys.argv[1:] ))  # var=val ...
random.seed(1)

    # big list of random names of states --
names = []
for j in xrange(N):
    name = copy( random.choice( states.values() ))
    names.append(name)
print "%d strings in mem:  %s" % (N, nid(names) )  # 10 ids, even with copy()

    # list to a file, back again -- each string is allocated anew
joinsplit = "\n".join(names).split()  # same as > file > mem again
assert joinsplit == names
print "%d strings from a file:  %s" % (N, nid(joinsplit) )

# 10000 strings in mem:  10 ids  42149 pickle len  
# 10000 strings from a file:  10000 ids  188080 pickle len
# Python 2.6.4 mac ppc

Добавлено 25jan:
В памяти Python (или любой программы) есть два типа строк:

  • Установки в Ucache уникальных строк: они сохраняют память и быстро делают == b, если оба находятся в Ucache
  • Ostrings, остальные, которые могут храниться любое количество раз.

intern(astring) помещает вёрстка в Ucache (Alex +1); кроме того, что мы ничего не знаем о том, как Python перемещает Ostrings в Ucache - как "a" + "b" встал после "ab"? ( "Строки из файлов" бессмысленны - нет способа узнать.)
Короче говоря, Ucaches (может быть несколько) остаются мутными.

Историческая сноска: SPITBOL uniquified все строки ca. 1970.

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

Этот процесс обычно называется интернингом - и, действительно, выглядит эта страница, он также вызывает интернирование в Python.

Ответ 3

Замечание: очень важно знать время жизни объектов в Python. Обратите внимание на следующий сеанс:

Python 2.6.4 (r264:75706, Dec 26 2009, 01:03:10) 
[GCC 4.3.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a="a"
>>> b="b"
>>> print id(a+b), id(b+a)
134898720 134898720
>>> print (a+b) is (b+a)
False

Ваше мнение, что, напечатав идентификаторы двух отдельных выражений и отметив "они равны ergo, два выражения должны быть равными/эквивалентными/одинаковыми" ошибочными. Одна строка вывода не обязательно подразумевает, что все ее содержимое было создано и/или сосуществует в один и тот же момент времени.

Если вы хотите знать, являются ли два объекта одним и тем же объектом, спросите Python напрямую (используя оператор is).

Ответ 4

x = 42
y = 42
x == y #True
x is y #True

В этом взаимодействии X и Y должны быть == (то же значение), но не является (тот же объект), потому что мы запускали два разных буквальные выражения. Потому что маленький целые числа и строки кэшируются и reused, тем не менее, говорит нам, что они ссылку на один и тот же объект.

На самом деле, если вы действительно хотите посмотреть под капотом вы всегда можете спросить Python, сколько ссылок есть к объекту с использованием getrefcountфункция в стандартном модуле sys возвращает счетчик объектов. Такое поведение отражает один из многих Python оптимизирует свою модель для скорость выполнения.

Обучение Python

Ответ 5

Я нашел хорошую статью, чтобы объяснить поведение intern CPython: http://guilload.com/python-string-interning/

Короче:

  1. Строковый объект в CPython имеет флаг, чтобы указать, что если он в intern.
  2. Внутренние строки, сохраняя их в обычном словаре с ключами и значениями, являются строковыми указателями. Это принимает только string класс.
  3. Interning помогает Python уменьшить потребление памяти, потому что объекты могут ссылаться на один и тот же адрес памяти, и ускорить скорость сравнения, потому что ему нужно только сравнивать строковые указатели.
  4. Python выполняет intern в процессе компиляции, что означает только литеральные строки (или строка может быть вычислена во время компиляции, как 'hello' + 'world')
  5. На ваш вопрос: интернируются только строки длиной 0 или длиной 1 или содержащие только буквы ASCII (az, AZ, 0-9)
  6. Intern в Python из-за строк являются неизменяемыми, иначе не имеет смысла.

Это действительно хорошая статья, я настоятельно рекомендую посетить его сайт и проверить другие, стоящие нашего времени.