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
,只需检查函数是否返回某些内容。不太理想,但这可能是一个不错的第一步。我已经重构了我的问题,以澄清实际问题。:)问题其实并不在于保护所述功能不受外界影响。它只是在模块/类本身中尽可能多地强制执行程序的正确性。当前版本具有确保特定执行顺序的优良特性。将三个函数按主函数中正确的顺序进行分组,并将调用限制为只调用主函数,这样就可以实现这种效果。我想你的问题正是如何将调用仅限于主函数,对吗?如果我是你,我会考虑更简单、更易读的选项,比如把这个函数升级到一个类,但是现在我只给了我。