可组合循环¶
- 版本:
0.12
- 日期:
2024 年 1 月 18 日
文档 |
|
PyPI |
|
GitHub |
cycler
API¶
|
从单个位置参数、一对位置参数或关键字参数的组合创建一个新的 |
|
可组合循环。 |
|
连接 |
cycler
的公共 API 包含一个类 Cycler
、一个工厂函数 cycler()
和一个连接函数 concat()
。工厂函数提供了一个简单的接口来创建“基本” Cycler
对象,而类则负责处理组合和迭代逻辑。
Cycler
用法¶
基础¶
单个条目 Cycler
对象可用于轻松循环遍历单个样式。要创建 Cycler
,请使用 cycler()
函数将键/样式/关键字参数链接到一系列值。键必须是可散列的(因为它最终将用作 dict
中的键)。
In [1]: from __future__ import print_function
In [2]: from cycler import cycler
In [3]: color_cycle = cycler(color=['r', 'g', 'b'])
In [4]: color_cycle
Out[4]: cycler('color', ['r', 'g', 'b'])
Cycler
知道它的长度和键
In [5]: len(color_cycle)
Out[5]: 3
In [6]: color_cycle.keys
Out[6]: {'color'}
遍历此对象将产生一系列以标签为键的 dict
对象
In [7]: for v in color_cycle:
...: print(v)
...:
{'color': 'r'}
{'color': 'g'}
{'color': 'b'}
Cycler
对象可以作为参数传递给 cycler()
,它返回一个带有新标签但具有相同值的新的 Cycler
。
In [8]: cycler(ec=color_cycle)
Out[8]: cycler('ec', ['r', 'g', 'b'])
遍历 Cycler
会导致有限的条目列表,要获得无限循环,请调用 Cycler
对象(类似于生成器)
In [9]: cc = color_cycle()
In [10]: for j, c in zip(range(5), cc):
....: print(j, c)
....:
0 {'color': 'r'}
1 {'color': 'g'}
2 {'color': 'b'}
3 {'color': 'r'}
4 {'color': 'g'}
组合¶
单个 Cycler
可以很容易地用单个 for
循环替换。 Cycler
对象的强大之处在于它们可以组合起来轻松创建复杂的多个键循环。
加法¶
具有不同键的等长 Cycler
可以相加以获得两个循环的“内积”。
In [11]: lw_cycle = cycler(lw=range(1, 4))
In [12]: wc = lw_cycle + color_cycle
结果具有相同的长度,并且具有两个输入 Cycler
的并集作为键。
In [13]: len(wc)
Out[13]: 3
In [14]: wc.keys
Out[14]: {'color', 'lw'}
遍历结果相当于遍历两个输入循环的 zip。
In [15]: for s in wc:
....: print(s)
....:
{'lw': 1, 'color': 'r'}
{'lw': 2, 'color': 'g'}
{'lw': 3, 'color': 'b'}
与算术运算一样,加法是可交换的。
In [16]: lw_c = lw_cycle + color_cycle
In [17]: c_lw = color_cycle + lw_cycle
In [18]: for j, (a, b) in enumerate(zip(lw_c, c_lw)):
....: print('({j}) A: {A!r} B: {B!r}'.format(j=j, A=a, B=b))
....:
(0) A: {'lw': 1, 'color': 'r'} B: {'color': 'r', 'lw': 1}
(1) A: {'lw': 2, 'color': 'g'} B: {'color': 'g', 'lw': 2}
(2) A: {'lw': 3, 'color': 'b'} B: {'color': 'b', 'lw': 3}
为了方便起见,cycler()
函数可以具有多个键值对,并且会自动通过加法将它们组合成单个 Cycler
。
In [19]: wc = cycler(c=['r', 'g', 'b'], lw=range(3))
In [20]: for s in wc:
....: print(s)
....:
{'c': 'r', 'lw': 0}
{'c': 'g', 'lw': 1}
{'c': 'b', 'lw': 2}
乘法¶
任何一对 Cycler
都可以相乘。
In [21]: m_cycle = cycler(marker=['s', 'o'])
In [22]: m_c = m_cycle * color_cycle
这将得到两个循环的“外积”(与 itertools.product()
相同)。
In [23]: len(m_c)
Out[23]: 6
In [24]: m_c.keys
Out[24]: {'color', 'marker'}
In [25]: for s in m_c:
....: print(s)
....:
{'marker': 's', 'color': 'r'}
{'marker': 's', 'color': 'g'}
{'marker': 's', 'color': 'b'}
{'marker': 'o', 'color': 'r'}
{'marker': 'o', 'color': 'g'}
{'marker': 'o', 'color': 'b'}
请注意,与加法不同,乘法不可交换(类似于矩阵)。
In [26]: c_m = color_cycle * m_cycle
In [27]: for j, (a, b) in enumerate(zip(c_m, m_c)):
....: print('({j}) A: {A!r} B: {B!r}'.format(j=j, A=a, B=b))
....:
(0) A: {'color': 'r', 'marker': 's'} B: {'marker': 's', 'color': 'r'}
(1) A: {'color': 'r', 'marker': 'o'} B: {'marker': 's', 'color': 'g'}
(2) A: {'color': 'g', 'marker': 's'} B: {'marker': 's', 'color': 'b'}
(3) A: {'color': 'g', 'marker': 'o'} B: {'marker': 'o', 'color': 'r'}
(4) A: {'color': 'b', 'marker': 's'} B: {'marker': 'o', 'color': 'g'}
(5) A: {'color': 'b', 'marker': 'o'} B: {'marker': 'o', 'color': 'b'}
整数乘法¶
Cycler
也可以乘以整数值以增加长度。
In [28]: color_cycle * 2
Out[28]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])
In [29]: 2 * color_cycle
Out[29]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])
连接¶
Cycler
对象可以通过 Cycler.concat()
方法
In [30]: color_cycle.concat(color_cycle)
Out[30]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])
或顶层 concat()
函数进行连接
In [31]: from cycler import concat
In [32]: concat(color_cycle, color_cycle)
Out[32]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])
切片¶
循环可以使用 slice
对象进行切片
In [33]: color_cycle[::-1]
Out[33]: cycler('color', ['b', 'g', 'r'])
In [34]: color_cycle[:2]
Out[34]: cycler('color', ['r', 'g'])
In [35]: color_cycle[1:]
Out[35]: cycler('color', ['g', 'b'])
以返回循环的子集作为新的 Cycler
.
检查 Cycler
¶
要检查转置的 Cycler
的值,请使用 Cycler.by_key
方法
In [36]: c_m.by_key()
Out[36]:
{'marker': ['s', 'o', 's', 'o', 's', 'o'],
'color': ['r', 'r', 'g', 'g', 'b', 'b']}
此 dict
可以被修改并用于创建一个新的 Cycler
,其中包含更新后的值
In [37]: bk = c_m.by_key()
In [38]: bk['color'] = ['green'] * len(c_m)
In [39]: cycler(**bk)
Out[39]: (cycler('marker', ['s', 'o', 's', 'o', 's', 'o']) + cycler('color', ['green', 'green', 'green', 'green', 'green', 'green']))
示例¶
我们可以使用 Cycler
实例来循环遍历一个或多个 kwarg
到 plot
from cycler import cycler
from itertools import cycle
fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True,
figsize=(8, 4))
x = np.arange(10)
color_cycle = cycler(c=['r', 'g', 'b'])
for i, sty in enumerate(color_cycle):
ax1.plot(x, x*(i+1), **sty)
for i, sty in zip(range(1, 5), cycle(color_cycle)):
ax2.plot(x, x*i, **sty)
(Source code
, png
, hires.png
, pdf
)
from cycler import cycler
from itertools import cycle
fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True,
figsize=(8, 4))
x = np.arange(10)
color_cycle = cycler(c=['r', 'g', 'b'])
ls_cycle = cycler('ls', ['-', '--'])
lw_cycle = cycler('lw', range(1, 4))
sty_cycle = ls_cycle * (color_cycle + lw_cycle)
for i, sty in enumerate(sty_cycle):
ax1.plot(x, x*(i+1), **sty)
sty_cycle = (color_cycle + lw_cycle) * ls_cycle
for i, sty in enumerate(sty_cycle):
ax2.plot(x, x*(i+1), **sty)
持久循环¶
将给定标签与样式关联起来,通过字典查找并动态生成映射,这很有用。可以使用 defaultdict
很容易实现。
In [40]: from cycler import cycler as cy
In [41]: from collections import defaultdict
In [42]: cyl = cy('c', 'rgb') + cy('lw', range(1, 4))
要获得有限的样式集
In [43]: finite_cy_iter = iter(cyl)
In [44]: dd_finite = defaultdict(lambda : next(finite_cy_iter))
或重复
In [45]: loop_cy_iter = cyl()
In [46]: dd_loop = defaultdict(lambda : next(loop_cy_iter))
这在绘制包含分类和标签的复杂数据时很有用
finite_cy_iter = iter(cyl)
styles = defaultdict(lambda : next(finite_cy_iter))
for group, label, data in DataSet:
ax.plot(data, label=label, **styles[group])
这将导致每个具有相同 group
的 data
以相同的样式绘制。
异常¶
如果添加了长度不相等的 Cycler
,则会引发 ValueError
In [47]: cycler(c=['r', 'g', 'b']) + cycler(ls=['-', '--'])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[47], line 1
----> 1 cycler(c=['r', 'g', 'b']) + cycler(ls=['-', '--'])
File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:283, in Cycler.__add__(self, other)
275 """
276 Pair-wise combine two equal length cyclers (zip).
277
(...)
280 other : Cycler
281 """
282 if len(self) != len(other):
--> 283 raise ValueError(
284 f"Can only add equal length cycles, not {len(self)} and {len(other)}"
285 )
286 return Cycler(
287 cast(Cycler[Union[K, L], Union[V, U]], self),
288 cast(Cycler[Union[K, L], Union[V, U]], other),
289 zip
290 )
ValueError: Can only add equal length cycles, not 3 and 2
或者如果组合了两个具有重叠键的循环
In [48]: color_cycle = cycler(c=['r', 'g', 'b'])
In [49]: color_cycle + color_cycle
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[49], line 1
----> 1 color_cycle + color_cycle
File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:286, in Cycler.__add__(self, other)
282 if len(self) != len(other):
283 raise ValueError(
284 f"Can only add equal length cycles, not {len(self)} and {len(other)}"
285 )
--> 286 return Cycler(
287 cast(Cycler[Union[K, L], Union[V, U]], self),
288 cast(Cycler[Union[K, L], Union[V, U]], other),
289 zip
290 )
File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:179, in Cycler.__init__(self, left, right, op)
176 else:
177 self._right = None
--> 179 self._keys: set[K] = _process_keys(self._left, self._right)
180 self._op: Any = op
File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:84, in _process_keys(left, right)
82 r_key: set[K] = set(r_peek.keys())
83 if l_key & r_key:
---> 84 raise ValueError("Can not compose overlapping cycles")
85 return l_key | r_key
ValueError: Can not compose overlapping cycles
动机¶
在绘制多条线时,通常希望能够循环遍历一个或多个艺术家样式。对于简单的情况,无需太多麻烦即可完成
fig, ax = plt.subplots(tight_layout=True)
x = np.linspace(0, 2*np.pi, 1024)
for i, (lw, c) in enumerate(zip(range(4), ['r', 'g', 'b', 'k'])):
ax.plot(x, np.sin(x - i * np.pi / 4),
label=r'$\phi = {{{0}}} \pi / 4$'.format(i),
lw=lw + 1,
c=c)
ax.set_xlim([0, 2*np.pi])
ax.set_title(r'$y=\sin(\theta + \phi)$')
ax.set_ylabel(r'[arb]')
ax.set_xlabel(r'$\theta$ [rad]')
ax.legend(loc=0)
但是,如果你想做一些更复杂的事情
fig, ax = plt.subplots(tight_layout=True)
x = np.linspace(0, 2*np.pi, 1024)
for i, (lw, c) in enumerate(zip(range(4), ['r', 'g', 'b', 'k'])):
if i % 2:
ls = '-'
else:
ls = '--'
ax.plot(x, np.sin(x - i * np.pi / 4),
label=r'$\phi = {{{0}}} \pi / 4$'.format(i),
lw=lw + 1,
c=c,
ls=ls)
ax.set_xlim([0, 2*np.pi])
ax.set_title(r'$y=\sin(\theta + \phi)$')
ax.set_ylabel(r'[arb]')
ax.set_xlabel(r'$\theta$ [rad]')
ax.legend(loc=0)
绘图逻辑很快就会变得非常复杂。为了解决这个问题并允许轻松地循环遍历任意的 kwargs
,开发了 Cycler
类,这是一个可组合的关键字参数迭代器。