parameter annotation semantics

Subject was: Re: [Python-ideas] Simple class initialization On Sat, Apr 16, 2011 at 6:27 PM, Steven D'Aprano <steve@pearwood.info>wrote:
I understand that. I can still think it a bit strange, can't I? To be more specific, you hit the crux of the problem with the statement "it is up to * THE* library or decorator to impose whatever meaning makes sense" [emphasis added] is that it assumes the singular. If I want to use two decorators which impose different meanings, I'm stuck. Imagine I have two decorators like this: @memoize # memoizes return values; if memo_value annotation is used, memoizes using # the value returned by applying the function def a(foo:memo_value(int), bar:memo_value(tuple)): pass @log_me # logs function calls and return values excluding parameters annotated no_log def b(foo, bar:no_log): pass Can I use both @memoize and @logging? Not if they're defined as they are above. I can if the decorators expect a list of annotations instead of a solo annotation: @memoize def a(foo:[memo_value(int)], bar:[memo_value(tuple)]): pass @log_me def b(foo, bar:[no_log]): pass @memoize @log_me def c(foo:[memo_value(int)], bar:[no_log, memo_value(tuple)]): pass Note that I am NOT proposing a change to the language (none is necessary), just suggesting that this would be good advice to offer authors. PEP 8 offers no advice on annotation syntax and PEP 3107 explicitly does not offer any advice saying "Despite yet more discussion, it was decided not to standardize a mechanism for annotation interoperability." What I would propose is something more like this: There is no standard for annotation interoperability. Annotations that wish to support interoperability should consider treating the annotation as an enumerable of values and checking value in annotation (or something similar) instead of value == annotation. Of course an obvious alternative is @memoize @log_me def c(foo:{memoize:memo_value(int)}, bar:{log_me:no_log, memoize:memo_value(tuple)}): pass but I think that's too verbose and therefore less likely to be adopted. --- Bruce *New! *Puzzazz newsletter: http://j.mp/puzzazz-news-2011-04 including April Fools! *New!** *Blog post: http://www.vroospeak.com Ironically, a glaring Google grammatical error

On Sat, Apr 16, 2011 at 9:53 PM, Bruce Leban <bruce@leapyear.org> wrote:
I imagine one nice way to approach the problem would be a meta-decorator like the following: [completely untested; please excuse likely Gmail line-wrapping] NULL = object() def annotation_sensitively_decorate(*decs): def decorate(f): annots = f.func_annotations keys = list(annots.keys()) for dec, assignments in zip(decs, zip(*list(annots.values()))): # determine and swap in annotations for current decorator cur_annots = {k:v for k,v in zip(keys, assignments) if v is not NULL} f.func_annotations = cur_annots f = dec(f) f.func_annotations = annots # restore orig annotations return f return decorate @annotation_sensitively_decorate(log_me, memoize) def c(foo: [do_log, memo_value(int)], bar: [no_log, memo_value(tuple)], baz: [NULL, NULL]) -> [NULL, NULL]: ... Cheers, Chris -- Metaprogramming is the best kind of programming, except when debugging. http://blog.rebertia.com

Bruce Leban wrote:
Well, yes, you're stuck... but if we standardise on a single meaning, then you can't have two decorators with different meanings, can you? This is kind like stating that we should standardise on a single flavour of ice cream (vanilla), because otherwise if you want two clashing flavours (liquorish and lemon, say) on the same cone, they will clash. Yes, if you pick two clashing decorators, they will clash. So either write a bridge between them, or do without one. This is hardly unique to annotations. You can't take two arbitrary decorators and sensibly combine them either. @eat_cake_now @save_cake_for_later def get_cake(): pass Could developers write decorators that process annotations in a cooperative fashion, regardless of the nature of the other decorators? Perhaps they could, but that will hardly prevent clashes: @exclude_given_types @include_given_types def func(arg: float): pass But even ignoring such obvious clashes, I'm not convinced that such cooperative signature processing is desirable. It seems to me that to make this work, decorators would have to be written to ignore annotations they don't expect, Just In Case some other decorator might be expecting them: @include_given_types def func(arg: "float"): # Oops, I meant a type object, not a string. pass This would mask errors and make debugging much harder. Now, of course this is not to suggest that any particular library can't be written in a cooperative fashion. But it seems to me that it would have to cooperate with a finite set of other *known* decorators (perhaps only its own, perhaps some other well-known library), rather than trying to interoperate with arbitrary decorators that expect arbitrary signatures and do arbitrary things. -- Steven

Bruce Leban wrote:
Seems to me that *any* scheme for attaching multiple annotations is likely to lead to unreadably verbose function headers. Or even single annotations, for that matter. Back when the decorator syntax was being hammered out, one of the objections to putting the decorator on the same line as the function header was that it could lead to excessively long and hard-to-read headers. I think the whole open-slather annotation idea is asking for the same thing in spades. -- Greg

On Sun, Apr 17, 2011 at 12:57 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
That's a good point, but the above example *could* be rewritten: foo_ans = {memoize: memo_value(int)} bar_ans = {log_me: no_log, memoize:memo_value(tuple)} @memoize @log_me def c(foo: foo_ans, bar:bar_ans): pass But at that point you're probably better off just making a fancy decorator that takes parameters instead of stuffing everything into an annotation.

On Sat, Apr 16, 2011 at 9:53 PM, Bruce Leban <bruce@leapyear.org> wrote:
I imagine one nice way to approach the problem would be a meta-decorator like the following: [completely untested; please excuse likely Gmail line-wrapping] NULL = object() def annotation_sensitively_decorate(*decs): def decorate(f): annots = f.func_annotations keys = list(annots.keys()) for dec, assignments in zip(decs, zip(*list(annots.values()))): # determine and swap in annotations for current decorator cur_annots = {k:v for k,v in zip(keys, assignments) if v is not NULL} f.func_annotations = cur_annots f = dec(f) f.func_annotations = annots # restore orig annotations return f return decorate @annotation_sensitively_decorate(log_me, memoize) def c(foo: [do_log, memo_value(int)], bar: [no_log, memo_value(tuple)], baz: [NULL, NULL]) -> [NULL, NULL]: ... Cheers, Chris -- Metaprogramming is the best kind of programming, except when debugging. http://blog.rebertia.com

Bruce Leban wrote:
Well, yes, you're stuck... but if we standardise on a single meaning, then you can't have two decorators with different meanings, can you? This is kind like stating that we should standardise on a single flavour of ice cream (vanilla), because otherwise if you want two clashing flavours (liquorish and lemon, say) on the same cone, they will clash. Yes, if you pick two clashing decorators, they will clash. So either write a bridge between them, or do without one. This is hardly unique to annotations. You can't take two arbitrary decorators and sensibly combine them either. @eat_cake_now @save_cake_for_later def get_cake(): pass Could developers write decorators that process annotations in a cooperative fashion, regardless of the nature of the other decorators? Perhaps they could, but that will hardly prevent clashes: @exclude_given_types @include_given_types def func(arg: float): pass But even ignoring such obvious clashes, I'm not convinced that such cooperative signature processing is desirable. It seems to me that to make this work, decorators would have to be written to ignore annotations they don't expect, Just In Case some other decorator might be expecting them: @include_given_types def func(arg: "float"): # Oops, I meant a type object, not a string. pass This would mask errors and make debugging much harder. Now, of course this is not to suggest that any particular library can't be written in a cooperative fashion. But it seems to me that it would have to cooperate with a finite set of other *known* decorators (perhaps only its own, perhaps some other well-known library), rather than trying to interoperate with arbitrary decorators that expect arbitrary signatures and do arbitrary things. -- Steven

Bruce Leban wrote:
Seems to me that *any* scheme for attaching multiple annotations is likely to lead to unreadably verbose function headers. Or even single annotations, for that matter. Back when the decorator syntax was being hammered out, one of the objections to putting the decorator on the same line as the function header was that it could lead to excessively long and hard-to-read headers. I think the whole open-slather annotation idea is asking for the same thing in spades. -- Greg

On Sun, Apr 17, 2011 at 12:57 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
That's a good point, but the above example *could* be rewritten: foo_ans = {memoize: memo_value(int)} bar_ans = {log_me: no_log, memoize:memo_value(tuple)} @memoize @log_me def c(foo: foo_ans, bar:bar_ans): pass But at that point you're probably better off just making a fancy decorator that takes parameters instead of stuffing everything into an annotation.
participants (6)
-
Bruce Leban
-
Carl M. Johnson
-
Chris Rebert
-
Greg Ewing
-
Stefan Behnel
-
Steven D'Aprano