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

Python 如何在保持所述步骤之间的关系的同时,将长函数拆分为单独的步骤?,python,Python,我有一个非常长的函数func,它接受一个浏览器句柄,执行一系列请求,并按特定顺序读取一系列响应: 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 respon

我有一个非常长的函数
func
,它接受一个浏览器句柄,执行一系列请求,并按特定顺序读取一系列响应:

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
由于DOM的复杂性,每个操作都需要大量代码,而且它们的增长速度往往非常快

将此函数重构为更小的组件以保持以下属性的最佳方法是什么:

  • 操作和/或其先决条件的执行流与当前版本中的一样得到保证
  • 由于这是一项非常昂贵的操作,因此不使用针对状态的断言检查前提条件
  • 可以在浏览器上多次调用
    func

这是我想出的解决办法。我使用了一个装饰器(与中的装饰器密切相关),它只允许调用一次函数

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()
您将看到,如果重新调用任何函数,该函数将抛出一个
异常
,说明函数已被调用

问题在于,如果您需要再次调用
state()
,那么您就被套住了。除非您将这些函数实现为私有函数,否则我认为,由于Python的作用域规则的性质,您不能完全做您想要做的事情

编辑
您还可以删除decorator中的
else
,您的函数将始终返回
None

这里是我曾经用于状态机的代码片段

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

您可以在
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
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'

调用
func
时,这三个部分按顺序执行。但是,如果您首先调用
func('TEST')
,则可以访问本地
glob
变量作为
testCR
,以及3个子函数作为
testA
testB
testC
。这样,您仍然可以使用定义良好的输入单独测试3个部分,并控制其输出。

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


如果唯一的目的是可读性,我会将func分为三个“私有”>或“受保护”的属性(即_func1或__func1)和一个私有或受保护的属性>,使状态在函数之间共享

这对我来说很有意义,而且在面向对象编程中似乎比其他选项更常见。把这个例子当作一个备选方案:

您的类(teste.py):

其他文件:

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'
回溯(最近一次呼叫最后一次):
文件“C:/Users/Lucas/PycharmProjects/testes/other.py”,第6行,在
t、 _uuufunc_ua()
AttributeError:测试实例没有属性“\uu func\u a”
如果尝试访问受保护的环境变量,也会发生同样的情况:

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
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'
回溯(最近一次呼叫最后一次):
文件“C:/Users/Lucas/PycharmProjects/testes/other.py”,第5行,在
打印环境
AttributeError:测试实例没有属性“\uu环境”

在我看来,这是解决您的问题的最优雅、最简单、最易读的方法,让我知道它是否适合您的需要:)

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

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

您可以创建一个
@只调用一次
装饰器。但不确定如何最小化范围。如果唯一的目的是可读性,我会将func分为三个“私有”或“受保护”的func(即
\u func1
\u func1
)以及一个私有或受保护的财产,它保持功能之间的状态共享。那么TCO呢?@AliSAIDOMAR我不确定这如何适用于我的情况。您是否建议每一步调用下一步?事实上,每一步都知道要针对条件调用下一步,但不确定这有何帮助。可能包括一个解释?我明白了,但是作为一个函数被多次调用。也许我应该在问题正文中详细说明这一点。。。这让问题变得更难了。。。您可以删除
else
,只需检查函数是否返回某些内容。不太理想,但这可能是一个不错的第一步。我已经重构了我的问题,以澄清实际问题。:)问题其实并不在于保护所述功能不受外界影响。它只是在模块/类本身中尽可能多地强制执行程序的正确性。当前版本具有确保特定执行顺序的优良特性。将三个函数按主函数中正确的顺序进行分组,并将调用限制为只调用主函数,这样就可以实现这种效果。我想你的问题正是如何将调用仅限于主函数,对吗?如果我是你,我会考虑更简单、更易读的选项,比如把这个函数升级到一个类,但是现在我只给了我。