【问题标题】:Color cycler example in matplotlib documentation is cryptic (python)matplotlib 文档中的颜色循环器示例很神秘(python)
【发布时间】:2020-09-06 02:00:00
【问题描述】:

有人能向我解释一下这个特定的例子是如何工作的吗,取自 matplotlib documentation 在 Persistent Cycles 部分下?

基本上他们创建了颜色循环器,我不确定它们是工厂函数还是迭代器,还是普通函数或对象。不知何故,您可以使用有限的iter(my_cycler) 和使用my_cycler() 的 itertools.cycle 生成器来制作生成器。这是如何工作的。

所以我的问题是:为什么你可以做iter(my_cycler),它给出了有限的东西,为什么你可以做my_cycler(),它给出了同样的东西,但它是无限的(它循环)。

>>> from cycler import cycler
>>> cyl = cycler(color=['r', 'g', 'b'])

>>> type(cyl)
cycler.Cycler

>>> import cycler
>>> cycler.Cycler.__mro__
(cycler.Cycler, object)

>>> type(cyl())
itertools.cycle

>> type(iter(cyl))
generator

当我输入时:

>>> next(cyl)
TypeError: 'Cycler' object is not an iterator

但不知何故,这在这里有效:

>>> for s in cyl:
...     print(s)
 
{'color': 'r'}
{'color': 'g'}
{'color': 'b'}

另一个问题是defaultdict的奇怪应用。 styles = defaultdict(lambda : next(finite_cy_iter))。我想我大致了解它,但我想确定:每次密钥不在字典中时,都会调用 (lambda : next(finite_cy_iter))() 或其他东西。根据关于 defaultdicts 的文档,你必须给它一个函数,在这种情况下是function = (lambda : next(finite_cy_iter))。所以我猜每次一个键不在字典中时 function 被调用,这实际上是一个伪装的迭代器,它为您提供循环器中的下一个字典,然后将其存储为该键的值。

这是 matplotlib 文档中的部分:

持续循环 通过字典查找将给定标签与样式相关联并动态生成该映射可能很有用。这可以使用 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))
or repeating

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])

这将导致同一组的每个数据都以相同的样式绘制。

【问题讨论】:

  • 休闲读者,请理解 OP 声明“基本上他们创建颜色循环仪”具有误导性፨ ፨ cycler 对象可以在每次迭代时提供不同 Artist 属性的字典(例如,对于 Line2D,您不仅可以指定颜色,还可以指定线宽、线型、标记形状等),以便在不幸的情况下询问您在同一张图上绘制数十条不同的曲线(除了你的老板,没有人会想要这样一团糟;-),每条曲线都具有使其独特的视觉特征。
  • 对不起,你是对的。我的意思是说一般来说线属性循环器。我认为最常见的用例,尤其是对我来说,是以特定的、更合乎逻辑的顺序制作颜色(例如,在一个图中显示一天一天的进展)。

标签: python matplotlib colors iterator


【解决方案1】:

关于问题的第一部分,

所以我的问题是:为什么你可以做iter(my_cycler),它给出了有限的东西,为什么你可以做my_cycler(),它给出了同样的东西,但它是无限的(它循环)。

cycler 类的行为是这样的,因为它是这样定义的,看看这个更简单的例子就明白我的意思了……

In [13]: class callable_list(list): 
    ...:     def __call__(self): 
    ...:         from itertools import cycle 
    ...:         return cycle(self)                                                       

In [14]: l = callable_list((1,2))                                                         

In [15]: l                                                                                
Out[15]: [1, 2]

In [16]: it_l = iter(l) ; cy_l = l()                                                      

In [17]: for _ in range(3): print(next(it_l))                                             
1
2
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-17-e4324d8268a6> in <module>
----> 1 for _ in range(3): print(next(it_l))

StopIteration: 

In [18]: for _ in range(3): print(next(cy_l))                                             
1
2
1

这种行为的“原因”是什么?

我们希望将任意数量的艺术家放置在一个轴中,并且我们希望能够使用不同的属性(不仅仅是颜色)来唯一地识别它们。

默认是调用 Cycler 对象,并使用返回的无限循环,允许某种程度的重复,但不会引发StopIteration 异常的风险。

在 OP 引用的示例中显示的替代方法是在 Cycler 对象上使用 iter,这样可以避免重复,但编码人员必须预测 StopIteration 异常的可能性。


附录寻址an OP's comment


当我对我的 Cycler 对象执行 cyl[1] 时。我明白了:ValueError: Can only use slices with Cycler.__getitem__ 这是什么意思?

这意味着cycler模块的编码者决定一个Cycler对象只能通过切片来寻址……

cyl[1:2] 有效,但奇怪的是返回两个元素而不是一个,cyl[1:1] 返回一个元素

这不是我看到的……定义两个 Cycler 及其外部产品

In [1]: from cycler import cycler 
   ...: c, w = cycler(c=['r','g','b']), cycler(lw=range(1,5)) 
   ...: cw = c*w 
   ...: print(c, w, cw, sep='\n')                                                         
cycler('c', ['r', 'g', 'b'])
cycler('lw', [1, 2, 3, 4])
(cycler('c', ['r', 'g', 'b']) * cycler('lw', [1, 2, 3, 4]))

从单个 Cycler 开始,首先是 [1:2],对我来说它返回单个值

In [2]: c[1:2]                                                                            
Out[2]: cycler('c', ['g'])

然后[1:1],又是一个单一的值,即一个空的循环器

In [3]: c[1:1] # empty cycler                                                             
Out[3]: cycler('c', [])

请注意,这类似于对列表进行切片,在任何情况下,表达式的值都是单个对象、一个列表,可能是一个空列表……没什么新意。

最后,让我们看看切片外部产品时会发生什么,但首先让我们看看里面是什么

In [5]: for i, d in enumerate(cw): print(i, d)                                            
0 {'c': 'r', 'lw': 1}
1 {'c': 'r', 'lw': 2}
2 {'c': 'r', 'lw': 3}
3 {'c': 'r', 'lw': 4}
4 {'c': 'g', 'lw': 1}
5 {'c': 'g', 'lw': 2}
6 {'c': 'g', 'lw': 3}
7 {'c': 'g', 'lw': 4}
8 {'c': 'b', 'lw': 1}
9 {'c': 'b', 'lw': 2}
10 {'c': 'b', 'lw': 3}
11 {'c': 'b', 'lw': 4}

现在我们通过切片来解决它

In [6]: cw[3:9]                                                                           
Out[6]: (cycler('c', ['r', 'g', 'g', 'g', 'g', 'b']) + cycler('lw', [4, 1, 2, 3, 4, 1]))

结果是单个 Cycler,两个 Cycler 的内积,当展开对应于上面列举的打印输出的位置 3÷8。

最后,让我们使用[3:3] 来寻址cw,即一个空切片(剧透,不会发生任何好事!)

In [7]: cw[3:3]                                                                           
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-7-1bf3cb52c570> in <module>
----> 1 cw[3:3]

~/lib/miniconda3/lib/python3.7/site-packages/cycler.py in __getitem__(self, key)
    219             trans = self.by_key()
    220             return reduce(add, (_cycler(k, v[key])
--> 221                                 for k, v in six.iteritems(trans)))
    222         else:
    223             raise ValueError("Can only use slices with Cycler.__getitem__")

~/lib/miniconda3/lib/python3.7/site-packages/cycler.py in __add__(self, other)
    241             raise ValueError("Can only add equal length cycles, "
    242                              "not {0} and {1}".format(len(self), len(other)))
--> 243         return Cycler(self, other, zip)
    244 
    245     def __mul__(self, other):

~/lib/miniconda3/lib/python3.7/site-packages/cycler.py in __init__(self, left, right, op)
    116         """
    117         if isinstance(left, Cycler):
--> 118             self._left = Cycler(left._left, left._right, left._op)
    119         elif left is not None:
    120             # Need to copy the dictionary or else that will be a residual

~/lib/miniconda3/lib/python3.7/site-packages/cycler.py in __init__(self, left, right, op)
    133             self._right = None
    134 
--> 135         self._keys = _process_keys(self._left, self._right)
    136         self._op = op
    137 

~/lib/miniconda3/lib/python3.7/site-packages/cycler.py in _process_keys(left, right)
     66         The keys in the composition of the two cyclers
     67     """
---> 68     l_peek = next(iter(left)) if left is not None else {}
     69     r_peek = next(iter(right)) if right is not None else {}
     70     l_key = set(l_peek.keys())

StopIteration: 

In [8]:                                                                                   

以我的拙见,最后一个功能非常像一个错误......

【讨论】:

  • 哇,非常好的解释和非常好的代码!所以本质上循环器对象是“可调用列表”?太疯狂了。很难理解这一点。但是您显示的代码清楚地表明可以构建类似的东西。当您调用它们时,它们会不断重复,但是当您将它们塞入 iter 时,它们会被“表达”并像普通列表一样起作用?
  • 当我用我的循环对象做 cyl[1] 时。我得到: ValueError: Can only use slices with Cycler.__getitem__ 这是什么意思? cyl[1:2] 有效,但奇怪的是返回了两个元素而不是一个。 cyl[1:1] 返回一个元素。
  • @Jim 不知道为什么,但我错过了你的 cmets……我很抱歉。我会尽快解决他们(不要屏住呼吸;-)
猜你喜欢
  • 2014-10-23
  • 2014-05-05
  • 2013-09-16
  • 1970-01-01
  • 1970-01-01
  • 2015-08-12
  • 1970-01-01
  • 1970-01-01
  • 2014-08-03
相关资源
最近更新 更多