You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I think it's a good first issue 'cause it's fairly standalone and rote if you know classes, only potential difficulty might be in storing the original args:
When I try AutoDateLocator.__repr__ = lambda self: f'AutodateLocator(maxticks={self.maxticks})' then repr(AutoDateLocator(maxticks=8)) returns 'AutodateLocator(maxticks={0: 8, 1: 8, 3: 8, 4: 8, 5: 8, 6: 8, 7: 8})', i.e. the class variable maxticks. I don't know how to get the value passed in during the function call. But even if this is possible, I find defining all the repr rather verbose and a bit hacky as compared to a simple eval.
only potential difficulty might be in storing the original args:
I don't see how you are going to do that without adding a bunch of verbiage to the init, which I wouldn't be in favour of just to make one example more pythonic.
I don't see how you are going to do that without adding a bunch of verbiage to the init
Kinda hoping @anntzer will tell me there's some sorta magic you can stick on the base class that will just handle it, which really yeah I dunno that I'd be in favor of reprs if they were overly complicated, but might be worth someone poking and seeing if it can be done cleanly.
It wouldn't just be to make the example more pythonic - the repl can be useful for debugging & reproducibility - if a formatter is being weird, it'd be great to do a quick first pass by printing - especially the default formatters.
I'm fine with a clean repr that can be based on the current state of the Locator or Formatter. I'm just leery of magic to capture what the user typed as arguments.
The reason why it's a problem is because it conflicts with my first assumption in my PR, specifically RadialLocator which you can pass in an Axis object that doesn't have a working repr at init time like PolarAxes.
I do also wish to reassert @jklymak's comment from above:
I'm fine with a clean repr that can be based on the current state of the Locator or Formatter. I'm just leery of magic to capture what the user typed as arguments.
Both the code above and that of the PR only capture the starting state, and so will be misleading if the values are set after instantiation.
:/ but I do see your point that it should reflect current state if it's gonna be useful for debugging, even if'd rather it reflect initial state for the purpose of the examples.
As for the technical question, is the working repr a must or something that can be special cased?
Just to clarify, based on my testing, the repr works, it's just that when we are initing a new PolarAxis object it's missing something for the repr to work. I think I can fix it two ways:
Since from the unit tests it looks like PolarAxes objects are the only ones being passed like this, I could treat it like a special case like you suggest.
Figure out exactly what's missing for the repr to work and fiddle with how it's being inited, so it works by the time we call it.
Regarding reflecting the current state, I think this is possible if we just got all the param names from the init function in init_subclass like anntzer's example, and grabbed the names of all kwargs, from a wrapped init.
Then at run time in the repr call, we simply get all the values using getattr. This would work as long as the names of the args matched the names of the attributes exactly (a small difference like a _ in the front could be accounted for though). For any attributes that don't match, we can just manually add mappings in a private class variable to avoid making any api changes. Killing two birds with one stone, doing it like this will most likely fix the issue with RadialLocator too.
I'm leaning towards anything that tries to be magical and work for all subclasses is likely to be fragile at best... (And a confusing not very well trodden path to figure out what is going wrong at worst).
I would tend to agree with #25767 (comment) where the solution is less of a "try to write something general" and more of a "go through each of the first party classes and write simple reprs for each"
The fact of the matter is the reason such a magical repr doesn't already exist is that there is no requirement that the inputs to __init__ are stored directly in variables that are so trivial to pick out, nor especially that the internal state has names that are related to the __init__ parameters.
The example given above is incorrect because the __init__ arg is named invalidMap but the internal state is called validMap, and that is what is put in the repr. You could try to disambiguate on value, but that will quickly run into problems if multiple fields have the same value.
In short, I don't think it can be done without imposing restrictions on subclasses (such as naming of internal state). The alternative is to write bespoke reprs for each class, which is not as interesting of a problem, but elides nearly every concern by putting implementation local to the individual classes which know what their parameters are and how to back calculate them. The only additional concern is therefore that subclasses further down the class hierarchy may get incorrect reprs inherited. This is solved by implenting reprs for that specifically, but that must be done by any downstream library that makes one. We can at least use type(self).__name__ to get the class name correct, and if params/backcalculations are unchanged that will work.
I don't have a strong bias in either direction - I like a catch all approach b/c it catches everything in one shot and is probably more fun to code, but I agree that individual reprs in each class will probably be easier to maintain. And very possibly altogether add less verbiage than the automatic solutions, which is how we got down that rabbit hole in the first place - @Daniil-Aleshechkin how do you feel about going simple?
Yea actually I agree with @ksunden, making this restriction will basically couple the existing names or values to the repr, and it's not exactly clear from each class without having to do some deep diving how the repr method is generated. Plus I'd admit this solution isn't exactly simple or pythonic. Also it's not guaranteed to grab everything. What I'm leaning towards is to have a general repr, but it's dependent on some function like _get_repr_params which returns a dictionary of all params we'd care about for each class. Something like:
One problem about it is we can't enforce an eval(repr()) = call relationship. Though we can always just test that with unit tests, and that will be good enough for maintainability.
One problem about it is we can't enforce an eval(repr()) = call relationship.
Many of these classes have state that can only be set after the fact so ensuring that this always holds is not currently possible (that is if the repr is the init call than there can be two objects that are functionally different (swapping them on an Axis changes the perceived visual appearance) but have identical reprs).
We have a number of ad-hoc protocols like this in the codebase already, however they need to save a fair amount of complexity to be worth it. In this case it is not clear to me that defining a special private method to source a dictionary to to pass to an over-ridable function static method from the parent class is simpler or easier to explain when compared to
I think the general solution would eventually converge to re-writing attrs or dataclasses piece by piece and that is a much larger project (and if we went that route we probably should just use one of the "managed attribute" libraries that are out there like traitlets, attrs, pydandtic, dataclasses, etc rather than roll our own).
So while I put this in as a motivation, this isn't a blocker by any means since I primarily want reprs for the debugging and the examples can be tweaked through other means.
Activity
jklymak commentedon Dec 10, 2021
I don't see how you are going to do that without adding a bunch of verbiage to the init, which I wouldn't be in favour of just to make one example more pythonic.
story645 commentedon Dec 10, 2021
Kinda hoping @anntzer will tell me there's some sorta magic you can stick on the base class that will just handle it, which really yeah I dunno that I'd be in favor of reprs if they were overly complicated, but might be worth someone poking and seeing if it can be done cleanly.
It wouldn't just be to make the example more pythonic - the repl can be useful for debugging & reproducibility - if a formatter is being weird, it'd be great to do a quick first pass by printing - especially the default formatters.
jklymak commentedon Dec 10, 2021
I'm fine with a clean repr that can be based on the current state of the Locator or Formatter. I'm just leery of magic to capture what the user typed as arguments.
anntzer commentedon Dec 10, 2021
Something like
? No guarantees, may brick your homework, eat your dog, or whatnot.
Daniil-Aleshechkin commentedon May 1, 2023
@story645 Heyyo, so I've got a question about this. Should we include RadialLocator, ThetaLocator. and ThetaFormatter from projections/polar.py? They aren't mentioned in the docs (https://matplotlib.org/devdocs/api/ticker_api.html, https://matplotlib.org/devdocs/gallery/ticks/tick-formatters.html, https://matplotlib.org/devdocs/gallery/ticks/tick-locators.html), but they do decorate the Locator/ Formater classes so they still technically inherit from them.
The reason why it's a problem is because it conflicts with my first assumption in my PR, specifically RadialLocator which you can pass in an Axis object that doesn't have a working repr at init time like PolarAxes.
story645 commentedon May 1, 2023
Um so I think they should definitely be documented, possibly in their own example like datetime
As for the technical question, is the working repr a must or something that can be special cased?
ksunden commentedon May 1, 2023
I do also wish to reassert @jklymak's comment from above:
Both the code above and that of the PR only capture the starting state, and so will be misleading if the values are set after instantiation.
story645 commentedon May 1, 2023
:/ but I do see your point that it should reflect current state if it's gonna be useful for debugging, even if'd rather it reflect initial state for the purpose of the examples.
Daniil-Aleshechkin commentedon May 1, 2023
Just to clarify, based on my testing, the repr works, it's just that when we are initing a new PolarAxis object it's missing something for the repr to work. I think I can fix it two ways:
Regarding reflecting the current state, I think this is possible if we just got all the param names from the init function in init_subclass like anntzer's example, and grabbed the names of all kwargs, from a wrapped init.
Then at run time in the repr call, we simply get all the values using getattr. This would work as long as the names of the args matched the names of the attributes exactly (a small difference like a _ in the front could be accounted for though). For any attributes that don't match, we can just manually add mappings in a private class variable to avoid making any api changes. Killing two birds with one stone, doing it like this will most likely fix the issue with RadialLocator too.
Daniil-Aleshechkin commentedon May 3, 2023
@story645 Does the position of each arg matter? I have a (rough) working solution currently like:
but it does not guarantee that the args will match the order of the init method. For ex:
prints
Child(validMap='test2', kwargsTest='test3', foo='test1', bool=True)story645 commentedon May 3, 2023
Um mostly depends on how much extra code would be needed to make things match, but I don't consider it a blocker.
ksunden commentedon May 4, 2023
I'm leaning towards anything that tries to be magical and work for all subclasses is likely to be fragile at best... (And a confusing not very well trodden path to figure out what is going wrong at worst).
I would tend to agree with #25767 (comment) where the solution is less of a "try to write something general" and more of a "go through each of the first party classes and write simple reprs for each"
The fact of the matter is the reason such a magical repr doesn't already exist is that there is no requirement that the inputs to
__init__are stored directly in variables that are so trivial to pick out, nor especially that the internal state has names that are related to the__init__parameters.The example given above is incorrect because the
__init__arg is namedinvalidMapbut the internal state is calledvalidMap, and that is what is put in the repr. You could try to disambiguate on value, but that will quickly run into problems if multiple fields have the same value.In short, I don't think it can be done without imposing restrictions on subclasses (such as naming of internal state). The alternative is to write bespoke reprs for each class, which is not as interesting of a problem, but elides nearly every concern by putting implementation local to the individual classes which know what their parameters are and how to back calculate them. The only additional concern is therefore that subclasses further down the class hierarchy may get incorrect reprs inherited. This is solved by implenting reprs for that specifically, but that must be done by any downstream library that makes one. We can at least use
type(self).__name__to get the class name correct, and if params/backcalculations are unchanged that will work.story645 commentedon May 4, 2023
I don't have a strong bias in either direction - I like a catch all approach b/c it catches everything in one shot and is probably more fun to code, but I agree that individual reprs in each class will probably be easier to maintain. And very possibly altogether add less verbiage than the automatic solutions, which is how we got down that rabbit hole in the first place - @Daniil-Aleshechkin how do you feel about going simple?
Daniil-Aleshechkin commentedon May 4, 2023
Yea actually I agree with @ksunden, making this restriction will basically couple the existing names or values to the repr, and it's not exactly clear from each class without having to do some deep diving how the repr method is generated. Plus I'd admit this solution isn't exactly simple or pythonic. Also it's not guaranteed to grab everything. What I'm leaning towards is to have a general repr, but it's dependent on some function like _get_repr_params which returns a dictionary of all params we'd care about for each class. Something like:
What do you think?
One problem about it is we can't enforce an eval(repr()) = call relationship. Though we can always just test that with unit tests, and that will be good enough for maintainability.
tacaswell commentedon May 4, 2023
Many of these classes have state that can only be set after the fact so ensuring that this always holds is not currently possible (that is if the repr is the init call than there can be two objects that are functionally different (swapping them on an Axis changes the perceived visual appearance) but have identical reprs).
We have a number of ad-hoc protocols like this in the codebase already, however they need to save a fair amount of complexity to be worth it. In this case it is not clear to me that defining a special private method to source a dictionary to to pass to an over-ridable function static method from the parent class is simpler or easier to explain when compared to
in each class!
I think the general solution would eventually converge to re-writing
attrsordataclassespiece by piece and that is a much larger project (and if we went that route we probably should just use one of the "managed attribute" libraries that are out there like traitlets, attrs, pydandtic, dataclasses, etc rather than roll our own).story645 commentedon May 4, 2023
So while I put this in as a motivation, this isn't a blocker by any means since I primarily want reprs for the debugging and the examples can be tweaked through other means.
Add __repr__ to Formatter and Locator classes (fixes matplotlib#21898)