如何捕获Python subprocess.check_output()中的异常输出?
我想在Python中进行比特币支付。在命令行中,我通常会这样做:
bitcoin sendtoaddress <bitcoin address> <amount>
比如说:
bitcoin sendtoaddress 1HoCUcbK9RbVnuaGQwiyaJGGAG6xrTPC9y 1.4214
如果支付成功,我会得到一个交易ID作为输出,但如果我尝试转账的金额超过了我的比特币余额,我会得到以下输出:
error: {"code":-4,"message":"Insufficient funds"}
现在在我的Python程序中,我尝试这样进行支付:
import subprocess
try:
output = subprocess.check_output(['bitcoin', 'sendtoaddress', address, str(amount)])
except:
print "Unexpected error:", sys.exc_info()
如果余额足够,支付就能顺利进行,但如果余额不足,sys.exc_info()
会输出这个:
(<class 'subprocess.CalledProcessError'>, CalledProcessError(), <traceback object at 0x7f339599ac68>)
不过它没有包含我在命令行上看到的错误信息。所以我想问的是;我怎么才能在Python中获取到这个错误信息({"code":-4,"message":"Insufficient funds"}
)呢?
10 个回答
从Python 3.5开始,subprocess.run()
这个函数增加了一个叫做check
的参数:
如果check设置为真,而程序的退出状态码不是零,那么就会抛出一个叫
CalledProcessError
的异常。这个异常里面会包含一些信息,比如传入的参数、退出状态码,以及如果有的话,标准输出和错误输出。
下面是一个简单的例子,这个例子会抛出并打印出CalledProcessError
:
import subprocess
try:
subprocess.run("exit 1", shell=True, check=True, timeout=15, capture_output=True)
except subprocess.CalledProcessError as e:
print(e) # Output: Command 'exit 1' returned non-zero exit status 1.
尝试“转账一个超过我比特币余额的金额”并不是一个意外的错误。你可以直接使用 Popen.communicate()
,而不是 check_output()
,这样就可以避免不必要地抛出异常:
from subprocess import Popen, PIPE
p = Popen(['bitcoin', 'sendtoaddress', ..], stdout=PIPE)
output = p.communicate()[0]
if p.returncode != 0:
print("bitcoin failed %d %s" % (p.returncode, output))
正如@Sebastian提到的,默认的解决方案应该使用 run()
方法:https://docs.python.org/3/library/subprocess.html#subprocess.run
这里有一个方便的实现(你可以把日志类换成打印语句,或者用你正在使用的其他日志功能):
import subprocess
def _run_command(command):
log.debug("Command: {}".format(command))
result = subprocess.run(command, shell=True, capture_output=True)
if result.stderr:
raise subprocess.CalledProcessError(
returncode = result.returncode,
cmd = result.args,
stderr = result.stderr
)
if result.stdout:
log.debug("Command Result: {}".format(result.stdout.decode('utf-8')))
return result
还有一个示例用法(这段代码虽然不相关,但我觉得它可以作为一个例子,展示这个简单实现的错误处理是多么易读和好用):
try:
# Unlock PIN Card
_run_command(
"sudo qmicli --device=/dev/cdc-wdm0 -p --uim-verify-pin=PIN1,{}"
.format(pin)
)
except subprocess.CalledProcessError as error:
if "couldn't verify PIN" in error.stderr.decode("utf-8"):
log.error(
"SIM card could not be unlocked. "
"Either the PIN is wrong or the card is not properly connected. "
"Resetting module..."
)
_reset_4g_hat()
return
我觉得被接受的解决方案没有处理错误信息在标准错误输出(stderr)上的情况。根据我的测试,异常的输出属性没有包含来自标准错误输出的结果,而且文档也提醒不要在check_output()中使用stderr=PIPE。相反,我建议对J.F Sebastian的解决方案做一个小改进,增加对标准错误输出的支持。毕竟,我们是在处理错误,而错误通常是在标准错误输出中报告的。
from subprocess import Popen, PIPE
p = Popen(['bitcoin', 'sendtoaddress', ..], stdout=PIPE, stderr=PIPE)
output, error = p.communicate()
if p.returncode != 0:
print("bitcoin failed %d %s %s" % (p.returncode, output, error))
根据 subprocess.check_output()
的文档,当出现错误时,会抛出一个异常,这个异常有一个 output
属性,你可以用它来获取错误的详细信息:
try:
subprocess.check_output(...)
except subprocess.CalledProcessError as e:
print(e.output)
然后你可以分析这个字符串,并使用 json
模块来解析错误的具体信息:
if e.output.startswith('error: {'):
error = json.loads(e.output[7:]) # Skip "error: "
print(error['code'])
print(error['message'])