如何在保持所述步骤之间的关系的同时将长函数拆分为单独的步骤?

时间:2016-03-16 10:56:51

标签: python

我有一个很长的函数def func(browser): # make sure we are logged in otherwise log in # make request to /search and check that the page has loaded # fill form in /search and submit it # read table of response and return the result as list of objects ,它接受​​浏览器处理并执行一堆请求,并按特定顺序读取一堆响应:

func

由于DOM的复杂性,每个操作都需要大量代码,并且它们的增长速度非常快。

将此函数重构为较小的组件的最佳方法是什么,以便以下属性仍然存在:

  • 保证操作的执行流程和/或它们的前提条件,就像在当前版本中一样
  • 不会使用针对状态的断言来检查先决条件,因为这是一项非常昂贵的操作
  • $(function () { $("#txtFrom").datepicker({ numberOfMonths: 2, onSelect: function (selected) { var dt = new Date(selected); dt.setDate(dt.getDate() + 1); $("#txtTo").datepicker("option", "minDate", dt); } }); $("#txtTo").datepicker({ numberOfMonths: 2, onSelect: function (selected) { var dt = new Date(selected); dt.setDate(dt.getDate() - 1); $("#txtFrom").datepicker("option", "maxDate", dt); } }); }); 可以在浏览器上多次调用

5 个答案:

答案 0 :(得分:4)

只需将三个辅助方法包装在一个类中,并跟踪允许在实例中运行的方法。

class Helper(object):

    def __init__(self):
        self.a = True
        self.b = False
        self.c = False

    def funcA(self):
        if not self.A:
            raise Error("Cannot run funcA now")
        # do stuff here
        self.a = False
        self.b = True
        return whatever

    def funcB(self):
        if not self.B:
            raise Error("Cannot run funcB now")
        # do stuff here
        self.b = False
        self.c = True
        return whatever

    def funcC(self):
        if not self.C:
            raise Error("Cannot run funcC now")
        # do stuff here
        self.c = False
        self.a = True
        return whatever

def func(...):
    h = Helper()
    h.funcA()
    h.funcB()
    h.funcC()

# etc

调用方法的唯一方法是,如果其标志为true,并且每个方法都清除其自己的标志并在退出之前设置下一个方法的标志。只要你不碰h.a等。直接,这确保每个方法只能以正确的顺序调用。

或者,您可以使用单个标志,该标志是对当前允许运行的函数的引用。

class Helper(object):
    def __init__(self):
        self.allowed = self.funcA
    def funcA(self):
        if self.allowed is not self.funcA:
            raise Error("Cannot run funcA now")
        # do stuff
        self.allowed = self.funcB
        return whatever
    # etc

答案 1 :(得分:2)

这是我提出的解决方案。我使用了一个装饰器(与this blog post中的装饰密切相关),它只允许调用一次函数。

def call_only_once(func):  
  def new_func(*args, **kwargs):  
    if not new_func._called:  
      try:  
        return func(*args, **kwargs)  
      finally:  
        new_func._called = True  
    else:
      raise Exception("Already called this once.")
  new_func._called = False  
  return new_func  

@call_only_once  
def stateA(): 
   print 'Calling stateA only this time' 

@call_only_once  
def stateB(): 
   print 'Calling stateB only this time'    

@call_only_once  
def stateC(): 
   print 'Calling stateC only this time' 

def state():
  stateA()
  stateB()
  stateC()

if __name__ == "__main__":
  state()

你会看到,如果你重新调用任何函数,该函数将抛出一个Exception,说明函数已被调用。

问题在于,如果您需要再次拨打state(),那么您就会受到冲击。除非你将这些函数实现为私有函数,否则由于Python的作用域规则的性质,我认为你不能完全完全你想要的东西。

修改

您还可以移除装饰器中的else,并且您的函数将始终返回None

答案 2 :(得分:1)

这是我用过状态机的一段代码

class StateMachine(object):
    def __init__(self):
        self.handlers = {}
        self.start_state = None
        self.end_states = []

    def add_state(self, name, handler, end_state=0):
        name = name.upper()
        self.handlers[name] = handler
        if end_state:
            self.end_states.append(name)

    def set_start(self, name):
        # startup state
        self.start_state = name

    def run(self, **kw):
        """
        Run
        :param kw:
        :return:
        """
        # the first .run call call the first handler with kw keywords
        # each registered handler should returns the following handler and the needed kw 
        try:
            handler = self.handlers[self.start_state]
        except:
            raise InitializationError("must call .set_start() before .run()")
        while True:
            (new_state, kw) = handler(**kw)
            if isinstance(new_state, str):
                if new_state in self.end_states:
                    print("reached ", new_state)
                    break
                else:
                    handler = self.handlers[new_state]
            elif hasattr(new_state, "__call__"):
                handler = new_state
            else:
                return

使用

class MyParser(StateMachine):
    def __init__(self):
        super().__init__()
        # define handlers
        # we can define many handler as we want
        self.handlers["begin_parse"] = self.begin_parse
        # define the startup handler
        self.set_start("begin_parse")

    def end(self, **kw):
        logging.info("End of parsing ")
        # no callable handler => end 
        return None, None

    def second(self, **kw):
        logging.info("second  ")
        # do something
        # if condition is reach the call `self.end` handler
        if ...:
            return self.end, {}

    def begin_parse(self, **kw):
        logging.info("start  of parsing ")
        # long process until the condition is reach then call the `self.second` handler with kw new keywords
        while True:
            kw = {}
            if ...:
                return self.second, kw
            # elif other cond:
                # return self.other_handler, kw
            # elif other cond 2:
                # return self.other_handler 2, kw
             else:
                return self.end, kw
# start the state machine
MyParser().run()

将打印

   INFO:root:start  of parsing 
   INFO:root:second  
   INFO:root:End of parsing

答案 3 :(得分:0)

您可以在func功能中使用本地功能。好吧,它们仍然在一个单一的全局函数中声明,但是Python足够好,仍然可以让你访问它们进行测试。

这是一个函数声明和执行3(假定重)子函数的一个例子。它需要一个可选参数test,当设置为TEST时会阻止实际执行,而是提供对各个子函数和局部变量的外部访问:

def func(test=None):
    glob = []
    def partA():
        glob.append('A')
    def partB():
        glob.append('B')
    def partC():
        glob.append('C')
    if (test == 'TEST'):
        global testA, testB, testC, testCR
        testA, testB, testC, testCR = partA, partB, partC, glob
        return None
    partA()
    partB()
    partC()
    return glob

当您致电func时,3个部分将按顺序执行。但是,如果您先拨打func('TEST'),则可以将glob变量视为testCR,将3个子功能设为testAtestB和{{1 }}。这样,您仍然可以使用定义良好的输入单独测试3个部件并控制其输出。

答案 4 :(得分:0)

我会坚持@ user3159253在对原始问题的评论中提出的建议:

  

如果唯一的目的是可读性,我会将功能分成三个“私人”>或“受保护的”(即_func1或__func1)和私人或受保护的财产>这使得状态在函数之间保持共享。

这对我来说很有意义,在面向对象编程中似乎比其他选项更常见。考虑这个例子作为替代:

你的班级(teste.py):

class Test:
    def __init__(self):
        self.__environment = {}             # Protected information to be shared
        self.public_stuff = 'public info'   # Accessible to outside callers

    def func(self):
        print "Main function"
        self.__func_a()
        self.__func_b()
        self.__func_c()
        print self.__environment

    def __func_a(self):
        self.__environment['function a says'] = 'hi'

    def __func_b(self):
        self.__environment['function b says'] = 'hello'

    def __func_c(self):
        self.__environment['function c says'] = 'hey'

其他档案:

from teste import Test

t = Test()
t.func()

这将输出:

Main function says hey guys
{'function a says': 'hi', 'function b says': 'hello', 'function c says': 'hey'}

如果您尝试调用其中一个受保护的函数,则会发生错误:

Traceback (most recent call last):
  File "C:/Users/Lucas/PycharmProjects/testes/other.py", line 6, in <module>
    t.__func_a()
AttributeError: Test instance has no attribute '__func_a'

如果您尝试访问受保护的环境变量,则相同:

Traceback (most recent call last):
  File "C:/Users/Lucas/PycharmProjects/testes/other.py", line 5, in <module>
    print t.__environment
AttributeError: Test instance has no attribute '__environment'

在我看来,这是解决问题的最优雅,简单和可读的方式,让我知道它是否符合您的需求:)