-
-
Save bazzaar/d1436636613f1f1d7e0157df204f98ec to your computer and use it in GitHub Desktop.
use Inline::Python; | |
my $py = Inline::Python.new(); | |
$py.run('import matplotlib.pyplot'); | |
class Matplotlib::Plot { | |
method cm { | |
class { | |
# this getattr method, called like so : 'say $plt.cm.getattr($cmap, 'N');' | |
method getattr($obj, $name) { | |
$py.call('__builtin__', 'getattr', $obj, $name); | |
} | |
method FALLBACK($name, $idx) { | |
$py.run("matplotlib.pyplot.cm.{$name}($idx)", :eval); | |
} | |
}.new(); | |
} | |
method FALLBACK($name, |c) { | |
$py.call('matplotlib.pyplot', $name, |c) | |
} | |
} | |
class Matplotlib { | |
method FALLBACK($name, |c) { | |
$py.call('matplotlib', $name, |c) | |
} | |
} |
use v6; | |
use lib '.'; | |
use Matplotlib_reduced; | |
my $plt = Matplotlib::Plot.new; | |
my $cmap = $plt.get_cmap('seismic', 5); | |
# -- this works | |
#say 'Number of Colors : ' ~ $plt.cm.getattr($cmap, 'N'); | |
#say 'Number of Colors : ' ~ $plt.cm.getattr($cmap, 'N'); | |
# -- this fails on the 3rd call | |
#say 'Number of Colors : ' ~ $plt.cm.getattr($cmap, 'N'); | |
#say 'Number of Colors : ' ~ $plt.cm.getattr($cmap, 'N'); | |
#say 'Number of Colors : ' ~ $plt.cm.getattr($cmap, 'N'); | |
# -- this works | |
#say 'Colormap Name : ' ~ $plt.cm.getattr($cmap, 'name'); | |
#say 'Colormap Name : ' ~ $plt.cm.getattr($cmap, 'name'); | |
# -- this fails on the 3rd call | |
#say 'Colormap Name : ' ~ $plt.cm.getattr($cmap, 'name'); | |
#say 'Colormap Name : ' ~ $plt.cm.getattr($cmap, 'name'); | |
#say 'Colormap Name : ' ~ $plt.cm.getattr($cmap, 'name'); | |
# -- this works | |
#say 'Number of Colors : ' ~ $plt.cm.getattr($cmap, 'N'); | |
#say 'Colormap Name : ' ~ $plt.cm.getattr($cmap, 'name'); | |
# -- this fails on the 3rd call | |
say 'Colormap Name : ' ~ $plt.cm.getattr($cmap, 'name'); | |
say 'Number of Colors : ' ~ $plt.cm.getattr($cmap, 'N'); | |
say 'Number of Colors : ' ~ $plt.cm.getattr($cmap, 'N'); | |
# -- etc.... | |
So... here's an example of how you could wrap it...
class Matplotlib::Plot {
# ...
method get_cmap(|c) {
my $map = $py.call('matplotlib.pyplot', 'get_cmap', |c);
class {
method gist { 'ColorMap' ~ c.List.perl }
method N { return $map.__getattribute__('N') }
method name { return $map.__getattribute__('name') }
method FALLBACK($name, |c) {
$py.call('matplotlib.colors.LinearSegmentedColormap', $name, |c)
}
}.new()
}
I'm using an anonymous class, but you could just as easily define a proper class outside of Matplotlib::Plot
I think - possibly - that anonymous classes have slower performance, or maybe cannot be optimised as well... at least, that's the impression I got from a bunch of commits to Rakudo that removed anonymous classes in a bunch of iterators
In any case... this works now
my $plt = Matplotlib::Plot.new;
my $cmap = $plt.get_cmap('seismic', 5);
say $cmap; # ColorMap("seismic", 5)
say $cmap.name; # seismic
say $cmap.N; # 5
... though it's annoying to wrap every attribute like this... I had hoped this would work...
method FALLBACK($name, |c) {
# If no args and attribute exists...
if !c and $py.call('__builtin__', 'hasattr', $map, $name) {
return $map.__getattribute__($name)
}
# otherwise call method
else {
$py.call('matplotlib.colors.LinearSegmentedColormap', $name, |c)
}
}
and it does work... the first time, then it throws an error saying method-wrapper' object has no attribute
on the second call to any attribute. I dunno, this seems like a bug in Inline::Python
, but I don't want to bother the maintainer with issues I can work around with a little effort anyways. He's already put in a massive effort to make Inline::Python
work as well as it does.
Here's one more option... Attributes are not methods, you don't call them... you just ask what they are... sometimes you can assign to them (if Python does not define them as a @property
). So in this way, they are kind of like keys of a hash... so you can treat them as such.
class {
my $obj = $py.call( ... );
method AT-KEY($name) {
$obj.__getattribute__($name);
}
method ASSIGN-KEY($name, $new-value) {
$obj.__setattr__($name, $new-value)
}
method FALLBACK($name, |c) { ... }
}
Now you could access (and assign to) attributes as if they were a Hash
$pyobject<attributename> = "new";
# calls $pyobject.ASSIGN-KEY('attributename', 'new')...
# which calls $pyobject.__setattr('attributename', 'new')
say $pyobject<attributename>; # new
Upon further experimentation, it seems like problems happen as soon as you start doing calls to methods on __builtin__
. If I avoid those methods, I don't seem to hit the same problem.
For example, in my FALLBACK
in the earlier comment, if I don't check __builtin__.hasattr
then it works, but of course, Python will throw an exception if the attribute doesn't exists. You can put a try
in front of the call to __getattribute__
and it will return an undefined value (instead of throwing) if the attribute doesn't exists.
I'm not yet sure how to put all these ideas together in a cohesive whole, but play around with them and see what you come up with.
btw python's getattr
supports a third argument which is a default value in case the attribute doesn't exist.
If you write a wrapper, maybe the simplest thing to do is to create a sentinel value Any.new
as the default, and if that is returned, interpret it as not found.
More thanks 0racle, and Moritz, I definitely got way more out of this than I put in :-) I hope this thread has somehow kickstarted a future new article on your Perl6/Matplotlib blog 0racle. I agree about not bugging the Inline::Python maintainer over this when there is a work around.
It all seems so straightforward when it is explained, like you have both done in your comments, I'm not sure after a month of researching I'd have come to the same understanding :-) It's given me a whole ton of concepts and reasoning to investigate. I'll carry on with my efforts on this, and try to add some results to this gist when I have them. Thanks again.
No problem at all. I had never used matplotlib before I wrote that series... which is why I was eager at the time to document my progress, so that others such as yourselves could see the problems I ran into, and a way I solved them. As I said in the blog, often I'm not sure if the way I solved the problem was the best way, but a solution is better than none at all.
The colormap object is a
matplotlib.colors.LinearSegmentedColormap
, so... running with my plan of "wrap all the things"... you would define a new class (sayMatplotlib::Colors::Colormap
or something) that was instantiated when you called$ptl.cmap(|c)
. Subsequently when you call$cmap.name
, you can catch that method call in your newly defined class and redirect it to__getattribute__
... then as usual, punt everything else toFALLBACK
.Though your Exception idea is not bad either... but it could be extended with my idea of attribute checking. Under a
FALLBACK
method, if no arguments are provided, you could check Python'shasattr($name)
and ifTrue
, call__getattribute__($name)
on the object. Potentially within that same method you can catch exceptions and handle them accordingly.Regarding you formatting woes... Markdown uses double underscores to denote bold. When referring to short code-specific words, wrap them in `backticks` (which will produce the result
backticks
, and also disable other Markdown formatting within the backticks.)