在python模块中强制执行方法顺序

时间:2011-10-26 17:02:37

标签: oop singleton python

处理必须按特定顺序调用方法的模块的最pythonic方法是什么?

例如,我有一个XML配置,在执行任何其他操作之前必须先读取,因为配置会影响行为。必须首先使用提供的配置文件调用parse_config()。在调用query_data()之前,调用parse_config()等其他支持方法将无效。

我首先将其作为单例实现,以确保在初始化时传递配置文件名,但注意到模块实际上是单例,它不再是类,而只是常规模块。

在模块中强制首先调用parse_config的最佳方法是什么?

编辑值得注意的是,该功能实际上是parse_config(configfile)

6 个答案:

答案 0 :(得分:7)

如果对象在调用之前无效,则在__init__中调用该方法(或使用工厂函数)。你不需要任何愚蠢的单身,这是肯定的。

答案 1 :(得分:2)

我一直在使用的模型是后续函数仅作为前面函数返回值的方法,如:

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,)使得流畅的库能够处理复杂的流水线操作,其中库中的后续步骤需要早期计算的结果和新鲜的来自调用函数的输入。

一个有趣的结果是错误处理。我发现在管道步骤中处理易于理解的错误的最佳方法是有一个空白的错误类,据说可以处理管道中的每个函数(初始函数除外)但这些函数(除了可能的终端函数)仅返回{ {1}}:

self

在您的示例中,您将拥有Parser类,它几乎只有一个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() 函数返回一个ConfiguredParser类的实例,它将执行所有只有正确配置的解析器才能执行的操作做。这使您可以访问多个配置和处理配置失败尝试等操作。

答案 2 :(得分:1)

正如Cat Plus Plus所说的那样,将行为/功能包装在一个类中,并将所有必需的设置放在__init__方法中。您可能会抱怨这些函数看起来并不像对象中的自然属性,因此,这是糟糕的OO设计。如果是这种情况,请将您的类/对象视为名称间距的一种形式。它比以某种方式强制执行函数调用顺序或使用单例更清晰,更灵活。

答案 3 :(得分:0)

如果在配置函数之前调用函数,您希望错误消息的友好程度如何?

最不友好的是不要做任何额外的事情,并且让这些功能在AttributeError s,IndexError等等的情况下失败。

最友好的是具有引发信息异常的存根函数,例如自定义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()方法,然后将所有错误提升存根替换为实际记忆的函数。

答案 4 :(得分:0)

模块在使用之前需要“配置”的简单要求最好由在__init__方法中执行“配置”的类处理,如在当前接受的答案中那样。其他模块函数成为该类的方法。 尝试制作单身人士没有任何好处......调用者可能希望同时运行两个或更多不同配置的小工具。

从那里继续到更复杂的要求,例如方法的时间顺序:

这可以通过在对象的属性中维护状态来以非常一般的方式处理,这通常在任何OOPable语言中完成。每个具有先决条件的方法都必须检查是否满足这些先决条件。

使用替换方法进行混淆是与COBOL ALTER verb相提并论,并且使用装饰器会变得更糟 - 它只是不会/不应该通过代码审查。

答案 5 :(得分:-1)

一个模块没有做任何事情,它没有被告知这样做,把你的函数调用放在模块的底部,这样当你导入它时,事情按你指定的顺序运行:

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