我正在尝试为Matplotlib中的Axes实例构建一个简单的状态跟踪功能。 每次我创建一个新的axes对象(直接或通过其他功能,例如subplots())时,我都希望实例具有一个绑定方法a.next_color(),当我创建要添加的新行时,可以使用该方法在颜色之间循环 到轴。 我写了这样的东西:
1
2
3
4
5
6def set_color_sequence(colors = ['r', 'g', 'b', 'c', 'm', 'y']):
i = [0]
def cf(self):
i[0] += 1
return colors[(i[0]-1) % len(colors)]
return cf
并且以为我很聪明,可以将其添加到父类中:
1plt.Axes.next_color = set_color_sequence()
问题在于状态变量i似乎由所有Axes实例共享,而不是每个新实例都具有自己的状态。 确保所有新实例具有自己的状态跟踪功能的一种优雅方法是什么? (顺便说一句,我想这样做而不修改原始的matplotlib代码。)
你可以继承它吗? class MyAxes(plt.Axes): def next_color(self): ...
也许这会有所帮助? 另外,为什么不创建一个单独的对象/函数而不是附加一个需要手动调用的方法(假设您不进一步修改类)呢?
matplotlib已经有这样的东西。给定ax1一个AxesSubplot实例(或任何派生Axes的东西),您可以使用_get_lines访问线属性。这包括prop_cycler,可以使用以下命令覆盖它:
1ax2._get_lines.prop_cycler = ax1._get_lines.prop_cycler
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# Generate some data
x = linspace(-10, 10)
y_lin = 2*x
y_quad = x**2
# Create empty axes
_, ax1 = subplots()
# Plot linear
l1 = ax1.plot(y_lin)
ax1.set_ylabel('linear', color=l1[0].get_color())
ax1.tick_params('y', colors=l1[0].get_color())
# Generate secondary y-axis
ax2 = ax1.twinx()
# Make sure that plots on ax2 continue color cycle
ax2._get_lines.prop_cycler = ax1._get_lines.prop_cycler
# Plot quadratic
l2 = ax2.plot(y_quad)
ax2.set_ylabel('quadratic', color=l2[0].get_color())
ax2.tick_params('y', colors=l2[0].get_color())
哪个输出:
省略ax2._get_lines.prop_cycler = ax1._get_lines.prop_cycler可以使我们:
注意:如果您使用%pylab inline魔术在jupyter(或nteract)中工作(因此您将无法直接访问matplotlib.pyplot模块)。所以像plt.Axes这样的东西有点麻烦。
AMacK的评论使我想到了一个简单的解决方案:
1
2
3def next_color(self):
return next(self._get_lines.color_cycle)
plt.Axes.next_color = next_color
这没有我上面概述的自定义颜色序列行为,但是确实获得了我在简洁简洁的代码中正在寻找的每个实例的行为。 (如果我想生成自定义序列,大概可以直接覆盖color_cycle迭代器。)
好的,很公平,但是为什么不直接调用self._get_lines.color_cycle.next()? 另外,虽然可以覆盖color_cycle,但请记住,您必须要么基于每个实例执行此操作(否则必须子类plt.Axes),以使每个实例具有自己的状态。
如果您将next_color属性分配给
Axes的实例,而不是类本身。
首先,用
set_color_sequence,您基本上是在实现
回旋处的发电机。至
简化事情,我们可以使用
itertools.cycle:
1
2
3from itertools import cycle
...
axes_instance.next_color = cycle(['r', 'g', 'b', 'c', 'm', 'y']).next
实际上,这就是matplotlib跟踪其颜色位置的方式
周期。例如,如果您查看一个实例
matplotlib.axes._subplots.AxesSubplot,您将看到它具有属性
_get_lines.color_cycle,它是一个itertools.cycle(尝试调用
color_cycle.next())。
现在看下面两个例子:
1
2
3
4
5
6
7
8
9
10
11
12class MyClass1(object):
# next_color is an attribute of the *class itself*
next_color = cycle(['r', 'g', 'b', 'c', 'm', 'y']).next
class MyClass2(object):
def __init__(self):
# next_color is an attribute of *this instance* of the class
self.next_color = cycle(['r', 'g', 'b', 'c', 'm', 'y']).next
在第一种情况下,发生的情况是分配
1next_color = cycle(['r', 'g', 'b', 'c', 'm', 'y]).next
首次导入该类时,仅一次评估一次。这表示
每当您创建MyClass1的新实例时,其next_color
属性将指向完全相同的itertools.cycle实例,并且
因此MyClass1的所有实例将共享一个公共状态:
1
2
3
4
5
6
7
8a = MyClass1()
b = MyClass1()
print a.next_color is b.next_color
# True
print a.next_color(), a.next_color(), b.next_color()
# r g b
但是,每当一个新的__init__方法被一次又一次地调用
该类的实例正在创建。结果,每个MyClass2实例都会
它自己的itertools.cycle,因此它是自己的状态:
1
2
3
4
5
6
7
8a = MyClass2()
b = MyClass2()
print a.next_color is b.next_color
# False
print a.next_color(), a.next_color(), b.next_color()
# r g r
如果您的目标是子类plt.Axes,则需要放置您的作业
子类的每个新实例都会调用它的地方
(可能在__init__中)。但是,如果您只想添加此内容
现有实例的方法,那么您实际上需要做的就是:
1axes_instance.next_color = get_color_sequence()
感谢您在此处阐明问题。 接受此答案是因为它提供了两种出色的策略-每个实例的子类化和分配。 我在回答问题时采用了该策略,因为(a)它的简洁性和(b)可以应用在快速设置脚本中,而无需再考虑了。