4. 编写脚本

4.1. 基本脚本编写

可以直接从GDA的脚本编辑器面板输入和运行脚本。您可以处理多个脚本,当然也可以打开和保存它们。

要从命令行运行脚本,请键入:

>>> run "ScriptName"

4.1.1. 位置

/dls/iXX/scripts

用户为他们的实验编写的脚本。

/dls/iXX/software/gda/config/scripts

由钻石员工编写的特定于光束线的脚本(只读)。

/dls/iXX/software/gda/scripts

核心GDA脚本可用于所有光束线(只读)。

(其中XX为波束线号)

4.1.2. 命名空间

GDA GUI的JythonTerminal面板中的Jython命令行使用与任何脚本相同的名称空间,因此在一个脚本中创建的变量可以从另一个脚本访问。

4.2. 编码标准

Python语言有一套业界认可的编码和文档标准。在本节中,我们将介绍对GDA脚本编写者特别有益的标准。

我们将讨论如何布局代码以及如何记录脚本(包括类和模块的记录)。本节最后给出了命名变量、对象、函数、类和模块的推荐实践。

4.2.1. 缩进

我们建议每个缩进级别始终使用4个空格。这将使剪切和粘贴代码变得容易得多,并且这种缩进很容易阅读。切勿将制表符和空格混为一谈。

4.2.2. 长长的队伍

尝试将所有行限制为最多72个字符。您可以使用Python在圆括号内隐含的行续行符或使用反斜杠来换行。例如::

# wrapping lines with backslash
if width == 0 and height == 0 and \
colour == 'red' and emphasis == 'strong':
    f()

# wrapping lines with parentheses
if (width == 0 and height == 0 and
colour == 'red' and emphasis == 'strong'):
    f()

4.2.3. 文档标准和帮助

您应该在代码中尽可能多地编写注释,以便向代码的任何其他用户(或者您自己,如果您在将来某个时候查看代码)解释代码的作用和原因。也不要做过头。

您应该为所有公共模块、函数、类和方法编写文档字符串。在非公共方法上不需要DocString。

Jython有一个内置的文档编制机制。这是 __doc__ 属性。您可以在每个类或方法定义的第一行用三个引号(‘’)来写注释。这些报价的内容随后可通过 __doc__ 属性。在GDA中,您可以通过以下帮助命令快速访问这些注释::

def myMethod():
    '''This is my comment explaining what this method does.

    The Jython convention is to have a single line which is a
    concise summary of the object or method starting with a
     capital letter and ending with a period. It should have
    not mention the functions name. If you need more detail,
    leave a blank line and then one or more paragraphs separated
    by blank lines.
    '''

通过两种方式访问注释:

>>> print myMethod.__doc__
>>> help myMethod

通过这种方式可以获得许多内置GDA对象和类的帮助。

4.2.4. 命名约定

讨论了Jython中所有常见条目的推荐命名约定

4.2.4.1. 包和模块名称

模块名称应简短,全部为小写,并使用下划线作为分隔符:例如:

my_module

4.2.4.2. 类名

类名应该以大写字母开头,后面的单词应该大写。这称为驼箱,例如::

MyClass

4.2.4.3. 例外名称

异常应该是类,因此使用带有后缀“Exception”的类命名约定:例如::

MyClassException

4.2.4.4. 全局变量名

全局变量只能在一个模块内部使用。以小写字母开头,单词大写:例如::

myGlobalVariable

4.2.4.5. 函数名称

函数名称应以小写字母开头,后面的单词应大写:例如::

myFunction

始终使用“self”作为类的实例化方法的第一个参数。始终使用“cls”作为类(静电)方法的第一个参数。

4.3. 导入模块

Jython import语句需要讨论,因为有几种方法可以导入模块。如果我们使用最简单的导入形式:

>>> import myModule

执行模块中的所有代码,然后将模块中定义的所有符号添加到命名空间myModule。若要访问命名空间中的任何符号,必须用命名空间限定符号。例如,如果模块定义了一个名为MyClass的类,则要实例化此类的对象,必须编写::

>>> import myModule
>>> myObject = myModule.MyClass()

但是,如果您使用替代形式的导入:

>>> from myModule import *

执行模块中的所有代码,然后将模块中定义的所有符号添加到全局名称空间,而不是名称空间myModule。这意味着我们现在可以使用非限定名称来访问模块中定义的符号。因此,上面的示例现在可以编写为::

>>> from myModule import *
>>> myObject = MyClass()

4.4. 中断脚本

尽管GDA提供了暂停和停止脚本的按钮,但您会发现脚本可能不会立即暂停或停止。事实上,这些行动可能需要相当长的时间才能见效。这是底层Java系统工作方式的结果。如果您了解GDA用于暂停和停止脚本的机制,您将能够编写使脚本响应更快的附加代码。

当您在GDA工作台中按下暂停或停止按钮时,GDA系统会向您的Jython脚本发送一个信号。收到信号后,Java系统将执行两个可能的操作之一。如果您的脚本忙于执行代码,那么Java会设置一个中断标志来指示您应该暂停或停止。但是,在您完成当前函数调用或循环语句之前,Jython系统不会尝试检查此标志,因此您的脚本将变得没有响应。但是如果您的脚本已经暂停(处于休眠状态),那么Java将立即中断您的代码并停止脚本。

因为您永远不能确定何时需要暂停或停止脚本,所以可以调用函数::

>>> pause()

或其别名仅为::

>>> pause

要求GDA随时检查中断标志。如果您在函数或循环内进行此调用,您将确保快速响应。例如,修改循环以定期调用Pause::

>>> for i in range(1,10000):
>>>      if (i % 100 = 0) pause() # pause every 100 iterations
>>>          # your_code ...

4.5. 矩阵

GDA与JAMA矩阵包一起部署以执行矩阵计算。JAMA旨在成为Java的标准矩阵包。它是由制作Matlab的MathWorks合著的。由于任何Java类都可以在Jython中使用,而无需任何特殊编程,因此可以直接调用Jama类。但是,已经编写了一个包装类来使Jama对象能够更轻松地与Jython环境交互。

要使用此库,请键入以下命令导入GDA Jama包装器::

>>> from Jama import Matrix as M

然后,可以通过键入命令轻松创建矩阵,例如::

>>> a = M([[1,2,3], [4,5,6]])

然后可以很容易地在Jython中操作这些矩阵。它们可以以矩阵的方式与Jython列表、元组和整数交互。要举例说明,请执行以下操作:

>>> print a * 3
     [[3, 6, 9], [12, 15, 18]]

和:

>>> b = M([1 2 3])
>>> print b + [4, 5, 6]
    [5,7,9].

4.6. 高级脚本编写

本节对于编写用户在光束线上重复使用的脚本的光束线工作人员更感兴趣。

4.6.1. 与用户交互

您可能会编写一个脚本,该脚本在每次运行时都需要用户输入,例如请求一个变量。要在GDA中做到这一点,您可以使用Jython的内置 raw_input 功能。

要使用此命令,请执行以下操作:

>>> raw_input('Prompt for input: ')

当调用此函数时,脚本将暂停,JythonTerminal GUI面板中的提示将更改为要求用户在那里输入一些内容。在用户按下Return之后,requestInput命令将输入的内容作为字符串返回。例如::

>>> target = raw_input("Where would you like to move s1_upper to?")
>>> pos s1_upper float(target)

注解

使用时 raw_input 从RCP客户端,可以在任何连接的客户端上输入输入,而不仅仅是启动脚本或运行命令的客户端。

使用时 raw_input 从远程连接输入将仅限于该连接。

4.6.2. 脚本中的错误处理

如果您有一个将重复用于任务的脚本,而不是执行实验的简单一次性脚本,则您可能希望在脚本中添加错误处理,以便在运行脚本时如果出现任何错误(如硬件故障),则会显示相应的消息,并采取措施彻底停止设备或解决问题。

Jython语言具有类似于许多其他语言的错误处理机制。受保护的代码包含在try/Except块中,这些块“包装”附带的代码和拾取异常(如果异常发生在其中),并运行代码来解决或报告问题。

需要注意的一点是,所有Jython错误都继承了Jython类 org.python.core.exceptions 。这不是从Java异常基类继承的 java.lang.Exception 。在为脚本编写错误处理时,记住这一点很重要。当脚本因用户干预而停止或停止时,将引发Java异常;但是,脚本中可能出现的许多其他错误将是Jython错误。脚本错误处理应该能够捕获这两种形式的异常。

例如::

>>> try:
...     # your logic goes here
...     # have lots of calls to gda.jython.commands.ScannableControl.pause()
...     # to allow users to pause/halt the script cleanly
... except InterruptedException, e:
...     print "a user-requested halt!"
...     # code here to stop the equipment and return the beamline to a safe state
... except java.lang.Exception, e:
...     print "a Java exception must have occurred!"
...     # code here to stop the equipment and return the beamline to a safe state
... except:
...     print "a Jython error must have occurred!"
...     # code here to stop the equipment and return the beamline to a safe state
...

或者,Jython有一个Finally子句,无论是否抛出异常,该子句总是在尝试挡路之后操作。在某些情况下,这可能更合适。::

>>> try:
...     # your logic goes here
...     # have lots of calls to gda.jython.commands.ScannableControl.pause()
...     #to allow users to pause/halt the script cleanly
... except InterruptedException, e:
...     print "a user-requested halt!"
... except java.lang.Exception, e:
...     print "a Java exception must have occurred!"
... except:
...     print "a Jython error must have occurred!"
... finally:
...     # code here to stop the equipment and return the beamline to a safe state
...     # this is always called whether an exception was raised or not
...

4.6.3. 持之以恒

持久性是指您希望存储变量,以便在软件重新启动后可以保存和检索这些变量。GDA为此提供了一种易于使用的机制。值存储在/DLS/iXX/SOFTWARE/GDA/var目录下的XML文件中。这些XML文件可以从Jython环境中创建、读取和保存。

要使用此系统,您必须创建一个XMLConfiguration对象(表示XML文件)。然后,您可以使用名称来标识每条信息,轻松地读取值并将值存储到此文件中。您必须确保调用save方法,以确保在每次更改后都保存XML文件。以下是有关如何使用此命令的示例代码::

>>> from uk.ac.diamond.daq.persistence.jythonshelf import LocalParameters
>>> config = LocalParameters.getXMLConfiguration("my_parameters_file")
>>> config.setProperty("mythings.myint", 42)
>>> config.setProperty("mythings.mystring", "blarghh")
>>> aJavaListOfStrings = ['one', 'two', 'three']
>>> config.setProperty("otherthings.mylist", aJavaListOfStrings)
>>>
>>> config.save() # Make sure to save them!
>>>
>>> Integer myint = config.getInt("mythings.myint")
>>> String mystring = config.getString("mythings.myint")
>>> String[] stringArray = config.getStringArray("otherthings.mylist")
>>> List stringList = config.getList("otherthings.mylist")

4.6.4. 登录

在运行长脚本时,在运行过程中记录正在发生的事情通常很有用。Jython有一个内置的日志库,可以用来将消息发送到中央GDA日志和控制台。这可能比简单地打印到终端更可取,因为源可以被跟踪,并且可以在不编辑脚本的情况下将其禁用。

要在脚本导入中使用日志记录,请执行以下操作 logging 并创建一个记录器。::

>>> import logging
>>> logger = logging.getLogger('script_name')
>>>
>>> # To use the logger
>>> logger.info('This message will be printed to the console and the logs')
script_name: This message will be printed to the console and the logs

如果您的脚本使用 error handling ,上面的记录器可以提取堆栈跟踪来帮助调试原因。这是使用 exc_info 要包括当前异常的关键字::

>>> try:
...     # with stage_x being a faulty motor
...     stage_x.getPosition()
... except DeviceException as de:
...     logger.error('Failed to get motor position', exc_info=True)
...
script_name: Failed to get motor position - Error getting position
  gda.device.MotorException: <message of underlying motor error>

完整堆栈跟踪将写入日志。仅向用户显示潜在原因。

4.6.5. 正在写入外部文件

写入文本文件是在脚本运行期间记录GDA和光束线状态的一种有用方式。Jython内置了打开、写入和关闭文件的方法,使您不必手动处理可能发生的任何错误。

要打开要写入的文件,请执行以下操作:

>>> with open('/path/to/file/to/open', 'a') as output:
...     output.write('text to write to file\n') # \n adds a new line

这将替换任何 try:...except:...finally:... 写入文件时需要的块。该文件可以随时使用,直到 with 挡路,即使挡路中的某个地方发生错误,它也将关闭。

注解

使用此选项会将文本添加到文件末尾。要覆盖文件的当前内容,请在open命令中传递‘w’而不是‘a’。