可組合的循環¶
- 版本:
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
實例來循環遍歷 plot
的一個或多個 kwarg
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)

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
類別。