找回密码
 会员注册
查看: 30|回复: 0

python篇-pywinauto使用-持续更新

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
71625
发表于 2024-9-9 14:56:03 | 显示全部楼层 |阅读模式
1-pywinauto中的uia是什么意思?在pywinauto库中,uia指的是UIAutomation,这是Windows操作系统提供的一种技术框架,用于实现用户界面(UI)的自动化测试和辅助功能访问。UIAutomation是微软从WindowsVista开始引入的核心技术,旨在为所有UI元素提供一致的编程接口,无论这些元素是由何种技术(如Win32、WPF、UWP等)实现的。在pywinauto中,通过指定backend="uia",用户可以选择使用UIAutomation作为底层引擎来与应用程序的UI元素进行交互。这为自动化测试提供了更广泛的支持,尤其是在处理现代Windows应用(尤其是使用WPF和UWP技术构建的应用)时更为有效。UIAutomation后端相比传统的win32后端,提供了更丰富和高级的元素属性、控制模式(ControlPatterns)以及事件支持,使得自动化脚本可以更加灵活和强大。使用UIAutomation后端,开发者可以更容易地定位和操作UI元素,如获取元素的属性、模拟鼠标和键盘操作、监听UI事件等,这对于创建复杂的自动化测试场景尤其有用。2-pywinauto中main_window.child_window()方法及参数详细介绍在pywinauto中,child_window()方法是用于定位父窗口中的一个特定子窗口或控件。这对于自动化测试中准确地找到并操作特定的UI元素至关重要。下面是对child_window()方法及其参数的详细说明:基本用法child_window()方法属于窗口对象,用于在当前窗口的所有子窗口中搜索符合指定条件的第一个子窗口。基本语法如下:Python1parent_window.child_window(arguments)其中,parent_window是你已经获取到的窗口对象,arguments是用于筛选子窗口的一系列关键字参数。参数详解child_window()方法接受多个关键字参数来精确地定位子窗口,常见的参数包括但不限于:title:字符串,控件的或文本。class_name:字符串,控件的类名。control_type:字符串,控件的类型(仅在UIAbackend下有效)。auto_id:字符串,控件的自动化ID(AutomationId)。name:字符串,控件的名称属性,可能与或自动化ID不同。backend:字符串,指定后端类型,如 'win32' 或 'uia',默认自动选择。process:整数,指定目标窗口所在的进程ID。enabled:布尔值,是否只查找启用的控件。visible:布尔值,是否只查找可见的控件。top_level_only:布尔值,是否只在顶级窗口中查找。found_index:整数,当找到多个匹配项时,可以选择特定索引的匹配项。control_id:整数,控件的ID(WindowsAPI中的控件ID)。handle:整数或ctypes.c_void_p,直接指定控件的句柄。regex_title:正则表达式对象或字符串,用于匹配的正则表达式。regex_class_name:同上,但用于匹配类名的正则表达式。best_match:字符串,用于best_match()逻辑,尝试匹配最接近的控件。示例假设你想在记事本应用的主窗口中找到名为“文件”(File)的菜单项,可以这样做:PythonfrompywinautoimportApplicationapp=Application(backend="uia").start("notepad.exe")main_window=app.window(title="无-记事本")#使用class_name和title定位子窗口menu_item=main_window.child_window(class_name="MenuItem",title="文件")注意事项参数组合:通常,为了精确匹配,你需要结合多个参数一起使用。比如,同时使用class_name和title可以更准确地定位目标控件。性能考量:如果条件设置得过于宽泛,可能会导致搜索缓慢或找到错误的控件。尽量使用具体的属性来缩小搜索范围。异常处理:如果找不到匹配的子窗口,child_window()会抛出异常。可以使用try-except结构来妥善处理这种情况。通过灵活运用child_window()方法及其参数,你可以高效地定位并操作UI自动化中的各种元素。3-child_window()  children() descendants()方法介绍如果要获取父窗口下的所有直接子窗口(而不只是单个子窗口),可以使用children()方法,它会返回一个包含所有直接子元素的列表。例如:#此定位元素的方式,需要window类型为:WindowSpecificationPythonall_direct_children=parent_window.children()而如果你需要递归地获取所有子孙窗口(子窗口以及子窗口的子窗口等),可以使用descendants()方法:#此定位元素的方式,需要window类型为:UiaWrapperPythonall_descendants=parent_window.descendants()总结来说,parent_window.child_window()是用于定位单个满足特定条件的子窗口,而不是获取所有子内容。若要获取所有子内容,请根据需要使用children()或descendants()。4-pywinauto打开程序时,程序启动的比较慢,或点击按钮弹窗比较慢,或者打开新窗口窗口渲染的比较慢,怎么添加合理的等待时间在使用pywinauto进行自动化操作时,合理地添加等待时间是非常重要的,尤其是在程序启动慢、按钮点击后弹窗延迟或新窗口渲染速度较慢的情况下。这能确保脚本在继续执行下一步操作前,等待相关的UI元素准备就绪。以下是几种添加等待时间的方法:1.显式等待(推荐)使用wait方法是最灵活且推荐的等待方式,它允许你指定等待的条件和超时时间。等待窗口可见:Pythonapp.Window(title="窗口").wait("visible",timeout=10)等待控件可用:Pythonapp.Dialog.child_window(title="按钮",control_type="Button").wait("enabled",timeout=20)2.静态等待(time.sleep)虽然不如显式等待灵活,但在某些简单场景下可以使用time.sleep来简单地暂停脚本执行。Pythonimporttimetime.sleep(5)#等待5秒3.动态等待对于某些不确定何时完成的操作,可以在循环中结合检查条件和短暂停顿来实现动态等待。Pythonimporttimetimeout=30#总超时时间interval=1#检查间隔start_time=time.time()whiletime.time()-start_time有什么区别在使用pywinauto时,你观察到的区别反映了两种不同状态的窗口对象处理方式。未使用等待(直接获取)Pythonwindow=app.window(title="Newmail")print(window)输出类似于 ,这表明你获得的是一个WindowSpecification对象。这个对象实际上是一个窗口的描述符或者说规范,它定义了你要找的窗口应该满足的条件(在这个例子中是为"Newmail"的窗口)。但是,此时pywinauto尚未实际找到并实例化这个窗口,这意味着打印出来的信息是一个内存地址,代表了这个待查找窗口描述的实例。使用等待后获取Pythonwindow=app.window(title="Newmail").wait('visible')print(window)或者Pythonwindow=app.window(title="Newmail").wait('exists',timeout=10)输出为uiawrapper.UIAWrapper-'Newmail',Dialog,这表示你成功获取到了一个实际的UI元素对象,它已经不是一个简单的查找规范,而是窗口实际存在的、可操作的对象。uiawrapper.UIAWrapper是pywinauto内部用于包装UIA(UIAutomation)元素的类,这意味着窗口不仅被找到了,而且已经准备好了进行进一步的交互操作,比如点击、输入文本等。区别总结未使用等待:你得到的是一个窗口的查找描述符,它定义了要找什么,但不代表窗口已被找到或实例化。使用等待:你得到的是一个实际的UI元素对象,它代表了已经找到的窗口,可以直接用于执行各种操作。在实际应用中,使用等待(如.wait('visible')或.wait('exists'))通常更为推荐,因为它确保了操作不会在窗口还没准备好时进行,提高了脚本的稳定性和可靠性。10-我的pywinauto程序,Application.start()后,还需要执行application.connect()后的app,才能正常使用。否则此app获得的windows不可用,为什么?在pywinauto中,理论上,一旦你使用Application.start()方法启动了一个应用程序,它应该自动连接到这个新启动的应用实例,并且你能够直接通过返回的app实例进行窗口和控件的查找及操作,无需再次调用app.connect()。如果遇到必须先执行app.connect()后才能正常使用app获得的窗口或控件,这可能由几个原因引起:启动时间问题:应用程序可能需要一段时间才能完全初始化其窗口和UI元素。即便start()方法在内部尝试连接,但如果UI还未完全加载,后续立即进行窗口或控件查找可能失败。此时,手动调用connect()给予额外的时间缓冲,可能恰好让UI准备就绪。后台启动或隐藏窗口:某些应用程序可能在启动时没有立即显示窗口,或者窗口最小化到托盘/后台运行,导致初次连接时无法正确识别窗口。稍后调用connect()可能窗口已变为可见或可交互状态。UI自动化框架的兼容性或bug:尽管少见,但不同版本的pywinauto或目标应用程序的UI框架(如WPF、UWP等)可能存在兼容性问题,导致自动连接行为不如预期。多实例问题:如果你的应用程序允许或意外启动了多个实例,start()可能连接到了不同的实例,而你期望操作的窗口位于另一个实例中。再次手动指定连接条件确保连接到正确的窗口实例。解决方法建议:增加启动后等待:在start()后,加入适当的等待时间(如time.sleep()),让应用程序有足够的时间初始化窗口和UI元素。精确连接:确保connect()时使用足够的参数(如窗口、类名等)来精确匹配你想要连接的窗口,避免连接到错误的实例或窗口。检查pywinauto版本:确保你使用的pywinauto版本是最新的,以获取最佳的兼容性和稳定性。日志和调试:使用pywinauto的日志功能或打印调试信息,检查启动和连接过程中可能出现的任何异常或警告,帮助定位问题所在。如果问题持续,考虑查看pywinauto的官方文档或社区,可能有针对特定应用或类似问题的解决方案。我的问题其实应该是兼容问题,我增加start()后的等待时间,无效。必须使用Application.connect()命令,重新获取app后,只有的操作才有效。app=Application(backend="uia").start(outlook_path,timeout=30)#等待也是必须的time.sleep(15)#必须使用connect()重新生成app=Application(backend="uia").connect(class_name="OlkHost")print("outlook第一次启动,打开outlook成功!,app=",app)11-Application(backend="uia").start()参数中有timeout参数吗?没有。因为在标准的pywinauto文档和通常的使用中,直接的start()方法调用并不直接接受一个timeout参数用于等待应用程序启动完成。通常,如果你想控制应用程序启动后的等待逻辑或处理超时,你需要采取一些额外的步骤,而不是直接通过start()方法的timeout参数(因为这个参数在标准用法中不存在)。以下是一些建议:手动等待:在start()调用之后,你可以使用time.sleep()或更复杂的逻辑来等待一段预估的合理时间,确保应用程序有足够的时间启动并加载完毕。动态等待窗口:利用pywinauto的等待机制,比如top_window()或wait()方法,来动态等待直到应用程序的某个窗口变得可用或满足特定条件。例如,如果你知道应用程序启动后主窗口的,可以这样做:Pythonapp=Application(backend="uia").start("your_app_path")main_window=app.window(title="YourMainWindowTitle").wait("visible",timeout=30)这里,timeout=30就是等待窗口变为可见的最大时间,单位为秒。异常处理:在尝试访问应用程序窗口或执行操作时,使用try-except结构来捕获可能的超时或失败异常,从而实现间接的超时控制。自定义逻辑:根据应用程序的具体行为,你可能需要更定制化的逻辑来确定何时应用程序已成功启动并准备好进行自动化操作,这可能涉及检查特定进程的状态、窗口属性变化等。综上所述,虽然直接在Application.start()方法中设置timeout参数不是标准用法,但通过上述方法你可以有效地控制和管理应用程序启动后的等待逻辑及超时处理。12-Application(backend="uia").connect()参数中有timeout参数吗?没有。但pywinauto提供了灵活的等待和超时控制机制来确保连接操作不会无限期地阻塞。自动化等待与超时控制虽然直接在connect()方法签名中没有公开timeout参数,但pywinauto的wait系列方法和自动化操作通常允许你指定一个超时限制,例如在尝试连接到一个窗口时:Pythonapp=Application(backend="uia")main_window=app.window(title="YourAppTitle").wait("exists",timeout=30)在这个例子中,wait("exists",timeout=30)是尝试等待窗口存在最多30秒。如果窗口在指定时间内变得可访问,则继续执行;如果超时,则会抛出异常,允许你的脚本据此作出反应。定制等待逻辑对于更复杂的场景,你可能需要自定义等待逻辑,比如检查窗口是否不仅存在还满足其他条件(如“visible”、“enabled”等),并结合自定义的循环和超时处理。总结尽管Application(backend="uia").connect()直接调用时不带有一个明确的timeout参数,pywinauto提供的wait方法和自动化操作的灵活性让你能够有效控制连接和等待窗口出现的时间限制,实现类似超时处理的逻辑。通过这种方式,确保了自动化脚本既能够稳健地等待必要的窗口准备就绪,又能避免不必要的阻塞。13-弹窗操作在pywinauto中,当需要连接并操作一个应用程序新弹出的窗口时,通常需要先确保新窗口已经出现,然后通过窗口的或其他属性来定位并操作它。以下是处理新弹窗的基本步骤:1.等待弹窗出现使用pywinauto的等待功能确保弹窗已经打开。这可以通过循环检查窗口是否存在,或者使用更高级的等待方法来实现。PythonfrompywinautoimportApplicationfrompywinauto.timingsimportwait_until_passesapp=Application().connect(title="主应用程序的")#首先连接到主应用程序defis_popup_opened():try:app.window(title="弹窗的")returnTrueexceptException:returnFalse#等待弹窗出现,最多等待10秒wait_until_passes(10,1,is_popup_opened)2.定位并操作弹窗一旦确认弹窗已经打开,就可以通过其或其它属性来定位并进行操作。Pythonpopup_window=app.window(title="弹窗的")#例如,点击弹窗上的一个按钮button_on_popup=popup_window.child_window(title="按钮的或自动化ID",control_type="Button")button_on_popup.click()3.异常处理和重试机制在实际操作中,可能需要添加适当的异常处理逻辑,以防窗口变化或打开速度过慢等情况。4.关闭弹窗如果操作完成后需要关闭弹窗,可以调用窗口的关闭方法:Pythonpopup_window.close()注意事项:确保窗口是唯一的,或者足够精确以避免误操作其他窗口。使用control_type、class_name等属性可以帮助更精确地定位控件。如果弹窗属于同一个应用程序,通常不需要再次调用Application().connect(),直接在已连接的应用实例上操作即可。根据弹窗出现的时机和应用的具体行为,可能需要微调等待逻辑。14-AttributeError:'UIAWrapper'objecthasnoattribute'print_control_identifiers'在pywinauto中,如果你遇到AttributeError:'UIAWrapper'objecthasnoattribute'print_control_identifiers'错误,这意味着你尝试在一个基于UIA(UIAutomation)的控件上使用了print_control_identifiers方法,但这个方法实际上并不直接存在于UIAWrapper对象中。print_control_identifiers方法主要用于经典的Windows应用程序自动化(win32应用程序),它在Win32Window对象中可用。然而,对于基于UIAutomation(UIA)的应用,如现代的UWP应用或使用了UIA框架构建的桌面应用,直接使用print_control_identifiers会报错。对于UIAWrapper对象,你可以使用.legacy_properties()方法来获取控件的部分信息,但这通常不如print_control_identifiers那样详尽。解决方案:检查应用类型:确认你的应用是否是现代UIA应用还是传统的win32应用。如果是UIA应用,继续阅读下文。使用UIA属性:对于UIAWrapper对象,你可以利用.properties属性来查看控件的属性。例如:Python1control=app.window(title="YourWindowTitle").child_window(auto_id="YourControlAutoId")2print(control.properties)这将打印出控件的属性字典,其中包含了控制类型、名称、位置等信息。自定义打印函数:如果需要类似print_control_identifiers的详细输出,你可能需要自己编写一个函数来遍历.properties或使用UIA的其他方法来获取更详细的控件信息。降级为win32:在某些情况下,如果应用同时支持UIA和win32,你可以尝试降级到win32模式来使用print_control_identifiers。但这取决于应用的具体实现和支持情况,且不是所有UIA应用都能这么做。记住,UIAutomation和经典Windows应用程序自动化在pywinauto中有一些差异,因此在处理不同类型的窗口和控件时,要相应地调整你的方法。15-AttributeError:'UIAWrapper'objecthasnoattribute'child_window'UIAWrapper窗口类型,定位元素需要使用如下方法定位:ele=setWindow.descendants(title="Remove")[0]当你在使用pywinauto遇到AttributeError:'UIAWrapper'objecthasnoattribute'child_window'错误时,这意味着你尝试在一个基于UIA(UIAutomation)的控件(UIAWrapper对象)上调用了child_window方法,但这个方法在UIAWrapper对象中并不可用。UIAWrapper对象主要对应于使用UIAutomation技术的应用程序,与传统的Win32应用程序使用的API有所不同。在UIAWrapper上,直接查找子窗口或控件的方法不是child_window,而是使用更符合UIA查询方式的属性和方法。解决方案:使用.descendants方法:对于UIAWrapper对象,可以使用.descendants方法来查找所有子孙控件,然后通过条件过滤找到目标控件。Pythoncontrols=window.descendants(control_type="ControlType")#替换"ControlType"为实际控件类型,如"Button","Edit",直接属性访问:如果已知某些属性(如 name, control_type等),可以直接通过属性筛选。Pythontarget_control=window.child_window(auto_id="目标控件的AutomationId")注意:上述child_window方法是针对经典的win32应用程序,对于UIA,你应该使用更符合其查询机制的方法,如.descendants或直接属性筛选。构造查询条件:使用更复杂的查询条件来精确定位控件,例如使用.filter()方法。Python1frompywinauto.controls.uia_controlsimportUIAWrapper2control=UIAWrapper(handle=你的窗口句柄).filter(control_type="ControlType")[0]#替换"ControlType"和条件确保理解你的应用是基于UIAutomation还是传统的Win32,然后选择相应的API和方法来操作和查找控件。对于UIA应用,利用.descendants和属性筛选通常是最直接有效的方式。16-pywinauto,已经找到窗口了,但使用window的方法print_control_identifiers()方法,会提示匹配到多个元素呢?Thereare5elementsthatmatchthecriteria{'backend':'uia','process':22188}当您已经成功定位到一个窗口,但在尝试使用window.print_control_identifiers()方法时遇到提示匹配到多个元素,这通常意味着您调用print_control_identifiers()的对象实际上是一个窗口集合或者是基于某些宽泛条件的窗口规范(WindowSpecification),而不是一个特定的窗口实例。为了解决这个问题,确保您在调用print_control_identifiers()之前,已经准确地指定了一个唯一的窗口。这里有几个步骤可以帮助您:重新检查定位窗口的代码:确认您是否确实已经通过足够精确的条件定位到单一窗口。比如,使用窗口的、类名、进程ID等属性来精确筛选。使用更具体的选择器:如果可能,增加筛选条件,比如使用正则表达式匹配窗口(title_re)或是直接使用窗口的自动化ID(auto_id)。确保调用链正确:如果通过一系列操作到达某个窗口,确认每一步都正确返回了预期的窗口对象。有时,错误的调用顺序可能导致返回的是窗口集合而非单个窗口。检查是否使用了模糊匹配:避免使用可能匹配到多个窗口的过于宽泛的条件,如只使用backend和process作为筛选条件。显式地获取窗口实例:在确定了窗口后,可以尝试显式地通过索引或其他方法获取具体的窗口实例。例如,如果您知道目标窗口是匹配条件下的第一个,可以尝试:Pythonspecific_window=windows[0]#假设'windows'是一个包含多个匹配窗口的列表specific_window.print_control_identifiers()使用top_level_only=False:如果目标窗口不是顶级窗口,尝试设置top_level_only=False来搜索所有级别的窗口,然后进一步筛选。调试与分析:如果以上方法都无法解决问题,可以先使用print_control_identifiers()的depth=-1参数来打印所有窗口的信息,帮助您识别哪些窗口与您的预期不符,并据此调整选择条件。确保每个步骤都尽可能具体和精确,以避免匹配到多个窗口的情况发生。17-pywinauto,使用Desktop(backend="uia")获取窗口注意点1:Desktop(backend="uia").windows()/window(),两个方法,只能查询所有的主窗口,不支持查询主窗口下都子窗口。注意点2:使用Desktop(backend="uia").windows()/window(),如果条件不能定位到窗口的情况下。Desktop(backend="uia").windows()方法,结果返回空列表。如下:wins=Desktop(backend="uia").windows(title="Workorschoolaccount",class_name="Windows.UI.Core.CoreWindow")print(wins)结果:[]Desktop(backend="uia").window()方法,结果返回WindowSpecification对象。如下:win=Desktop(backend="uia").window(title="Workorschoolaccount",class_name="Windows.UI.Core.CoreWindow")print(win)结果: ,不能使用返回的window()的返回值做判断。注意点3:如果窗口类型为:WindowSpecification,使用parent_window.child_window()方式查询窗口元素。如果窗口类型为:UIAWrapper,使用parent_window.parent_window.descendants()方式查询窗口元素。18-pywinauto,通过元素的element.rectangle()方法,使用pyautogui,移动鼠标到元素位置,实现。具体写法如下:#假设你想对窗口内的某个元素进行操作,首先找到该元素element=main_window.child_window(title="YourElementTitle",control_type="ControlTypeHere")#请替换为实际的和控件类型#获取元素的矩形信息element_rectangle=element.rectangle()#使用pyautogui将鼠标移动到该位置x,y=element_rectangle.left+(element_rectangle.width()//2),element_rectangle.top+(element_rectangle.height()//2)pyautogui.moveTo(x,y)#移动到元素中心位置,也可以直接移到左上角,去掉加法部分即可注意:element_rectangle.width和element_rectangle.height返回的类型都是method;计算位置坐标是应使用element_rectangle.width()和element_rectangle.height()形式。否则报错:TypeError:unsupportedoperandtype(s)for//:'method'and'int'(不支持这两两种类型使用//语法计算)19-编码过程中,注意循环引用问题E ImportError:cannotimportname'assertTrue'frompartiallyinitializedmodule'assertTool'(mostlikelyduetoacircularimport) --很可能是循环导入问题:“文件a”导入了“文件b”中的方法;“文件b”也导入了“文件a”中的方法,导致循环导入问题。20-pywinauot,定位uiaWrapper元素时,增加等待有时候不能解决问题。但是窗口最大化后,就能查找到元素了。可能的原因:窗口布局变化应用程序窗口在最大化之前和之后,其内部元素的布局可能会有细微的变化。这种布局变化可能会影响到元素的可访问性或者导致元素的属性(如坐标位置)发生变化。最大化窗口可能使得元素稳定地出现在预期的位置或状态,从而变得可被pywinauto识别。21-pywinauto+pytest执行的测试用例时,提示错误:Windowsfatalexception:code0x8001010d。但是如果把此用例的内容,都放到一个文件中执行,不通过pytest,就没有这个错误提示。应该怎么解决错误代码0x8001010d通常与COM(ComponentObjectModel)错误相关,特别是“RPC服务器不可用”或“接口调用中出现错误”。在使用pywinauto进行GUI自动化测试时遇到这个问题,可能的原因有几个方面,尤其是在结合pytest使用时,可能与测试环境的初始化、资源释放、并发执行测试等方面有关。以下是一些可能的解决方案:确保单例模式:确保在整个测试套件中,对应用程序实例的使用是单例的,避免多个测试用例同时操作同一个应用程序实例,这可能导致资源冲突。可以在conftest.py中使用fixture来管理应用实例的生命周期。资源清理:确保每个测试用例结束后,正确地关闭和清理所有打开的窗口和资源。使用try...finally块或者pytest.fixture的yield语句来确保即使测试失败也能执行清理操作。并发执行问题:如果你的测试套件配置为并行执行(pytest默认可能不会,但通过-nNUM参数可以设置),那么可能需要禁用并行执行,因为某些GUI操作或COM对象可能不支持多线程或并发访问。可以在运行pytest时加上-n1参数来禁用并行执行。COM对象生命周期管理:确保所有的COM对象在使用完毕后都被适当释放。在pywinauto中,通常这会自动处理,但如果涉及到自定义COM调用,请确保遵循正确的释放流程。增加异常处理:在测试代码中增加对特定异常的捕获和处理逻辑,尤其是与COM相关的异常,可以帮助诊断问题所在。可以尝试捕获并打印详细的错误信息,以便于进一步分析。检查环境差异:确认直接运行文件和通过pytest运行时的环境(如环境变量、权限等)是否一致。有时候,环境差异也会导致此类问题。如果以上建议不能解决问题,可能需要更详细的日志来定位问题。考虑在pytest运行时增加日志级别(通过-vvv参数或配置logging模块),并查看是否有其他相关的警告或错误信息输出,这有助于更准确地定位问题所在。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-10 05:50 , Processed in 0.431278 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表