如何修复将Python子进程迁移到unicode_literals的编码?

时间:2014-12-31 14:55:18

标签: python subprocess python-unicode

我们准备转向Python 3.4并添加unicode_literals。我们的代码使用子进程模块广泛依赖于与外部实用程序的管道连接。以下代码片段在Python 2.7上正常工作,可以将UTF-8字符串传递给子进程:

kw = {}
kw[u'stdin'] = subprocess.PIPE
kw[u'stdout'] = subprocess.PIPE
kw[u'stderr'] = subprocess.PIPE
kw[u'executable'] = u'/path/to/binary/utility'
args = [u'', u'-l', u'nl']

line = u'¡Basta Ya!'

popen = subprocess.Popen(args,**kw)
popen.stdin.write('%s\n' % line.encode(u'utf-8'))
...blah blah...

以下更改会抛出此错误:

from __future__ import unicode_literals

kw = {}
kw[u'stdin'] = subprocess.PIPE
kw[u'stdout'] = subprocess.PIPE
kw[u'stderr'] = subprocess.PIPE
kw[u'executable'] = u'/path/to/binary/utility'
args = [u'', u'-l', u'nl']

line = u'¡Basta Ya!'

popen = subprocess.Popen(args,**kw)
popen.stdin.write('%s\n' % line.encode(u'utf-8'))
Traceback (most recent call last):
  File "test.py", line 138, in <module>
    exitcode = main()
  File "test.py", line 57, in main
    popen.stdin.write('%s\n' % line.encode('utf-8'))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 0: ordinal not in range(128)

通过管道传递UTF-8的任何建议吗?

2 个答案:

答案 0 :(得分:6)

使用'%s\n'时,

unicode_literals是一个unicode字符串:

>>> line = u'¡Basta Ya!'
>>> '%s\n' % line.encode(u'utf-8')
'\xc2\xa1Basta Ya!\n'
>>> u'%s\n' % line.encode(u'utf-8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 0: ordinal not in range(128)

您的编码line值正在解码以插入到unicode '%s\n'字符串中。

您必须使用字节字符串;字符串前缀为b

>>> from __future__ import unicode_literals
>>> line = u'¡Basta Ya!'
>>> b'%s\n' % line.encode(u'utf-8')
'\xc2\xa1Basta Ya!\n'
在插值后

或编码

>>> line = u'¡Basta Ya!'
>>> ('%s\n' % line).encode(u'utf-8')
'\xc2\xa1Basta Ya!\n'

在Python 3中,无论如何都必须将字节串写入管道。

答案 1 :(得分:5)

如果utf-8代表你的语言环境编码然后使用Unicode字符串进行通信,你可以在Python 3上使用universal_newlines=True

#!/usr/bin/env python3
from subprocess import Popen, PIPE

p = Popen(['/path/to/binary/utility', '-l', 'nl'],
          stdin=PIPE, stdout=PIPE, stderr=PIPE,
          universal_newlines=True)
out, err = p.communicate('¡Basta Ya!')

即使区域设置的编码不是utf-8,代码也能正常工作。输入/输出是Unicode字符串(str类型)。

如果子进程需要utf-8无论当前语言环境是什么,那么使用字节串进行通信(传递/读取字节):

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
from subprocess import Popen, PIPE

p = Popen(['/path/to/binary/utility', '-l', 'nl'],
          stdin=PIPE, stdout=PIPE, stderr=PIPE)
out, err = map(lambda b: b.decode('utf-8').replace(os.linesep, '\n'),
               p.communicate((u'¡Basta Ya!' + os.linesep).encode('utf-8')))

代码在Python 2和3上的工作方式相同。