【问题标题】:How to set python exception messages to use a special language?如何设置 python 异常消息以使用特殊语言?
【发布时间】:2015-12-09 09:30:45
【问题描述】:

我想更改 python 处理返回的错误字符串的语言的方式,例如 WinError/OSError 异常。我正在使用 ctypes 并且 WinError 被定义为

def WinError(code=None, descr=None):
    if code is None:
        code = GetLastError()
    if descr is None:
        descr = FormatError(code).strip()
    return OSError(None, descr, None, code)

FormatError函数是从..\Python34\DLLs_ctypes.pyd中提取出来的,是C++ FormatMessage函数的python版本。

DWORD WINAPI FormatMessage(
  _In_     DWORD   dwFlags,
  _In_opt_ LPCVOID lpSource,
  _In_     DWORD   dwMessageId,
  _In_     DWORD   dwLanguageId,
  _Out_    LPTSTR  lpBuffer,
  _In_     DWORD   nSize,
  _In_opt_ va_list *Arguments
);

理想情况下,python 等效项应该具有相同的参数,但 FormatError 只能有一个参数,即 FormatError([code])。 我找到了用 c++ 编写的 ctypes 的源代码。有一个名为 callproc.c 的文件,其中定义了 FormatError 函数

static TCHAR *FormatError(DWORD code)
{
    TCHAR *lpMsgBuf;
    DWORD n;
    n = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
              NULL,
              code,
              MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
              (LPTSTR) &lpMsgBuf,
              0,
              NULL);
    if (n) {
        while (isspace(lpMsgBuf[n-1]))
            --n;
        lpMsgBuf[n] = '\0'; /* rstrip() */
    }
    return lpMsgBuf;
}

LANG_NEUTRAL|SUBLANG_DEFAULT = 回退到用户的默认语言。

有没有办法控制错误字符串的语言,可能是通过设置语言环境、环境变量或其他方式?

提前致谢!

编辑:我想我发现了一些有趣的东西,但我稍后会测试它,因为我真的很困。这应该有效吗? https://gist.github.com/EBNull/6135237

【问题讨论】:

标签: python c++ exception ctypes


【解决方案1】:

在 Windows Vista 和更高版本上,您可以调用 SetThreadPreferredUILanguages 为当前线程设置最多 5 种语言的列表,按偏好排序。 Windows 7+ 也有SetProcessPreferredUILanguages 为整个过程更改此设置。

Windows 语言包提供所需的多语言用户界面 (MUI) 资源库。对于系统库,您将在 System32 的按语言命名的子目录中找到 MUI 资源。例如,如果安装了西班牙语语言包,则应该存在 System32\es-ES\kernel32.dll.mui。有关 MUI 的更多信息,请阅读 Understanding MUIMUI Fundamental Concepts Explained

这是调用SetThreadPreferredUILanguages的示例ctypes定义:

import ctypes
from ctypes import wintypes

kernel32 = ctypes.WinDLL('kernel32')    

MUI_LANGUAGE_ID    = 0x004
MUI_LANGUAGE_NAME  = 0x008
MUI_RESET_FILTERS  = 0x001
MUI_CONSOLE_FILTER = 0x100
MUI_COMPLEX_SCRIPT_FILTER = 0x200

def errcheck_bool(result, func, args):
    if not result:
        raise ctypes.WinError(ctypes.get_last_error())
    return args

kernel32.SetThreadPreferredUILanguages = ctypes.WINFUNCTYPE(
    wintypes.BOOL, 
    wintypes.DWORD,
    wintypes.LPCWSTR,
    wintypes.PULONG,
    use_last_error=True
)(('SetThreadPreferredUILanguages', kernel32),
  ((1, 'flags', MUI_LANGUAGE_NAME),
   (1, 'languages', None),
   (2, 'pulNumLanguages')))

kernel32.SetThreadPreferredUILanguages.errcheck = errcheck_bool

我将pulNumLanguages 参数定义为输出参数(即类型2),因此ctypes 负责将引用传递给临时c_ulong,然后将值作为高级Python 结果返回。

C 调用的低级 BOOL 结果由 errcheck_bool 处理,这可能会引发基于 Windows LastError 代码的异常。为确保该值的准确性,本机调用配置了use_last_error=True,它在线程本地存储变量中捕获LastError 值。通过调用ctypes.get_last_error 检索此捕获的错误值。

语言列表作为单个宽字符串传递,每个元素都以nul 字符结尾(即'\0')。建议使用 language names 代替旧语言 ID,因此我将 MUI_LANGUAGE_NAME 设置为默认标志值。

例子:

>>> ERROR_FILE_NOT_FOUND = 2
>>> kernel32.SetThreadPreferredUILanguages(languages='es-es\0en-us\0')
2
>>> ctypes.FormatError(ERROR_FILE_NOT_FOUND)
'El sistema no puede encontrar el archivo especificado.'

重置:

>>> kernel32.SetThreadPreferredUILanguages(flags=0)
0
>>> ctypes.FormatError(ERROR_FILE_NOT_FOUND)
'The system cannot find the file specified.'    

不幸的是,这并没有设置 C 运行时错误消息使用的语言。通常,只要 Python 依赖于 CRT 的低级 POSIX API,就会在 Windows 上使用 CRT 错误消息,例如_open/_wopen

>>> kernel32.SetThreadPreferredUILanguages(languages='es-es\0en-us\0')
2
>>> open('spam')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'spam'

上述ENOENT 消息被硬编码在CRT 的_sys_errlist 数组中。例如,这里是 Python 3.5 中该数组的前 10 个条目(使用新的通用 CRT):

ucrtbase = ctypes.CDLL('ucrtbase')
ucrtbase.__sys_nerr.restype = ctypes.POINTER(ctypes.c_int)
nerr = ucrtbase.__sys_nerr()[0]
ucrtbase.__sys_errlist.restype = ctypes.POINTER(ctypes.c_char_p * nerr)
errlist = ucrtbase.__sys_errlist()[0]

>>> print(*(m.decode() for m in errlist[:10]), sep='\n')
No error
Operation not permitted
No such file or directory
No such process
Interrupted function call
Input/output error
No such device or address
Arg list too long
Exec format error
Bad file descriptor

(实际上不要为此使用 ctypes。上面只是为了展示实现。在 Python 中使用 os.strerror 来获取这些 CRT 错误消息。)

在这种情况下,您必须通过使用自定义或机器翻译的错误消息重新引发异常来处理异常。

【讨论】:

    猜你喜欢
    • 2017-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多