Могу ли я указать метод экземпляра другого класса в качестве переменной для моего метода?

#python #refactoring #instance-methods

#python #рефакторинг #методы экземпляра

Вопрос:

Я все еще относительно новичок в Python, 1-2 года обучаюсь самостоятельно и пытаюсь улучшить структуру своего кода, поэтому я рефакторингую некоторые старые программы, которые я написал. В одной программе я определил пару методов для записи файлов. Первый использует «write» для дампа огромного http-ответа. Второй использует «линии записи» для сброса различных производных списков, например списков ссылок, или форм, или других извлеченных данных.

Изначально я учитывал именование файла:

     @property
    def baseFilename(self):
        unacceptable = re.compile(r'W ')
        fname = re.sub(unacceptable,'-',self.myUrl)
        t = datetime.datetime.now()
        dstring = "%s%s%s%s%s%s" % (t.year, t.month, t.day, t.hour, t.minute, t.second)
        fullname = fname   '_'   dstring   '.html'
        return fullname
  

Но у меня есть большой избыточный блок кода в каждом методе записи:

     def writeFile(self, someHtml, writeMethod=write, prefix="RESPONSE_"):
        '''The calling functions will supply only the data to be written and 
           static prefixes, e.g. "full_" for the entire http-response.
        '''

       fullpath = self.myDump   prefix   self.baseFilename
       with open(fullpath, 'w') as h:
           h.write(someHtml)
           h.close()
       print "saved %s" % fullpath
       return fullpath

    def writeList(self, someList, prefix="mechList_"):
        '''Like write file but for one of the many lists outputted.  
           How do I refactor this, since redundant?
        '''

        fullpath = self.myDump   prefix   self.baseFilename
        with open(fullpath, 'w') as h:
            h.writelines(someList)
            h.close()
        print "saved %s" % fullpath
        return fullpath
  

Я хотел бы иметь возможность добавлять переменную к каждой функции, которая определяет используемый метод записи, например (writeMethod=writelines). Я рассматривал возможность просто передать строку и использовать одну из функций черной магии — exec (), я полагаю, — но это не может быть правильным, поскольку, похоже, никто никогда не использует эти функции. Весь этот пример может быть относительно глупым, поскольку я мог бы просто обойти его, но я решил, что мне будет полезно знать, как передавать такого рода методы экземпляра (это правильный термин?). Связано ли это с привязкой и отменой привязки? Все, что мне нужно для хорошего ответа, — это синтаксис, необходимый для передачи ‘write’, ‘writelines’ и т.д. Может быть просто как: writeMethod = insert_your_syntax_here. Хотелось бы получить дополнительное объяснение или руководство. Спасибо.

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

1. Это не «большой» избыточный блок кода. Он довольно маленький и сфокусированный. Я бы не стал с этим связываться. Все в порядке, как есть. Никто не выигрывает в code golf.

2. Кроме того, h.close() вызов является полностью избыточным — в конце концов, это то, что менеджер контекста делает за вас.

3. Вы могли бы создать функцию для fullpath вычисления, возможно, это сэкономило бы вам «много» изменений позже. В остальном, я думаю, все выглядит нормально.

4. @Skurmedel: Хорошее замечание о вычислении полного пути. Ценю совет.

5. @delnan: Я знал, что контекстные менеджеры действуют как средства защиты, но почему-то сложилось впечатление, что я все равно должен вызвать close. Спасибо за указатель.

Ответ №1:

Вы можете получить «связанный метод» из объекта, который затем можно вызывать как функцию без ссылки на объект.

 f = obj.method
f(args)
# is equivalent to
obj.method(args)
  

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

Другим вариантом был бы декоратор, который выделял бы все общие части и передавал остальное в обратный вызов, оформленную функцию:

 def uses_file(prefix_default):
    def decorator(f):
        @functools.wraps(f)
        def decorated(self, data, prefix=prefix_default):
            fullpath = obj.myDump   prefix   obj.baseFilename
            with open(fullpath, 'w') as h:
                f(h, data, prefix)
            print "saved", % fullpath
            return fullpath
        return decorated
    return decorator
  

# …

 @uses_file(default_prefix="RESPONE_")
def writeFile(self, someHtml, prefix):
   '''...'''
   h.write(someHtml)

@uses_file(default_prefix="mechList_")
def writeList(self, someList, prefix):
    '''...'''
    h.writelines(someList)
  

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

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

Ответ №2:

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

 def writeFile(self, someHtml, writeMethod=lambda f, data: f.write(data), 
              prefix="RESPONSE_"):
    '''The calling functions will supply only the data to be written and 
       static prefixes, e.g. "full_" for the entire http-response.
    '''

   fullpath = self.myDump   prefix   self.baseFilename
   with open(fullpath, 'w') as h:
       writeMethod(h, someHtml)
       h.close()
   print "saved %s" % fullpath
   return fullpath