Каков хороший способ украсить итератор для изменения значения перед вызовом next в python? - PullRequest
5 голосов
/ 14 июня 2011

Я работаю над проблемой, которая включает проверку формата в унифицированном патче различий.

Переменные во внутреннем формате могут занимать несколько строк одновременно, поэтому я написал генератор, который извлекает каждую строку и возвращает переменную, когда она завершена.

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

from collections import Iterable

def inner_format_validator(inner_item):
    # Do some validation to inner items
    return inner_item[0] != '+'

def inner_gen(iterable):
    for inner_item in iterable:
        # Operates only on inner_info type data
        yield inner_format_validator(inner_item)

def outer_gen(iterable):
    class DecoratedGenerator(Iterable):
        def __iter__(self):
            return self
        def next(self):
            # Using iterable from closure
            for outer_item in iterable:
                self.outer_info = outer_item[0]
                inner_item = outer_item[1:]
                return inner_item
    decorated_gen = DecoratedGenerator()
    for inner_item in inner_gen(decorated_gen):
        yield inner_item, decorated_gen.outer_info

if __name__ == '__main__':    
    def wrap(string):
        # The point here is that I don't know what the first character will be
        pseudo_rand = len(string)
        if pseudo_rand * pseudo_rand % 2 == 0:
            return '+' + string
        else:
            return '-' + string

    inner_items = ["whatever"] * 3
    # wrap screws up inner_format_validator
    outer_items = [wrap("whatever")] * 3
    # I need to be able to
    # iterate over inner_items
    for inner_info in inner_gen(inner_items):
        print(inner_info)
    # and iterate over outer_items
    for outer_info, inner_info in outer_gen(outer_items):
        # This is an infinite loop
        print(outer_info)
        print(inner_info)

Есть какие-нибудь идеи относительно лучшего, более питонского способа сделать это?

Ответы [ 3 ]

2 голосов
/ 14 июня 2011

Мне все еще не очень нравится это, но, по крайней мере, оно короче и чуть более питонично:

from itertools import imap, izip
from functools import partial

def inner_format_validator(inner_item):
    return not inner_item.startswith('+')

inner_gen = partial(imap, inner_format_validator)

def split(astr):
    return astr[0], astr[1:]

def outer_gen(iterable):
    outer_stuff, inner_stuff = izip(*imap(split, iterable))
    return izip(inner_gen(inner_stuff), outer_stuff)

[РЕДАКТИРОВАТЬ] inner_gen() и outer_gen() без imap и частично:

def inner_gen(iterable):
    for each in iterable:
        yield inner_format_validator(each)

def outer_gen(iterable):
    outer_stuff, inner_stuff = izip(*(split(each) for each in iterable))
    return izip(inner_gen(inner_stuff), outer_stuff)

Может быть, это лучшее, хотя и другое, решение:

def transmogrify(iter_of_iters, *transmogrifiers):
    for iters in iter_of_iters:
        yield (
            trans(each) if trans else each
                for trans, each in izip(transmogrifiers, iters)
        )

for outer, inner in transmogrify(imap(split, stuff), inner_format_validator, None):
    print inner, outer
2 голосов
/ 14 июня 2011

Я бы сделал что-то попроще, например так:

def outer_gen(iterable):

    iterable = iter(iterable)
    first_item = next(iterable)
    info = first_item[0]

    yield info, first_item[1:]

    for item in iterable:
        yield info, item

Это выполнит 4 первые строки только один раз, затем войдет в цикл и выдаст то, что вы хотите.

Вы, вероятно, хотитедобавьте немного try / except к cacth IndexErrors здесь и там.

Если вы хотите принимать значения, когда они начинаются с чего-то или наоборот, помните, что вы можете использовать множество вещей изitertools набор инструментов, и в частности dropwhile, takewhile и chain:

>>> import itertools
>>> l = ['+foo', '-bar', '+foo']
>>> list(itertools.takewhile(lambda x: x.startswith('+'), l))
['+foo']
>>> list(itertools.dropwhile(lambda x: x.startswith('+'), l))
['-bar', '+foo']
>>> a = itertools.takewhile(lambda x: x.startswith('+'), l)
>>> b = itertools.dropwhile(lambda x: x.startswith('+'), l)
>>> list(itertools.chain(a, b))
['+foo', '-bar', '+foo']

И помните, что вы можете создавать генераторы, такие как списки понимания, сохранять их в переменных и связывать их, каквы бы передали команды linux:

import random

def create_item():
    return random.choice(('+', '-')) + random.choice(('foo', 'bar'))

random_items = (create_item() for s in xrange(10))
added_items = ((i[0], i[1:]) for i in random_items if i.startswith('+'))
valid_items = ((prefix, line) for prefix, line in added_items if 'foo' in line)

print list(valid_items)

При всем этом вы сможете найти какой-нибудь питонический способ решения вашей проблемы: -)

1 голос
/ 14 июня 2011

Я думаю, что это сделает то, что вы хотели, если вы измените определение DecoratedGenerator на это:

class DecoratedGenerator(Iterable):
    def __iter__(self):
        # Using iterable from closure
        for outer_item in iterable:
            self.outer_info = outer_item[0]
            inner_item = outer_item[1:]
            yield inner_item

Ваша первоначальная версия никогда не прекращала свою работу, поскольку ее метод next() не имел состояния и возвращал одно и то же значение при каждом вызове. Однако вам совсем не нужно было использовать метод next () - вы можете реализовать __iter__() самостоятельно (как я), и тогда все будет работать нормально.

...