избегая дублирования кода в коде Python - PullRequest
2 голосов
/ 03 мая 2011

Рассмотрим следующий фрагмент Python:

af=open("a",'r')
bf=open("b", 'w')

for i, line in enumerate(af):
    if i < K:
        bf.write(line)

Теперь предположим, что я хочу обработать случай, когда K равно None, поэтому запись продолжается до конца файла.В настоящее время я делаю

if K is None:
    for i, line in enumerate(af):
        bf.write(line)
else:
    for i, line in enumerate(af):            
        bf.write(line)
        if i==K:
            break

Это явно не лучший способ справиться с этим, так как я дублирую код.Есть ли более интегрированный способ, которым я могу справиться с этим?Естественно было бы, чтобы код if/break присутствовал только в том случае, если K не None, но это включает в себя написание синтаксиса на лету в виде макросов Лиспа, чего Python на самом деле не может сделать.Просто чтобы прояснить, меня не волнует конкретный случай (который я выбираю частично из-за его простоты), так же как и изучение общих приемов, с которыми я, возможно, не знаком.

ОБНОВЛЕНИЕ: После прочтения ответы людейопубликовал и провел больше экспериментов, вот еще несколько комментариев.

Как уже было сказано выше, я искал общие методы, которые можно было бы обобщить, и я думаю, что ответ Пола, а именно использование takewhile из iterrools подходит лучше всего.В качестве бонуса, он также намного быстрее, чем наивный метод, который я перечислил выше;Я не уверен почему.Я не очень знаком с itertools, хотя я смотрел на него несколько раз.С моей точки зрения это случай функционального программирования для Win !(Забавно, что однажды автор itertools попросил дать отзыв об отбрасывании takewhile. См. Начало темы http://mail.python.org/pipermail/python-list/2007-December/522529.html.) Я упростил мою ситуацию выше, реальная ситуация немного более грязная - я пишук двум различным файлам в цикле. Таким образом, код выглядит примерно так:

for i, line in enumerate(af):
    if i < K:
        bf.write(line)
        cf.write(line.split(',')[0].strip('"')+'\n')

Учитывая мой опубликованный пример, @Jeff разумно предположил, что в случае, когда K было None, я просто скопировалфайл. Так как на практике я все равно зацикливаюсь, сделать это не такой очевидный выбор. Однако takewhile безболезненно обобщает этот случай. У меня также был другой вариант использования, который я здесь не упомянул, и смог использовать takewhileтам тоже, что было приятно. Второй пример выглядит так (дословно)

i=0
for line in takewhile(illuminacond, af):
    line_split=line.split(',')
    pid=line_split[1][0:3]
    out = line_split[1] + ',' + line_split[2] + ',' + line_split[3][1] + line_split[3][3] + ',' \
                        + line_split[15] + ',' + line_split[9] + ',' + line_split[10]
    if pid!='cnv' and pid!='hCV' and pid!='cnv':
        i = i+1
        of.write(out.strip('"')+'\n')
        tf.write(line)

здесь я смог использовать условие

if K is None:
    illuminacond = lambda x: x.split(',')[0] != '[Controls]'
else:
    illuminacond = lambda x: x.split(',')[0] != '[Controls]' and i < K

для @ исходного примера Пола. Однако яЯ не совсем доволен тем, что я получаю i из внешней области видимости, хотя код работает. Есть ли лучший способ сделать это? Или, возможно, это должен быть отдельный вопрос. В любом случае, спасибовсем, кто ответил на мой вопрос.Похвальный отзыв @Jeff, который сделал несколько хороших предложений.

Ответы [ 5 ]

5 голосов
/ 03 мая 2011
for i, line in enumerate(af):  
    if K is None or i < K:
        bf.write(line)
    else:
        break
2 голосов
/ 03 мая 2011

itertools.takewhile применит ваше условие, а затем выйдет из цикла при первом сбое условия.

from itertools import takewhile

if K is None:
    condition = lambda x: True
else:
    condition = lambda x: x[0] < K

for i,line in takewhile(condition, enumerate(af)):
    bf.write(line)

Если K - None, вы не хотите, чтобы когда-либо останавливался,поэтому функция условия всегда должна возвращать True.Но если вам дано числовое значение для K, то после того, как 0-й элемент кортежа перейдет к условию> = K, тогда takewhile остановится.

1 голос
/ 03 мая 2011

Каким бы ни было K, оно всегда будет меньше бесконечности.

if K is None:
    K = float('inf') # infinity

for i, line in enumerate(af):            
    bf.write(line)
    if i==K:
        break

Или настройка K = -1 работает так же хорошо, хотя и менее семантически верна.В идеале вы должны установить K = max строк в af, но я предполагаю, что данные не доступны дешево.

1 голос
/ 03 мая 2011

Если вы должны выполнить цикл, как насчет этого?

from sys import maxint

limit = K or maxint
for i, line in enumerate(af):
    if i >= limit: break
    bf.write(line)

Или даже это?

from itertools import islice
from sys import maxint

bf.writelines(islice(af, K or maxint))

Зачем вообще цикл в случае, если K равно None? from shutil import copyfile <br/> aname = 'a' bname = 'b' if K is None: copyfile(aname, bname) else: af = open(aname, 'r') bf = open(bname, 'w') for i, line in enumerate(af): if i < K: bf.write(line)

0 голосов
/ 03 мая 2011

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

Я бы начал с того, что остался верен принципам DRY, и удалил дублирующийся код с помощьюнапример, write_until ...

def write_until(file_in,file_out,break_on)
    for i,line in enumerate(file_in)

        if break_on(i,line):
            break
        else:
            file_out.write(line)

af=open("a",'r')
bf=open("b", 'w')

if K is None:
    write_until(af,bf,lambda i,line: False)
else:
    write_until(af,bf,lambda i,line: i>K)

Затем используйте код и посмотрите, действительно ли вам 1008 * необходимо выполнить оптимизацию.Насколько честно вы увидите улучшение производительности при удалении чека if False?Если вам действительно нужно это дополнительное повышение скорости (в чем я сомневаюсь), вам просто придется смириться с некоторым дублированием кода.

...