Python: Lazy String Decoding - PullRequest
       18

Python: Lazy String Decoding

3 голосов
/ 01 ноября 2009

Я пишу парсер, и есть много текста для декодирования, но большинство моих пользователей будут заботиться только о нескольких полях из всех данных. Поэтому я хочу выполнять декодирование только тогда, когда пользователь фактически использует некоторые данные. Это хороший способ сделать это?

class LazyString(str):
    def __init__(self, v) :
        self.value = v
    def __str__(self) :
        r = ""
        s = self.value
        for i in xrange(0, len(s), 2) :
            r += chr(int(s[i:i+2], 16))
        return r

def p_buffer(p):
    """buffer : HASH chars"""
    p[0] = LazyString(p[2])

Это единственный метод, который мне нужно переопределить?

Ответы [ 5 ]

2 голосов
/ 02 ноября 2009

Я не уверен, что реализация строкового подкласса здесь очень полезна. Мне кажется, что если вы обрабатываете поток, содержащий петабайты данных, всякий раз, когда вы создали ненужный объект, вы уже проиграли игру. Ваш первый приоритет должен состоять в том, чтобы игнорировать как можно больше входных данных.

Вы, конечно, могли бы создать строковый класс, который бы делал это:

class mystr(str):
    def __init__(self, value):
        self.value = value
        self._decoded = None
    @property
    def decoded(self):
        if self._decoded == None:
            self._decoded = self.value.decode("hex")
            return self._decoded
    def __repr__(self):
        return self.decoded
    def __len__(self):
        return len(self.decoded)
    def __getitem__(self, i):
        return self.decoded.__getitem__(i)
    def __getslice__(self, i, j):
        return self.decoded.__getslice__(i, j)

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

>>> s = mystr('a0a1a2')
>>> s
 ¡¢
>>> len(s)
3
>>> s.capitalize()
'A0a1a2'
1 голос
/ 01 ноября 2009

Я не вижу в вашем коде ленивых вычислений. Тот факт, что вы используете xrange только означает, что список целых чисел от 0 до len(s) будет создан по запросу. Вся строка r будет в любом случае декодирована во время преобразования строки.

Лучший способ реализовать ленивую последовательность в Python - использовать генераторы . Вы можете попробовать что-то вроде этого:

def lazy(v):
    for i in xrange(0, len(v), 2):
        yield int(v[i:i+2], 16)

list(lazy("0a0a0f"))
Out: [10, 10, 15]
0 голосов
/ 02 ноября 2009

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

Скажем, если вы кодируете список строк в виде строк паскаля (т. Е. Префикс с длиной строки, закодированной как фиксированная)-size integer) и скажем, что вы хотите прочитать сотую строку из списка, вы можете искать () вперед для каждой из первых 99 строк и вообще не читать их содержимое.Это даст некоторый выигрыш в производительности, если строки большие.

Если, OTOH, вы закодируете список строк в виде сцепленных 0-концевых перемешиваний, вам придется читать все байты до сотых 0.

Кроме того, вы говорите о некоторых "полях", но ваш пример выглядит совершенно иначе.

0 голосов
/ 01 ноября 2009

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

Несмотря на то, что тип, основанный на str, выглядит для меня немного подозрительно, вы изучили реализацию str, чтобы проверить, имеет ли он атрибут value, который вы устанавливаете в __init__()? Выполнение dir(str) не означает, что такой атрибут есть в str. В этом случае обычные методы str не будут работать с вашими данными вообще, я сомневаюсь, что это тот эффект, который вы хотите получить, в противном случае было бы преимущество подкласса.

Подклассирование базовых типов данных в любом случае немного странно, если только у вас нет особых требований. Для ленивой оценки, которую вы хотите, вам, вероятно, лучше создать свой класс, содержащий строку, а не подкласс str, и написать свой клиентский код для работы с этим классом. После этого вы сможете свободно добавить нужную оценку во времени несколькими способами. Пример, использующий протокол дескриптора, можно найти в этой презентации: Модель объекта Python (поиск "class Jit (object)" «попасть в соответствующий раздел)

0 голосов
/ 01 ноября 2009

То, что вы делаете, уже встроено:

s =  "i am a string!".encode('hex')
# what you do
r = ""
for i in xrange(0, len(s), 2) :
    r += chr(int(s[i:i+2], 16))
# but decoding is builtin
print r==s.decode('hex') # => True

Как видите, все ваше декодирование - s.decode('hex').

Но "ленивое" декодирование для меня звучит как преждевременная оптимизация. Вам понадобятся гигабайты данных, чтобы даже заметить это. Попробуйте профилирование, .decode в 50 раз быстрее вашего старого кода.

Может быть, вы хотите что-то вроде этого:

class DB(object): # dunno what data it is ;)
    def __init__(self, data):
        self.data = data
        self.decoded = {} # maybe cache if the field data is long
    def __getitem__(self, name):
        try:
            return self.decoded[name]
        except KeyError:
            # this copies the fields data
            self.decoded[name] = ret = self.data[ self._get_field_slice( name ) ].decode('hex')
            return ret
    def _get_field_slice(self, name):
        # find out what part to decode, return the index in the data
        return slice( ... )

db = DB(encoded_data)    
print db["some_field"] # find out where the field is, get its data and decode it
...