文章
· 七月 18, 2022 阅读大约需 6 分钟

第九章 使用嵌入式 Python (六)

第九章 使用嵌入式 Python (六)

异常处理

IRIS 异常处理程序可以处理 Python 异常并将它们无缝传递给 ObjectScript。在前面的 Python 库示例的基础上,如果尝试使用不存在的文件调用 canvas.drawImage(),并在 ObjectScript 中捕获异常,会看到以下内容:

USER>try { do canvas.drawImage("C:\Sample\bad.png", 150, 600) } catch { write "Error: ", $zerror, ! }
Error: <THROW> *%Exception.PythonException <THROW> 230 ^^0^DO canvas.drawImage("W:\Sample\isc.png", 150, 600) 
<class 'OSError'>: Cannot open resource "W:\Sample\isc.png" -

这里 <class 'OSError'>: Cannot open resource "W:\Sample\isc.png"是从 Python 传回的异常。

字节和字符串

Python 对“字节”数据类型的对象和字符串(表示字符串的 UTF-8 字节序列)进行了明确区分,它们是简单的 8 位字节序列。在 Python 中,字节对象永远不会以任何方式进行转换,但字符串可能会根据主机操作系统使用的字符集进行转换,例如 Latin-1

IRIS 不区分字节和字符串。虽然 IRIS 支持 Unicode 字符串 (UCS-2/UTF-16),但任何包含小于 256 的值的字符串都可以是字符串或字节。出于这个原因,在将字符串和字节传入和传出 Python 时,以下规则适用:

  • IRIS 字符串假定为字符串,并在从 ObjectScript 传递到 Python 时转换为 UTF-8
  • Python 字符串在传回 ObjectScript 时会从 UTF-8 转换为 IRIS 字符串,这可能会产生宽字符。
  • Python 字节对象作为 8 位字符串返回给 ObjectScript。如果字节对象的长度超过最大字符串长度,则返回 Python 字节对象。
  • 要将字节对象从 ObjectScript 传递到 Python,请使用 ##class(%SYS.Python).Bytes()方法,该方法不会将基础 IRIS 字符串转换为 UTF-8

以下示例将 IRIS 字符串转换为字节类型的 Python 对象:

USER>set b = ##class(%SYS.Python).Bytes("Hello Bytes!")

USER>zwrite b
b=8@%SYS.Python  ; b'Hello Bytes!'  ; <OREF>

USER>zwrite builtins.type(b)
4@%SYS.Python  ; <class 'bytes'>  ; <OREF>

要在 IRIS 中构造大于 3.8MB 最大字符串长度的 Python 字节对象,可以使用 bytearray 对象并使用 extend() 方法附加更小的字节块。最后,将 bytearray 对象传递给内置的 bytes() 方法以获取字节表示:

USER>set ba = builtins.bytearray()

USER>do ba.extend(##class(%SYS.Python).Bytes("chunk 1"))

USER>do ba.extend(##class(%SYS.Python).Bytes("chunk 2"))

USER>zwrite builtins.bytes(ba)
"chunk 1chunk 2"

标准输出和标准错误映射

当使用嵌入式 Python 时,标准输出被映射到 IRIS 控制台,这意味着任何 print() 语句的输出都被发送到终端。标准错误映射到位于目录 <install-dir>/mgr 中的 IRIS messages.log 文件。

例如,考虑这个 Python 方法:

def divide(a, b):
    try:
        print(a/b)
    except ZeroDivisionError:
        print("Cannot divide by zero")
    except TypeError:
        import sys
        print("Bad argument type", file=sys.stderr)
    except:
        print("Something else went wrong")

如果在终端中测试此方法,可能会看到以下内容:

USER>set obj = ##class(%SYS.Python).Import("mymodule")

USER>do obj.divide(5, 0)
Cannot divide by zero

USER>do obj.divide(5, "hello")

如果除以零,则错误消息将定向到终端,但如果尝试除以字符串,则消息将发送到 messages.log

11/19/21-15:49:33:248 (28804) 0 [Python] Bad argument type

只有重要的消息应该发送到messages.log,以避免文件混乱。

在互操作性产品中使用嵌入式 Python

如果正在为 IRIS 中的互操作性产品编写自定义业务主机类或适配器类,则任何回调方法都必须用 ObjectScript 编写。回调方法是一种继承方法,默认情况下什么都不做,但设计为由用户实现。但是,回调方法中的 ObjectScript 代码可以使用 Python 库或调用 Python 中实现的其他方法。

以下示例显示了一个业务操作,该操作从传入消息中获取字符串值,并使用 Amazon Web Services (AWS) boto3 Python 库通过 Amazon Simple Notification Service (SNS) 以文本消息的形式将该字符串发送到手机。此 AWS 库的范围超出了本次讨论的范围,但可以在示例中看到 OnInit()OnMessage() 回调方法是用 ObjectScript 编写的,而方法 PyInit()SendSMS() 是用 ObjectScript 编写的Python

/// Send SMS via AWS SNS
Class dc.opcua.SMS Extends Ens.BusinessOperation
{

Parameter INVOCATION = "Queue";

/// AWS boto3 client
Property client As %SYS.Python;

/// json.dumps reference
Property tojson As %SYS.Python;

/// Phone number to send SMS to
Property phone As %String [ Required ];

Parameter SETTINGS = "phone:SMS";

Method OnMessage(request As Ens.StringContainer, Output response As Ens.StringContainer) As %Status
{
   #dim sc As %Status = $$$OK
   try {
      set response = ##class(Ens.StringContainer).%New(..SendSMS(request.StringValue))
      set code = +{}.%FromJSON(response.StringValue).ResponseMetadata.HTTPStatusCode
      set:(code'=200) sc = $$$ERROR($$$GeneralError, $$$FormatText("Error sending SMS,
         code: %1 (expected 200), text: %2", code, response.StringValue))
   } catch ex {
      set sc  = ex.AsStatus()
   }

   return sc
}

Method SendSMS(msg As %String) [ Language = python ]
{
   response = self.client.publish(PhoneNumber=self.phone, Message=msg)
   return self.tojson(response)
}

Method OnInit() As %Status
{
   #dim sc As %Status = $$$OK
   try {
      do ..PyInit()
   } catch ex {
      set sc = ex.AsStatus()
   }
   quit sc
}

/// Connect to AWS
Method PyInit() [ Language = python ]
{
   import boto3
   from json import dumps
   self.client = boto3.client("sns")
   self.tojson = dumps
}

}

注意:上面的 OnMessage() 方法中的代码包含一个额外的换行符,以便在打印此文档时更好地格式化。

此规则的一个例外是,如果它不使用来自适配器的输入,可以在 Python 中实现回调方法。

以下业务服务示例称为轮询器。在这种情况下,可以将业务服务设置为间隔运行并生成一个请求(在这种情况下包含一个随机字符串值),该请求将发送到业务流程进行处理。在此示例中,可以在 Python 中实现 OnProcessInput() 回调方法,因为它不使用方法签名中的 pInput 参数。

Class Debug.Service.Poller Extends Ens.BusinessService
{

Property Target As Ens.DataType.ConfigName;

Parameter SETTINGS = "Target:Basic";

Parameter ADAPTER = "Ens.InboundAdapter";

Method OnProcessInput(pInput As %RegisteredObject, Output pOutput As %RegisteredObject, 
    ByRef pHint As %String) As %Status [ Language = python ]
{
    import iris
    import random
    fruits = ["apple", "banana", "cherry"]
    fruit = random.choice(fruits)
    request = iris.cls('Ens.StringRequest')._New()
    request.StringValue = fruit + ' ' + iris.cls('Debug.Service.Poller').GetSomeText()
    return self.SendRequestAsync(self.Target,request)
}

ClassMethod GetSomeText() As %String
{
    Quit "is something to eat"
}

}
讨论 (0)1
登录或注册以继续