Обеспечение порядка методов в модуле Python - PullRequest
2 голосов
/ 26 октября 2011

Какой самый питонический способ иметь дело с модулем, в котором методы должны вызываться в определенном порядке?

В качестве примера у меня есть конфигурация XML, которую необходимо прочитать, прежде чем делать что-либо еще, потому что конфигурация влияет на поведение. Сначала необходимо вызвать parse_config() с предоставленным файлом конфигурации. Вызов других вспомогательных методов, таких как query_data(), не будет работать, пока не будет вызван parse_config().

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

Каков наилучший способ заставить parse_config вызываться первым в модуле?

Редактировать: Стоит отметить, что функция на самом деле parse_config(configfile)

Ответы [ 6 ]

7 голосов
/ 26 октября 2011

Если объект недействителен перед вызовом, вызовите этот метод в __init__ (или используйте фабричную функцию).Тебе не нужны глупые одиночки, это точно.

2 голосов
/ 26 октября 2011

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

class Second(object):
   def two(self):
     print "two"
     return Third()

class Third(object):
   def three(self):
     print "three"

def one():
   print "one"
   return Second()

one().two().three()

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

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

class Error(object):
   def two(self, *args):
      print "two not done because of earlier errors"
      return self
   def three(self, *args):
      print "three not done because of earlier errors"

class Second(object):
   def two(self, arg):
     if arg == 2:
       print "two"
       return Third()
     else:
       print "two cannot be done"
       return Error()

class Third(object):
   def three(self):
     print "three"

def one(arg):
   if arg == 1:
      print "one"
      return Second()
   else:
      print "one cannot be done"
      return Error()

one(1).two(-1).three()

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

1 голос
/ 26 октября 2011

Как сказал Cat Plus Plus, другими словами, оберните поведение / функции в классе и поместите все необходимые настройки в метод __init__. Вы можете жаловаться, что функции не выглядят так, как будто они естественным образом связаны друг с другом в объекте, и, следовательно, это плохой ОО-дизайн. Если это так, думайте о своем классе / объекте как о форме пространства имен. Это намного чище и гибче, чем пытаться каким-то образом навести порядок вызова функций или использовать синглтоны.

0 голосов
/ 20 ноября 2011

Простое требование о том, что модуль должен быть «настроен» перед его использованием, лучше всего обрабатывается классом, который выполняет «настройку» в методе __init__, как в принятом в настоящее время ответе.Другие функции модуля становятся методами класса. Нет смысла пытаться создать одиночный файл ... вызывающему абоненту может потребоваться одновременная работа двух или более гаджетов различной конфигурации.

Переход от этого к более сложномутребования, такие как временное упорядочение методов:

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

Использование методов замены - это запутывание наравне с глаголом COBOL ALTER , которое усугубляется использованием декораторов - просто не будет / не должно пройти проверку кода.

0 голосов
/ 19 ноября 2011

То, насколько вы хотите, чтобы ваши сообщения об ошибках были дружественными, если функция вызывается до ее настройки.

Наименее дружественным является не делать ничего лишнего, и позволить функциям сбои с шумом при AttributeError s, IndexError s и т. Д.

Наиболее дружественным было бы иметь функции-заглушки, которые вызывают информационное исключение, такое как пользовательский ConfigError: configuration not initialized. Когда вызывается функция ConfigParser(), она может заменить функции-заглушки реальными функциями. Примерно так:

config.py
----------
class ConfigError(Exception):
    "configuration errors"

def query_data():
    raise ConfigError("parse_config() has not been called")

def _query_data():
    do_actual_work()

def parse_config(config_file):
    load_file(config_file)
    if failure:
        raise ConfigError("bad file")
    all_objects = globals()
    for name in ('query_data', ):
        working_func = all_objects['_'+name]
        all_objects[name] = working_func

Если у вас очень много функций, вы можете добавить декораторы для отслеживания имен функций, но это ответ на другой вопрос. ;)

Хорошо, я не удержался - вот версия для декоратора, которая значительно облегчает реализацию моего решения:

class ConfigError(Exception):
    "various configuration errors"

class NeedsConfig(object):
    def __init__(self, module_namespace):
        self._namespace = module_namespace
        self._functions = dict()
    def __call__(self, func):
        self._functions[func.__name__] = func
        return self._stub
    @staticmethod
    def _stub(*args, **kwargs):
        raise ConfigError("parseconfig() needs to be called first")
    def go_live(self):
        for name, func in self._functions.items():
            self._namespace[name] = func

И примерный прогон:

needs_parseconfig = NeedsConfig(globals())

@needs_parseconfig
def query_data():
    print "got some data!"

@needs_parseconfig
def set_data():
    print "set the data!"

def okay():
    print "Okay!"

def parse_config(somefile):
    needs_parseconfig.go_live()

try:
    query_data()
except ConfigError, e:
    print e

try:
    set_data()
except ConfigError, e:
    print e

try:
    okay()
except:
    print "this shouldn't happen!"
    raise

parse_config('config_file')
query_data()
set_data()
okay()

и результаты:

parseconfig() needs to be called first
parseconfig() needs to be called first
Okay!
got some data!
set the data!
Okay!

Как вы можете видеть, декоратор работает, запоминая функции, которые он декорирует, и вместо возврата декорированной функции он возвращает простую заглушку, которая вызывает ConfigError, если он когда-либо вызывается. Когда вызывается подпрограмма parse_config(), необходимо вызвать метод go_live(), который заменит все заглушки, вызывающие ошибки, на фактические запомненные функции.

0 голосов
/ 26 октября 2011

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

test.py

import testmod

testmod.py

def fun1():
    print('fun1')

def fun2():
    print('fun2')

fun1()
fun2()

Когда вы запустите test.py, вы увидите, что fun1 запускается перед fun2:

python test.py 
fun1
fun2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...