IceCream — 再也不用print()来调试了
你是否曾经使用print()
或log()
来调试代码?当然你会。IceCream,简称ic
,让print调试变得更加甜蜜。
ic()
类似于print()
,但更胜一筹:
- 它同时打印表达式/变量名及其值。
- 输入速度提高60%。
- 数据结构以美观的方式打印。
- 输出带有语法高亮。
- 可选择包含程序上下文:文件名、行号和父函数。
IceCream经过充分测试,采用宽松许可证,支持Python 2、Python 3、PyPy2和PyPy3。
检查变量
你是否曾经打印变量或表达式来调试程序?如果你曾经输入过类似的内容
print(foo('123'))
或更详细的
print("foo('123')", foo('123'))
那么ic()
会让你眉开眼笑。带参数时,ic()
会检查自身并打印出它的参数及这些参数的值。
from icecream import ic
def foo(i):
return i + 333
ic(foo(123))
打印结果
ic| foo(123): 456
类似地,
d = {'key': {1: 'one'}}
ic(d['key'][1])
class klass():
attr = 'yep'
ic(klass.attr)
打印结果
ic| d['key'][1]: 'one'
ic| klass.attr: 'yep'
只需给ic()
一个变量或表达式,就大功告成了。简单易用。
检查执行
你是否曾经使用print()
来确定程序的哪些部分被执行,以及它们的执行顺序?例如,如果你曾经添加print语句来调试如下代码
def foo():
print(0)
first()
if expression:
print(1)
second()
else:
print(2)
third()
那么ic()
在这里也能帮上忙。不带参数时,ic()
会检查自身并打印调用它的文件名、行号和父函数。
from icecream import ic
def foo():
ic()
first()
if expression:
ic()
second()
else:
ic()
third()
打印结果
ic| example.py:4 in foo()
ic| example.py:11 in foo()
只需调用ic()
就大功告成了。简单明了。
返回值
ic()
返回其参数,所以ic()
可以轻松插入到已有的代码中。
>>> a = 6
>>> def half(i):
>>> return i / 2
>>> b = half(ic(a))
ic| a: 6
>>> ic(b)
ic| b: 3
其他功能
ic.format(*args)
类似于ic()
,但输出会作为字符串返回,而不是写入stderr。
>>> from icecream import ic
>>> s = 'sup'
>>> out = ic.format(s)
>>> print(out)
ic| s: 'sup'
此外,ic()
的输出可以通过ic.disable()
完全禁用,之后可以通过ic.enable()
重新启用。
from icecream import ic
ic(1)
ic.disable()
ic(2)
ic.enable()
ic(3)
打印结果
ic| 1: 1
ic| 3: 3
当然,禁用时ic()
仍会返回其参数;现有的带有ic()
的代码不会出错。
导入技巧
要使ic()
在每个文件中都可用,而无需在每个文件中导入,你可以install()
它。例如,在根文件A.py
中:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from icecream import install
install()
from B import foo
foo()
然后在被A.py
导入的B.py
中,直接调用ic()
:
# -*- coding: utf-8 -*-
def foo():
x = 3
ic(x)
install()
将ic()
添加到builtins模块中,该模块在解释器导入的所有文件之间共享。
同样,ic()
之后也可以被uninstall()
。
如果IceCream未安装(比如在生产环境中,而不是开发环境),ic()
也可以以一种优雅失败的方式导入。为此,这个后备导入片段可能会很有用:
try:
from icecream import ic
except ImportError: # 如果IceCream未安装,优雅地降级
ic = lambda *a: None if not a else (a[0] if len(a) == 1 else a) # noqa
配置
ic.configureOutput(prefix, outputFunction, argToStringFunction, includeContext, contextAbsPath)
控制ic()
的输出。
如果提供了prefix
,它会采用自定义的输出前缀。prefix
可以是一个字符串,比如
>>> from icecream import ic
>>> ic.configureOutput(prefix='hello -> ')
>>> ic('world')
hello -> 'world'
或者是一个函数。
>>> import time
>>> from icecream import ic
>>>
>>> def unixTimestamp():
>>> return '%i |> ' % int(time.time())
>>>
>>> ic.configureOutput(prefix=unixTimestamp)
>>> ic('world')
1519185860 |> 'world': 'world'
prefix
的默认值是ic|
。
如果提供了outputFunction
,它会在每次调用ic()
时被调用一次,参数是ic()
的输出(以字符串形式),而不是将该字符串写入stderr(默认行为)。
>>> import logging
>>> from icecream import ic
>>>
>>> def warn(s):
>>> logging.warning(s)
>>>
>>> ic.configureOutput(outputFunction=warn)
>>> ic('eep')
WARNING:root:ic| 'eep': 'eep'
如果提供了argToStringFunction
,它会被用来将参数值序列化为可显示的字符串。默认使用PrettyPrint的pprint.pformat(),但可以更改为以自定义方式处理非标准数据类型。
>>> from icecream import ic
>>>
>>> def toString(obj):
>>> if isinstance(obj, str):
>>> return '[!字符串 %r 长度为 %i!]' % (obj, len(obj))
>>> return repr(obj)
>>>
>>> ic.configureOutput(argToStringFunction=toString)
>>> ic(7, 'hello')
ic| 7: 7, 'hello': [!字符串 'hello' 长度为 5!]
默认的argToStringFunction
是icecream.argumentToString
,它有用于register
和unregister
特定类的函数的方法,使用functools.singledispatch
进行分派。它还有一个registry
属性用于查看已注册的函数。
>>> from icecream import ic, argumentToString
>>> import numpy as np
>>>
>>> # 注册一个函数来概括numpy数组
>>> @argumentToString.register(np.ndarray)
>>> def _(obj):
>>> return f"ndarray, 形状={obj.shape}, 数据类型={obj.dtype}"
>>>
>>> x = np.zeros((1, 2))
>>> ic(x)
ic| x: ndarray, 形状=(1, 2), 数据类型=float64
>>>
>>> # 查看已注册的函数
>>> argumentToString.registry
mappingproxy({object: <function icecream.icecream.argumentToString(obj)>,
numpy.ndarray: <function __main__._(obj)>})
>>>
>>> # 取消注册一个函数并回退到默认行为
>>> argumentToString.unregister(np.ndarray)
>>> ic(x)
ic| x: array([[0., 0.]])
如果提供includeContext
并设置为True,会将ic()
调用的文件名、行号和父函数添加到ic()
的输出中。
>>> from icecream import ic
>>> ic.configureOutput(includeContext=True)
>>>
>>> def foo():
>>> i = 3
>>> ic(i)
>>> foo()
ic| example.py:12 in foo()- i: 3
includeContext
默认为False。
如果提供contextAbsPath
并设置为True,当ic()
以includeContext == True
调用时,会输出绝对文件路径(如/path/to/foo.py
),而不是仅文件名(如foo.py
)。这在调试多个具有相同文件名的文件时很有用。此外,一些编辑器(如VSCode)会将绝对文件路径转换为可点击的链接,打开ic()
被调用的文件。
>>> from icecream import ic
>>> ic.configureOutput(includeContext=True, contextAbsPath=True)
>>>
>>> i = 3
>>>
>>> def foo():
>>> ic(i)
>>> foo()
ic| /absolute/path/to/example.py:12 in foo()- i: 3
>>>
>>> ic.configureOutput(includeContext=True, contextAbsPath=False)
>>>
>>> def foo():
>>> ic(i)
>>> foo()
ic| example.py:18 in foo()- i: 3
contextAbsPath
默认为False。
安装
使用pip安装IceCream非常简单。
$ pip install icecream
相关Python库
ic()
使用@alexmojaki的executing
来可靠地定位Python源代码中的ic()
调用。这非常神奇。
其他语言中的IceCream
美味的IceCream应该在每种语言中都能享用。
- Dart: icecream
- Rust: icecream-rs
- Node.js: node-icecream
- C++: IceCream-Cpp
- C99: icecream-c
- PHP: icecream-php
- Go: icecream-go
- Ruby: Ricecream
- Java: icecream-java
- R: icecream
- Lua: icecream-lua
- Clojure(Script): icecream-cljc
- Bash: IceCream-Bash
如果你希望在你喜欢的语言中拥有类似的ic()
函数,请提交一个拉取请求!IceCream的目标是在每种语言中都提供一个方便的ic()
函数来优化打印调试。