简体   繁体   English

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

[英]Enforcing method order in a python module

What is the most pythonic way to deal with a module in which methods must be called in a certain order? 处理必须按特定顺序调用方法的模块的最pythonic方法是什么?

As an example, I have an XML configuration that must be read before doing anything else because the configuration affects behavior. 例如,我有一个XML配置,在执行任何其他操作之前必须先读取,因为配置会影响行为。 The parse_config() must be called first with the config file provided. 必须首先使用提供的配置文件调用parse_config() Calling other supporting methods like query_data() won't work until parse_config() has been called. 像调用其他辅助方法query_data()直到将无法正常工作parse_config()被调用。

I first implemented this as a singleton to ensure that a filename for the config is passed at time of initialization, but noticing that modules are actually singletons, it's no longer a class, but just a regular module. 我首先将其实现为单例,以确保在初始化时传递配置文件名,但注意到模块实际上是单例,它不再是类,而只是常规模块。

What's the best way to enforce the parse_config being called first in a module? 强制首先在模块中调用parse_config的最佳方法是什么?

Edit: Worth noting is that the function is actually parse_config(configfile) 编辑:值得注意的是,该函数实际上是parse_config(configfile)

If the object isn't valid before it's called, then call that method in __init__ (or use a factory function). 如果对象在调用之前无效,则在__init__调用该方法(或使用工厂函数)。 You don't need any silly singletons, that's for sure. 你不需要任何愚蠢的单身,这是肯定的。

The model I have been using is that subsequent functions are only available as methods on the return value of previous functions, like this: 我一直在使用的模型是后续函数仅作为前一个函数的返回值的方法,如下所示:

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()

Properly designed, this style (which I admit is not terribly Pythonic, yet ) makes for fluent libraries to handle complex pipeline operations where later steps in the library require both the results of early calculations and fresh input from the calling function. 设计得当,这种风格(这点我承认是不是非常符合Python, )为使图书馆流畅处理复杂的流水线作业,其中在图书馆后面的步骤需要及早计算和调用函数新鲜输入的两种结果。

An interesting result is error handling. 一个有趣的结果是错误处理。 What I've found is the best way of handling well-understood errors in pipeline steps is having a blank Error class that supposedly can handle every function in the pipeline (except initial one) but those functions (except possibly terminal ones) return only self : 我发现在管道步骤中处理容易理解的错误的最好方法是有一个空白的错误类,据说可以处理管道中的每个函数(除了初始函数),但那些函数(除了可能是终端函数)只返回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()

In your example, you'd have the Parser class, which would have almost nothing but a configure function that returned an instance of a ConfiguredParser class, which would do all the thing that only a properly configured parser could do. 在您的示例中,您将拥有Parser类,它几乎只有一个configure函数返回一个ConfiguredParser类的实例,它将执行只有正确配置的解析器才能执行的所有操作。 This gives you access to such things as multiple configurations and handling failed attempts at configuration. 这使您可以访问多个配置和处理失败的配置尝试。

As Cat Plus Plus said in other words, wrap the behaviour/functions up in a class and put all the required setup in the __init__ method. 换句话说,正如Cat Plus Plus所说,将行为/函数包装在一个类中,并将所有必需的设置放在__init__方法中。 You might complain that the functions don't seem like they naturally belong together in an object and, hence, this is bad OO design. 您可能会抱怨这些函数看起来并不像对象中的自然属性,因此,这是糟糕的OO设计。 If that's the case, think of your class/object as a form of name-spacing. 如果是这种情况,请将您的类/对象视为名称间距的一种形式。 It's much cleaner and more flexible than trying to enforce function calling order somehow or using singletons. 它比以某种方式强制执行函数调用顺序或使用单例更干净,更灵活。

What it comes down to is how friendly do you want your error messages to be if a function is called before it is configured. 它归结为如果在配置函数之前调用函数,您希望您的错误消息是多么友好。

Least friendly is to do nothing extra, and let the functions fail noisily with AttributeError s, IndexError s, etc. 最不友好的是不做任何额外的事情,并且使用AttributeError s, IndexError s等让函数失败。

Most friendly would be having stub functions that raise an informative exception, such as a custom ConfigError: configuration not initialized . 最友好的是具有引发信息异常的存根函数,例如自定义ConfigError: configuration not initialized When the ConfigParser() function is called it can then replace the stub functions with real functions. ConfigParser()函数时,它可以用实际函数替换存根函数。 Something like this: 像这样的东西:

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

If you have very many functions you can add decorators to keep track of the function names, but that's an answer for a different question. 如果你有很多函数你可以添加装饰器来跟踪函数名称,但这是一个不同问题的答案。 ;) ;)

Okay, I couldn't resist -- here is the decorator version, which makes my solution much easier to actually implement: 好吧,我无法抗拒 - 这是装饰器版本,这使我的解决方案更容易实际实现:

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

And a sample run: 并运行示例:

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()

and the results: 结果:

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

As you can see, the decorator works by remembering the functions it decorates, and instead of returning a decorated function it returns a simple stub that raises a ConfigError if it is ever called. 正如您所看到的,装饰器通过记住它修饰的函数来工作,而不是返回一个修饰函数,它返回一个简单的存根,如果它被调用则会引发一个ConfigError When the parse_config() routine is called, it needs to call the go_live() method which will then replace all the error raising stubs with the actual remembered functions. parse_config()例程时,它需要调用go_live()方法,然后将所有引发错误的存根替换为实际的记忆函数。

The simple requirement that a module needs to be "configured" before it is used is best handled by a class which does the "configuration" in the __init__ method, as in the currently-accepted answer. 模块在使用之前需要“配置”的简单要求最好由在__init__方法中执行“配置”的类处理,如在当前接受的答案中那样。 Other module functions become methods of the class. 其他模块函数成为该类的方法。 There is no benefit in trying to make a singleton ... the caller may well want to have two or more differently-configured gadgets operating simultaneously. 尝试制作单身人士没有任何好处......调用者可能希望同时运行两个或更多不同配置的小工具。

Moving on from that to a more complicated requirement, such as a temporal ordering of the methods: 从那里继续到更复杂的需求,例如方法的时间顺序:

This can be handled in a quite general fashion by maintaining state in attributes of the object, as is usually done in any OOPable language. 这可以通过在对象的属性中维护状态来以非常一般的方式处理,这通常在任何OOPable语言中完成。 Each method that has prerequisites must check that those prequisites are satisfied. 每个具有先决条件的方法都必须检查是否满足这些先决条件。

Poking in replacement methods is an obfuscation on a par with the COBOL ALTER verb , and made worse by using decorators -- it just wouldn't/shouldn't get past code review. 更换方法是一种与COBOL ALTER动词相同的混淆,并且使用装饰器会变得更糟 - 它不会/不应该通过代码审查。

A module doesn't do anything it isn't told to do so put your function calls at the bottom of the module so that when you import it, things get ran in the order you specify: 一个模块没有做任何事情,它没有被告知这样做,把你的函数调用放在模块的底部,这样当你导入它时,事情按你指定的顺序运行:

test.py test.py

import testmod

testmod.py testmod.py

def fun1():
    print('fun1')

def fun2():
    print('fun2')

fun1()
fun2()

When you run test.py, you'll see fun1 is ran before fun2: 当你运行test.py时,你会看到fun1在fun2之前运行:

python test.py 
fun1
fun2

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM