предотвращение дублирования кода в Python code redux

#python #code-duplication #control-flow

#python #дублирование кода #поток управления

Вопрос:

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

 from itertools import takewhile

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

af=open('a')
bf=open('b', 'w')
cf=open('c', 'w')

i = 0
if K is None:
    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
            bf.write(out.strip('"') 'n')
            cf.write(line)
else:
    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
            bf.write(out.strip('"') 'n')
  

Возможно ли компактировать этот код? Если у меня есть что-то общее в двух циклах, подобных этому,
одна очевидная возможность — просто исключить общий код, но здесь, фу.
Раздражает то, что единственным отличием здесь является запись в c .

Краткое описание кода: Если K не равно None, то выполните цикл по K строкам a и запишите в оба b и c . В противном случае выполните цикл по всему a и просто напишите в b .

Ответ №1:

Почему бы не использовать только один цикл, но включить условие внутри этого цикла? Кроме того, я думаю, вы можете избавиться от избыточности в этом лямбда-выражении.

 from itertools import takewhile

k_is_none = K is None

def illuminacond(x):
    global i
    global K
    result = x.split(',')[0] != '[Controls]'
    if not k_is_none:
        result = result and i < K
    return result

af=open('a')
bf=open('b', 'w')
cf=open('c', 'w')

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
        bf.write(out.strip('"') 'n')
        if k_is_none:
            cf.write(line)
  

Комментарии:

1. Условие K is None нужно будет проверять по крайней мере K раз. В остальном, нет, ничего плохого. Конечно, в этом случае проверка не требует много времени, но предположим, что это было?

2. Просто кэшируйте его следующим образом: my_cond = K is None

3. Теперь вы выполняете не одну, а две проверки во время выполнения для K . 🙂

4. @faheem-mitha Пока @brian-odell печатал свой ответ, я редактировал предложенный мной код. Если вас беспокоит это сравнение, сделайте это один раз и сохраните результат. k_is_none = K is None

Ответ №2:

Одна проверка, один цикл, никаких классов, оптимизируемый для psyco.

 from itertools import takewhile

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

af=open('a')
bf=open('b', 'w')
cf=open('c', 'w')

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
        bf.write(out.strip('"') 'n')
        action(cf, line)
  

Комментарии:

1. Эй, неплохо! Какова здесь стоимость вызова функции?

2. Кроме того, заинтригован ссылкой на psyco. Хотя я почти ничего не знаю о psyco. Что означает, что код Python должен быть оптимизируемым для psyco?

3. Стоимость вызова функции сильно зависит от версии python / arch. Psyco — оптимизатор python (к сожалению, на данный момент не поддерживается в python> 2.6). Я провел некоторые измерения, используя этот скрипт , чистые вызовы функций CPython в моей системе составляют ~ 170-190ns / вызов, CPython Psyco выдает ~ 9-35ns / вызов.

4. Спасибо, это очень интересная и полезная информация. Я использую Python 2.6 в Debian, поэтому я мог видеть, насколько велика разница при использовании Psyco.

Ответ №3:

Почему бы просто:

 from itertools import takewhile

illuminacond = lambda x: x.split(',')[0] != '[Controls]' and (K is None or i<K) #i'm not so sure about this part, confused me a little :).

af=open('a')
bf=open('b', 'w')
cf=open('c', 'w')

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
        bf.write(out.strip('"') 'n')
        if K is None:
            cf.write(line)
  

Ответ №4:

Как насчет этого (версия на основе второго класса)?

 from itertools import takewhile

class Foo:
    def __init__(self, K = None):
        self.bf=open('b', 'w')
        self.cf=open('c', 'w')
        self.count = 0
        self.K = K

    def Go(self):
        for self.line in takewhile(self.Lamda(), open('a')):
            self.SplitLine()
            if self.IsValidPid():
                self.WriteLineToFiles()

    def SplitLine(self):
        self.lineSplit=self.line.split(',')

    def Lamda(self):
        if self.K is None:
            return lambda x: x.split(',')[0] != '[Controls]'
        else:
            return lambda x: x.split(',')[0] != '[Controls]' and self.count < self.K

    def IsValidPid(self):
        pid=self.lineSplit[1][0:3]
        return pid!='cnv' and pid!='hCV' and pid!='cnv'

    def WriteLineToFiles(self):
        self.count  = 1
        self.bf.write(self.ParseLine())
        if self.K is None:
            self.cf.write(self.line)

    def ParseLine(self):
        return (self.lineSplit[1]   ','   self.lineSplit[2]   ','   
                self.lineSplit[3][1]   self.lineSplit[3][3]   ','  
                self.lineSplit[15]   ','   self.lineSplit[9]   ','   
                self.lineSplit[10]).strip('"') 'n'

Foo().Go()
  

Оригинальная версия:

 from itertools import takewhile

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

def Parse(line):
    return (line[1]   ','   line[2]   ','   line[3][1]   line[3][3]   ','  
            line[15]   ','   line[9]   ','   line[10]).strip('"') 'n'

def IsValidPid(line_split):
    pid=line_split[1][0:3]
    return pid!='cnv' and pid!='hCV' and pid!='cnv'

bf=open('b', 'w')
cf=open('c', 'w')

def WriteLineToFiles(line, line_split):
    bf.write(Parse(line_split))
    if K is None:
        cf.write(line)

i = 0

for line in takewhile(illuminacond, open('a')):
    line_split=line.split(',')
    if IsValidPid(line_split):
        WriteLineToFiles(line, line_split)
        i  = 1
  

Комментарии:

1. Правильно, это то, что я имел в виду под «вычленением общего кода» в моем вопросе выше. Это, безусловно, один из способов.

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

3. Добавлена версия на основе второго класса, которая немного больше инкапсулирует и обеспечивает лучшую абстракцию. Вы сразу поймете, что делает код, проверив Go().

4. 1 за усилия, но я не большой поклонник подхода к вещам с использованием методов класса, если в этом нет абсолютной необходимости.

5. @Faheem: Ну, вы можете выбрать любой из них, мне все равно : D Необходимость может обсуждаться на многих уровнях. Лично я считаю, что чем чище код, тем меньше вам нужно его поддерживать и отлаживать. Вы выигрываете в долгосрочной перспективе. Было бы здорово, если бы кто-нибудь мог продолжить его рефакторинг 🙂