[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Fri, 18 Dec 2020 17:42:26 +1300 Greg Ewing wrote: > On 18/12/20 1:48 pm, Paul Sokolovsky wrote: > > So, it's already clear that mod.func() syntax will continue to work > > as before. I don't foresee any problems with implementing that, do > > you? > > What about this: > > import random > > class A: > def choice(self, stuff): > return stuff[0] > > a = A() > > def f(x, y): > return x.choice(y) > > print(f(random, [1, 2])) > print(f(a, ["buckle", "my shoe"])) > > How much of this is allowed under your restricted semantics? It's fully allowed, under both part 1 and part 2 of the strict mode. It won't be possible to optimize it in any way under just the "strict mode" idea, but again, it will work. Generally, the idea behind the strict mode is to optimize *dynamic name lookups*. It doesn't deal with *dynamic typing* in any way (well, no more than optimizing dynamic lookups into static (in some, not all cases) would allow, and it does allow that). That's because: a) dynamic typing is what everybody loves about Python (myself including); b) there's already a way to deal with dynamic typing issue - type annotations; c) dealing with typing issues generally requires type inference, and general type inference is a task of quite different scale than the "strict mode" thingy I build here. (But adhoc type inference can be cheap, and again, the strict mode effectively enables type (and even value) inference for many interesting and practical cases.) > -- > Greg -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/B6KYXSTB634ZBJUL33WCLT2HY5IWU2NP/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 18/12/20 1:48 pm, Paul Sokolovsky wrote: So, it's already clear that mod.func() syntax will continue to work as before. I don't foresee any problems with implementing that, do you? What about this: import random class A: def choice(self, stuff): return stuff[0] a = A() def f(x, y): return x.choice(y) print(f(random, [1, 2])) print(f(a, ["buckle", "my shoe"])) How much of this is allowed under your restricted semantics? -- Greg ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/P3MUJLB2NXNX3ARZRTST4KHIEY32LEIM/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Fri, 18 Dec 2020 12:29:10 +1300 Greg Ewing wrote: > On 18/12/20 7:01 am, Paul Sokolovsky wrote: > > Now, what if you have an object attribute which stores a callable > > (no matter is it's bound method, a function, a class, or whatever)? > > > > You will need to call it as "(obj.callable_ref)(arg, kw=val)". > > So, in strict mode, this: > > import random > n = random.choice([1,2,3]) > > would have to be written: > > import random > n = (random.choice)([1,2,3]) > > I would find that quite unpleasant. Surely no, as we touched in the other email. That's how "quick 2-clause idea" differs from "full spec". First one presents the main idea (being able to call methods based on just type namespace, not on combined instance + type namespaces), whereas "full spec" would need to consider "all other cases too". So, it's already clear that mod.func() syntax will continue to work as before. I don't foresee any problems with implementing that, do you? Generally, the semantic changes discussed affect *only* user-defined classes. Module objects, being builtin, aren't affected by new semantic aspect, so I would expect zero changes at all would be required to them in that regard. I'm basing on the architecture of MicroPython-based VM/object model. Again, if you foresee any issues with e.g. CPython, let me know, I can check that. Beyond modules, there're other cases to consider. E.g., I said that an instance attribute cannot shadow class'es *method* of the same name, you can reasonably ask "what about other class fields?". And I'd say "needs to be considered." I'd err on the side of banning any shadowing, but I have to admit I did use a pattern like that more than once myself: class Foo: option = "default" o1 = Foo() print(o1.option) o2 = Foo() o2.option = "overriden" Foo.option = "change default for all objs which didn't override it" I personally wouldn't consider it end of the world to switch that to: def get_option(self): if hasattr(self, "option"): return self.option else: return self.__class__.option But certainly requires more consideration and actual looking at the good corpus of code to see how common is that. > -- > Greg -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/EJ3OIOBZAYJMI3ELIDNUQ4JMB5CVANZC/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 18/12/20 7:01 am, Paul Sokolovsky wrote: Now, what if you have an object attribute which stores a callable (no matter is it's bound method, a function, a class, or whatever)? You will need to call it as "(obj.callable_ref)(arg, kw=val)". So, in strict mode, this: import random n = random.choice([1,2,3]) would have to be written: import random n = (random.choice)([1,2,3]) I would find that quite unpleasant. -- Greg ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/3TSES4AJQ3NQYDFUKG6CHHD376HYPOLL/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Thu, 17 Dec 2020 15:28:47 -0500 Alexandre Brault wrote: > On 2020-12-15 5:16 a.m., Paul Sokolovsky wrote: > >> The compiler applies it when it > >> can see the only use of the attribute is an immediately following > >> call. > > You're now just a step away from the "right answer". Will you make > > it? I did. And sorry, the whole point of the discussion if to see > > if the whole path, each step on it, and the final answer is as > > unavoidable as I now imagine them to be, so I can't push you > > towards it ;-). > > Oh, that was the point of the discussion? Wonderful then, I can easily > answer. > > Considering that, over multiple days of discussion, literally nobody > came to the same conclusion that you did, then it's obvious that the > while path, each step on it, and the final answer are NOT as > unavoidable as you imagine them to be. Right, that means the spec for "strict mode, part 2" will need to be as long and detailed as that for "strict mode, part 1", and collect together all the stuff which was discussed here over these multiple days. Whereas if other people would have come to that conclusion, it could be much shorter and faster to write. > > I think we can consider this case closed > > Alexandre Brault -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/GCTDJMZW772MJ2RRT454FX6ATXG3O4R4/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Thu, Dec 17, 2020 at 03:52:51PM +0300, Paul Sokolovsky wrote: > But I did by now, and you didn't need to wait for me to do it, because > "(a.b)()" does *exactly* what *you* or anybody else would think it > does, based on your knowledge of what grouping parens in the > expressions do. So again, "(a.b)()" first extracts a value of the "b" > *attribute* from the object held in variable "a", then calls that > value with 0 arguments. > > That's in striking difference to "a.b()", which calls the *method* "b" > of object held in variable "a". In CPython, both generate exactly the same byte-code, and both will call any sort of object. Or *attempt* to call, since there is no guarantee that the attribute returned by `a.b` (with or without parens) will be a callable object. You are imagining differences in behaviour which literally do not exist. ``` >>> dis.dis('a.b()') 1 0 LOAD_NAME0 (a) 2 LOAD_METHOD 1 (b) 4 CALL_METHOD 0 6 RETURN_VALUE >>> from types import SimpleNamespace >>> a = SimpleNamespace() >>> a.b = int >>> a.b("123") 123 ``` -- Steve ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/A5WWXS27SESCBESVQSUOCT3ZUC5GJ7EQ/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 2020-12-15 5:16 a.m., Paul Sokolovsky wrote: >> The compiler applies it when it >> can see the only use of the attribute is an immediately following >> call. > You're now just a step away from the "right answer". Will you make it? > I did. And sorry, the whole point of the discussion if to see if the > whole path, each step on it, and the final answer is as unavoidable as > I now imagine them to be, so I can't push you towards it ;-). Oh, that was the point of the discussion? Wonderful then, I can easily answer. Considering that, over multiple days of discussion, literally nobody came to the same conclusion that you did, then it's obvious that the while path, each step on it, and the final answer are NOT as unavoidable as you imagine them to be. I think we can consider this case closed Alexandre Brault ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/TRF2K35CZYGMY7ZEDI4QST2FXG2PU3GQ/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Fri, Dec 18, 2020 at 01:23:34AM +1300, Greg Ewing wrote: > On 17/12/20 11:25 pm, Paul Sokolovsky wrote: > >CPython compiles "(a.b)()" using LOAD_METHOD not because it consciously > >"optimizes" it, > >but simply because it's *unable* to represent the difference between > >"a.b()" and "(a.b)()". > > I'm pretty sure whoever added the optimisation fully intended it > to apply to (a.b)() as well as a.b() -- given that they are > supposed to have the same semantics, why would you *not* want > to optimise both? So even if the AST were able to distinguish > between them, there would be no reason to do so. Not only are they *supposed* to have the same semantics, but they *literally do* have the same semantics. The CALL_METHOD op code doesn't just call methods, just as the CALL_FUNCTION op code doesn't just call functions. The only difference between them is the implementation of *how* they perform the call. -- Steve ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/7PSRW4WNIPBIZN6U2XR5X576EB7N3I43/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
17.12.20 21:58, Ethan Furman пише: > On 12/17/20 11:09 AM, Chris Angelico wrote: >> "a+b" is NOT implemented as "a.__add__(b)", nor as "type(a).__add__(b)"! > > Okay, what is it implemented as? First, it can use not only method __add__ of the type of a, but also method __radd__ of the type of b. Second, the lookup of __add__ differs from both a.__add__ and type(a).__add__. It differs from a.__add__ because skips an instance dict and __getattr__. It differs from type(a).__add__ because does not fallback to methods of the metaclass. This all is pretty complicated. ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/ADK5DGEEPPFQ5XY55GUQWRUY3QBDXUSD/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 12/17/20 11:09 AM, Chris Angelico wrote: > I said I wasn't going to respond, but this one is SUCH a common > misunderstanding that I don't want people led astray by it. > > "a+b" is NOT implemented as "a.__add__(b)", nor as "type(a).__add__(b)"! Okay, what is it implemented as? -- ~Ethan~ ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/ZOENW5EYHNODZXRVQ2BJP5CKGMICR4N2/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Fri, 18 Dec 2020 06:09:56 +1100 Chris Angelico wrote: > On Fri, Dec 18, 2020 at 5:02 AM Paul Sokolovsky > wrote: > > This is not some completely new restriction. For example, following > > already doesn't work in Python: > > > > class A: > > pass > > > > o = A() > > o.__add__ = lambda self, x: print("me called") > > > > o + A() # lambda above is never called > > > > But the addition operator isn't just calling __add__, so this IS a > completely new restriction. You're comparing unrelated things. No, you're just shifting discussion to something else. Special methods assigned to object instances aren't get called in general. If you can show how to assign an arbitrary dunder method to an instance and get it called by operator, please do that. Otherwise, that was exactly the point to show. > > class A: > def __add__(self, other): > print("I got called") > > class B(A): > def __add__(self, other): > print("Actually I did") > > A() + B() > > The operator delegation mechanism doesn't use the class as a means of > optimization. It does it because it is the language specification to > do so. So, the language specification for the "strict execution mode" will say that "the only way to define a method is syntactically in the class body". What's your problem with that? You often assign your methods to individual instances after they're created? Please speak up and explain us your usecases. [] -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/RMSY4P42YVOXC3ZRT6SDRUOKNOZVL2JX/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Fri, Dec 18, 2020 at 5:02 AM Paul Sokolovsky wrote: > This is not some completely new restriction. For example, following > already doesn't work in Python: > > class A: > pass > > o = A() > o.__add__ = lambda self, x: print("me called") > > o + A() # lambda above is never called > But the addition operator isn't just calling __add__, so this IS a completely new restriction. You're comparing unrelated things. class A: def __add__(self, other): print("I got called") class B(A): def __add__(self, other): print("Actually I did") A() + B() The operator delegation mechanism doesn't use the class as a means of optimization. It does it because it is the language specification to do so. I said I wasn't going to respond, but this one is SUCH a common misunderstanding that I don't want people led astray by it. "a+b" is NOT implemented as "a.__add__(b)", nor as "type(a).__add__(b)"! Can this thread move off python-ideas and onto pycopy-ideas please? It's not really talking about Python any more, it just pretends to be. ChrisA ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/KWNAJHZ4YAT7IHHBHVA6HBQHLKF22HQR/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Thu, 17 Dec 2020 10:03:14 + Stestagg wrote: [] > > > > But still, are there Python implementations which compile > > > > "(a.b)()" faithfully, with its baseline semantic meaning? Of > > > > course there're. > > > > > > OK, Paul, why don't you propose a PR and a bug report about it? > > > > But that's not what this talk is about! It's about a new exciting > > (hmm, we'll see) feature which, turned out, was there all this time, > > but was overlooked (so, no patches are needed right away). So, I'm > > asking fellow Python programmers if they recognize it. If they do, > > we can consider how to get more from that feature, and maybe some > > patches will be useful. And if they don't, no patches would help. > > > > > I like it. > > The idea of a 'method call operator' is quite cute, it's an alternate > way of thinking about the situation that seems to be self-consistent > (at least on the surface). Thanks! > BUT. > > It's only worth talking about alternate interpretations if there's a > reasonable chance that introducing a new way of thinking about a > problem will lead to some improvement: either functional enhancement, > or better abstractions. By now I already mentioned a few times that the whole motivation to introduce it is to improve performance of name lookup operations (specifically, method lookup). And in this regard, it continues on my earlier posted proposal of the "strict execution mode" (https://github.com/pycopy/PycoEPs/blob/master/StrictMode.md) > Do you have concrete ideas about how treating this language construct > as a new operator might end up bringing tangible benefits? Yes, under my proposal, "method call operator" will literally call only methods, where "a method" is defined as "a function lexically contained within the class definition". This means that comparing to the current lookup rules, a.b() won't need to look for "b" inside the "a" instance, but will look straight in a's class. In other words, the lookup process will be largely approaching that of C++. Of course, there's still a big difference that C++ method vtables are indexed by integer id, as all types (and thus, methods) are known at compiler-time, while we still will need to maintain method "vdicts", where we map from symbolic method names to the actual method code. But, that's the price to pain for dynamic-typedness of the language. In other aspects, many existing C++-like optimizations of class hierarchies can be applied (if it all is coupled with the "strict mode part1" proposal above). To avoid ambiguity, shadowing class method with instance attributes won't work: class Foo: def meth(self): pass o = Foo() # doesn't work o.meth = 1 This is not some completely new restriction. For example, following already doesn't work in Python: class A: pass o = A() o.__add__ = lambda self, x: print("me called") o + A() # lambda above is never called So again, a new restriction is nothing but the existing restriction, applied consistently to all methods, not just dunder methods. And as it doesn't work with any method, we also can be explicit about it, instead of "not working silently", like the __add__ example above shows. So, back to 1st example: # Leads to AttributeError or RuntimeError o.meth = 1 That's why, well, it's called "strict mode" - erroneous actions don't pass silently. Now, what if you have an object attribute which stores a callable (no matter is it's bound method, a function, a class, or whatever)? You will need to call it as "(obj.callable_ref)(arg, kw=val)". This is a recent lucky improvement over the previous approach I had in mind, involving resurrecting the apply() function of good old Python1/Python2 days: apply(obj.callable_ref, (arg,), {"kw": val}) > Somewhat relatedly, I very much appreciate the simplicity and clean > approach that Python takes with objects (experiencing Ruby briefly > having written python made this benefit very clear). There's very good reason why I apply this effort to Python, as it's already more stricter/strongly-typed language than most of its popular cohorts. > With python 3, > the distinction between a function and a method has been further > reduced, and any change that risks moving us away from `obj.meth()` > being functionally equivalent to `getattr(obj, 'meth')()` or > `getattr(Cls, 'meth')(obj)` would have to have incredibly strong > benefits to outweigh the cost of doing so. The proposal preserves the first-class nature of bound methods, so if you had `obj.meth()`, you can always write `t = obj.meth; t()`, or the forms that you show above, with the same effect. However, the reverse is not true - if you had `obj.attr`, you can't call value of that attribute as `obj.attr()`, because that's the syntax to call methods, not attributes. You'll need to write it as `(obj.attr)()`. That syntax is fully compatible with the existing Python. So, a program which adheres to the strict mode restrictions, will also run in exactly
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Fri, 18 Dec 2020 03:05:02 +1300 Greg Ewing wrote: > On 18/12/20 1:52 am, Paul Sokolovsky wrote: > > On Fri, 18 Dec 2020 01:23:34 +1300 > > Greg Ewing wrote: > >> On 17/12/20 11:25 pm, Paul Sokolovsky wrote: > >>> a) (a.b)() syntax > >>> b) apply() being resurrected > >> > >> I can't answer that without knowing what alternative semantics > >> you have in mind for (a.b)(). You haven't really explained that > >> yet. > > > > But I did by now, and you didn't need to wait for me to do it, > > because "(a.b)()" does *exactly* what *you* or anybody else would > > think it does, based on your knowledge of what grouping parens in > > the expressions do. So again, "(a.b)()" first extracts a value of > > the "b" *attribute* from the object held in variable "a", then > > calls that value with 0 arguments. > > We're going round in circles. Yep :-(. I wonder, if reading of each other's arguments more thoroughly could help. > Are we talking about current > Python semantics, or proposed new semantics? Please just > give us a clear answer on that. I'm not talking about the current Python semantics, because there's nothing to talk about. We both agreed it won't change, so what do you want to talk about re: it? The only purpose "current Python semantics" serve in the discussion is that new semantics gets explained in terms of difference wrt to the current. Otherwise, I'm talking how the proposed new semantics comes out smoothly and naturally from the existing Python semantics, syntax, and other details. And feedback on the "comes out smoothly and naturally" part is exactly what I'm interested in. > If we're talking about current semantics, then my knowledge Right, so if the current semantics clouds your view, one approach would be to put it aside, and look at the things with unencumbered look, to see if you can see the things I'm talking about. > of what grouping parens do (which is based on what the > Python docs define them to do) means that a.b() and (a.b)() > are just different ways of writing *exactly* the same thing. > > > That's in striking difference to "a.b()", which calls the *method* > > "b" of object held in variable "a". > > No, it doesn't. It calls *whatever* object a.b returns. Bingo! That's exactly what I seek to improve, so it with much higher probability "returns" a *method*, and not just "whatever". > It only calls a method if b happens to be a method. > It might not be. It might be a callable object stored > in an instance attribute, or a function stored in a > module attribute, in which case the very same syntax is > just an ordinary call, not a method call. So, it seems like you have read what I wrote in the message https://mail.python.org/archives/list/python-ideas@python.org/message/PF42DP2IYYRFUJMYCJ2NDRBXU57VNFEH/ , because you explain how the current semantics work, after I explained how it's being changed to work differently. And if something is unclear in my explanation, just let me know. It's not like the message above is 2-line and leaves much for you to guess. It's not 30KB text either, which chews thru all aspects (like my previous proposal does). So, if some aspect *of new semantics* is unclear, I'd be glad to elaborate on it. (Ahead of a 30KB text, which is coming, but much later). > That last case is something you might want to ponder. > The same LOAD_METHOD/CALL_METHOD bytecode sequence is > being executed, yet it's *not* performing a method > call! How does your new improved theory of method calls > explain *that*? As any other such case - "as implementation artifact grounded in semantic ambiguities of a dynamic language". My aim is to reduce such ambiguities largely, while still preserving dynamic-typing spirit of the language (where it's needed). So, a compiler which employs my previous proposal https://mail.python.org/archives/list/python-ideas@python.org/thread/KGN4Q2EMOMGHLMUH4ITJB4ES7NR3FSB7/ will be able to optimize that to just CALL_FUNCTION in *most* cases. But not all cases, yeah. > > In a sense I don't. I intend to make very fine-grained, finely cut > > semantic *adjustments* > > If you come back when you've figured out exactly what those > adjustments will be and are able to explain them clearly, > we will have something to talk about. Again, the end of https://mail.python.org/archives/list/python-ideas@python.org/message/PF42DP2IYYRFUJMYCJ2NDRBXU57VNFEH/ drafts the scheme of the new proposed semantics (codenamed "the strict mode part 2"). I will also try to summarize it in a different (hopefully, clearer and more down-to-earth) way in a response to another participant. Eventually I'll also write down a full "spec", but that likely will take quite some time (and then will be a long read). So, if you happen to be really interested in that stuff, while waiting for the new spec, you may be interested to skim thru the previous part of the proposal,
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 18/12/20 1:52 am, Paul Sokolovsky wrote: On Fri, 18 Dec 2020 01:23:34 +1300 Greg Ewing wrote: On 17/12/20 11:25 pm, Paul Sokolovsky wrote: a) (a.b)() syntax b) apply() being resurrected I can't answer that without knowing what alternative semantics you have in mind for (a.b)(). You haven't really explained that yet. But I did by now, and you didn't need to wait for me to do it, because "(a.b)()" does *exactly* what *you* or anybody else would think it does, based on your knowledge of what grouping parens in the expressions do. So again, "(a.b)()" first extracts a value of the "b" *attribute* from the object held in variable "a", then calls that value with 0 arguments. We're going round in circles. Are we talking about current Python semantics, or proposed new semantics? Please just give us a clear answer on that. If we're talking about current semantics, then my knowledge of what grouping parens do (which is based on what the Python docs define them to do) means that a.b() and (a.b)() are just different ways of writing *exactly* the same thing. > That's in striking difference to "a.b()", which calls the *method* "b" > of object held in variable "a". No, it doesn't. It calls *whatever* object a.b returns. It only calls a method if b happens to be a method. It might not be. It might be a callable object stored in an instance attribute, or a function stored in a module attribute, in which case the very same syntax is just an ordinary call, not a method call. That last case is something you might want to ponder. The same LOAD_METHOD/CALL_METHOD bytecode sequence is being executed, yet it's *not* performing a method call! How does your new improved theory of method calls explain *that*? In a sense I don't. I intend to make very fine-grained, finely cut semantic *adjustments* If you come back when you've figured out exactly what those adjustments will be and are able to explain them clearly, we will have something to talk about. -- Greg ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/7B35CV52SA3VG43BU54PHTGJFOSNRALL/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Fri, 18 Dec 2020 01:23:34 +1300 Greg Ewing wrote: > On 17/12/20 11:25 pm, Paul Sokolovsky wrote: > > CPython compiles "(a.b)()" using LOAD_METHOD not because it > > consciously "optimizes" it, but simply because it's *unable* to > > represent the difference between "a.b()" and "(a.b)()". > > I'm pretty sure whoever added the optimisation fully intended it > to apply to (a.b)() as well as a.b() -- given that they are > supposed to have the same semantics, why would you *not* want > to optimise both? So even if the AST were able to distinguish > between them, there would be no reason to do so. > > Your alternative theory would be of use if you wanted to change > the semantics of (a.b)(). But the semantics need to be defined > first, then you make the AST and code generation whatever it needs > to be to support those semantics. > > > a) (a.b)() syntax > > b) apply() being resurrected > > I can't answer that without knowing what alternative semantics > you have in mind for (a.b)(). You haven't really explained that > yet. But I did by now, and you didn't need to wait for me to do it, because "(a.b)()" does *exactly* what *you* or anybody else would think it does, based on your knowledge of what grouping parens in the expressions do. So again, "(a.b)()" first extracts a value of the "b" *attribute* from the object held in variable "a", then calls that value with 0 arguments. That's in striking difference to "a.b()", which calls the *method* "b" of object held in variable "a". > > We just discussed the pillar #1 of the strict mode, part 2. For that > > pillar being the separation between methods and attributes. > > > What's interesting, that part 2, just like part 1, of the strict > > mode doesn't really make any "revolutionary" changes. It just > > exposes, emphasizes, and makes consistent, the properties Python > > language already has. Ain't that cute? Do you spot any issues, > > Greg? > > So you *don't* intend to make any semantic changes? In a sense I don't. I intend to make very fine-grained, finely cut semantic *adjustments*, with a motive of being able to implement the semantics more efficiently, and with a constraint that program valid under the new semantics is also valid (and have the same effect) under the old. Again, this is continuation of the effort previously presented in https://mail.python.org/archives/list/python-ideas@python.org/thread/KGN4Q2EMOMGHLMUH4ITJB4ES7NR3FSB7/ where you can assess yourself how successful the "also valid under old" part was so far. For indeed, that's the distinguishing trait of how my effort differs from some (many?) other efforts, for example the Prothon project that you quoted. Where people start with already rather distinct than Python ideas, and then can't resist to diverge more, and more, and more. Whereas I try (with an aspiration to be pretty thorough) to make as little changes as possible to achieve the desired effect (which is the ability to write more efficient Python programs, not inventing a new language!) > > I'm still confused. > > -- > Greg -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/N4CYM4QWTUFD7ESQ25MWLPXEBF2NWRSP/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 17/12/20 11:25 pm, Paul Sokolovsky wrote: CPython compiles "(a.b)()" using LOAD_METHOD not because it consciously "optimizes" it, but simply because it's *unable* to represent the difference between "a.b()" and "(a.b)()". I'm pretty sure whoever added the optimisation fully intended it to apply to (a.b)() as well as a.b() -- given that they are supposed to have the same semantics, why would you *not* want to optimise both? So even if the AST were able to distinguish between them, there would be no reason to do so. Your alternative theory would be of use if you wanted to change the semantics of (a.b)(). But the semantics need to be defined first, then you make the AST and code generation whatever it needs to be to support those semantics. a) (a.b)() syntax b) apply() being resurrected I can't answer that without knowing what alternative semantics you have in mind for (a.b)(). You haven't really explained that yet. We just discussed the pillar #1 of the strict mode, part 2. For that pillar being the separation between methods and attributes. What's interesting, that part 2, just like part 1, of the strict mode doesn't really make any "revolutionary" changes. It just exposes, emphasizes, and makes consistent, the properties Python language already has. Ain't that cute? Do you spot any issues, Greg? So you *don't* intend to make any semantic changes? I'm still confused. -- Greg ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/G7BMV2FLAZBTSY4ITI7G3DWJPZ6XWQQP/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Wed, Dec 16, 2020 at 10:16:01PM +0300, Paul Sokolovsky wrote: > With all the above in mind, Python3.7, in a strange twist of fate, and > without much ado, has acquired a new operator: the method call, ".()". No it hasn't. That's not a language feature, it is not documented as a language feature, and it could be removed or changed in any release without any deprecation or notice. It is a pure implementation detail, not a language feature. There is no documentation for a "method call" operator, and no interpreter is required to implement either LOAD_ATTR or CALL_METHOD. Byte code instructions are not part of the Python language. Every interpreter is free to decide on whatever byte codes it likes, including no byte codes at all. IronPython uses whatever primitives are offered by the .Net CLR, Jython uses whatever the JVM offers. Nuitka intends to generate C code; Brython generates Javascript. Suppose that I forked CPython 3.7 or later, and made a *single change* to it. When compiling expressions of the form expr.name(...) # arbitrary number of arguments my compiler looks for an environment variable PYOPTIMIZEMODE. If that environment variable is missing, empty or false, the above expression would be compiled using the old LOAD_ATTR and CALL_FUNCTION opcodes. But if it existed and was true, the LOAD_METHOD and CALL_METHOD opcodes would be used instead. Two questions: (1) Is this a legal Python implementation? (2) Apart from some performance differences, what user-visible difference to the behaviour of the code does that environment variable cause? I think that the answers are (1) Yes and (2) None whatsoever. > It's a ternary operator with the following syntax: > > expr.name(args) No it isn't. It is two pseudo-operators, one of which is a binary "attribute lookup" operator: expr.name and the other is an N-ary "call" operator. I say *pseudo* operator, because the meaning of "operator" is documented in Python, and neither `.` not function call `( ... )` is included as actual operators. But the important thing here is that they are two distinct operations: lookup the attribute, and call the attribute. > Now, everything falls into its place: > > An expression like: > > expr.name > > is an "attribute access operator" which gets compiled to LOAD_ATTR > instruction. The language does not specify what, if any, instructions the dot will be compiled to -- or even if it is compiled *at all*. A pure interpreter with no compilation stage would still be a valid Python implementation (although quite slow). Because Python is Turing complete, we could implement a full Python interpreter using a clockwork "Difference Engine" style machine, or a Turing Machine, or by merely running the code in our brain. None of these require the use of a LOAD_ATTR instruction. The parens make **no semantic difference** which is what we have been saying for **days**. ``` >>> dis.dis("(expr.name)()") 1 0 LOAD_NAME0 (expr) 2 LOAD_METHOD 1 (name) 4 CALL_METHOD 0 6 RETURN_VALUE >>> dis.dis("expr.name()") 1 0 LOAD_NAME0 (expr) 2 LOAD_METHOD 1 (name) 4 CALL_METHOD 0 6 RETURN_VALUE ``` The CPython byte-code is identical, parens or no parens, but more importantly, the *semantics* of the two expressions, as described by the language docs, require the two to be identical. And here is a bracketed expression when LOAD_ATTR gets used: ``` >>> dis.dis("(a:=expr.name)()") 1 0 LOAD_NAME0 (expr) 2 LOAD_ATTR1 (name) 4 DUP_TOP 6 STORE_NAME 2 (a) 8 CALL_FUNCTION0 10 RETURN_VALUE ``` which categorically falsifies your prediction that a parenthesized dot expression followed by call will use CALL_METHOD. -- Steve ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/33JD23HHK7VQZGASX7DN6OKVJKIMGXYB/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Thu, 17 Dec 2020 12:46:17 +1300 Greg Ewing wrote: > On 17/12/20 8:16 am, Paul Sokolovsky wrote: > > With all the above in mind, Python3.7, in a strange twist of fate, > > and without much ado, has acquired a new operator: the method call, > > ".()". > > > CPython3.6 and below didn't have ".()" operator, and compiled it as > > "attr access" + "function call", but CPython3.7 got the new > > operator, and compiles it as such (the single operator). > > Um, no. There is no new operator. You're reading far more into the > bytecode change than is actually there. So, here we would need to remember what construes "a good scientific theory". It's something which explain sufficiently wide array of phenomena with sufficiently concise premises, predicts effects of phenomena, and allows to use all that in some way which is "useful". Henceforth, the theory of the call operator was presented. And if we look at the actual code, then "a.b" is compiled into LOAD_ATTR, and "a.b()" into LOAD_METHOD, *exactly* as the theory predicts. It also makes it clear what is missing from the middle-layer (abstract syntax) to capture the needed effect - literally, MethodCall AST node. It also explains what's the meaning (and natural effect) of "(a.b)()". And that explanation agrees with an intuitive meaning of the use of parens for grouping. That meaning being "do stuff in parens before", and indeed, theory tells us, that "(a.b)()" should naturally be implemented by LOAD_ATTR, followed by CALL_FUNCTION. Which is again agrees with how some implementations do that. Given all that, "unlearning" a concept of a method call operator may be not easier than "unlearning" a concept of natural numbers (or real numbers, or transcendental numbers, or complex numbers). Please be my guest. > > So, why CPython3.7+ still compiles "(a.b)()" using LOAD_METHOD. > > Because a.b() and (a.b)() have the same semantics, *by definition*. > That definition has NOT changed. Because they have the same > semantics, there is no need to generate different bytecode for them. By *a* particular definition. The theory is above a particular definition. It explains how "a.b" vs "a.b()" should be compiled, and indeed, they that way. It also explains how "(a.b)()" should be compiled, and the fact that particular implementations "optimize" it doesn't invalidate the theory in any way. And yeah, as soon as we, umm, adjust the definition, the theory gets useful to explain what may need to be changed in the codegeneration. > > But still, are there Python implementations which compile "(a.b)()" > > faithfully, with its baseline semantic meaning? Of course there're. > > E.g., MicroPython-derived Python implementations compile it in the > > full accordance with the theory presented here: > > All that means is that Micropython is missing a potential > optimisation. This is probably a side effect of the way its > parser and code generator work, rather than a conscious decision. Good nailing down! But it's the same for CPython. CPython compiles "(a.b)()" using LOAD_METHOD not because it consciously "optimizes" it, but simply because it's *unable* to represent the difference between "a.b()" and "(a.b)()". More specific explanation: 1. MicroPython compiler is CST (concrete syntax tree) based, so it sees all those parens. (And yes, it has to do silly things to not miss various optimizations, and still misses a lot.) 2. CPython compiler is AST (abstract syntax tree) based, and as the current ASDL definition (https://github.com/python/cpython/blob/master/Parser/Python.asdl) misses the proper MethodCall node, it conflates "a.b()" and "(a.b)()" together. So, the proper way to address the issue would be to add explicit MethodCall node. So far (for Pycopy), I don't dare for such a step, instead having a new "is_parenform" attribute on existing nodes, telling that corresponding node was parens-wrapped in the surface syntax. (And I had it before, as it's needed to e.g. parse generator expressions with a recursive-decent parser.) > Now, it's quite possible to imagine a language in which > a.b() and (a.b)() mean different things. Not only that! It's even possible to imagine Python dialect where "a.b" and "a.b()" would mean *exactly* what they are - first is attribute access, second is method call, no exceptions allowed. But then the question will arise how to call a callable stored in an attributed. So tell me, Greg (first thing which comes to your mind, for as long as it's "a)" or "b)" please) what do you like better: a) (a.b)() syntax b) apply() being resurrected > Does anyone remember > Prothon? (It's a language someone was trying to design a while > back that was similar to Python but based on prototypes > instead of classes.) I don't remember it. Turned out, google barely remembers it, and I had to give it a bunch of clarifying questions and "do you really mean that?" answer before I got to e.g. http://wiki.c2.com/?ProthonLanguage > A feature of
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Thu, Dec 17, 2020 at 8:58 AM Paul Sokolovsky wrote: > Hello, > > On Thu, 17 Dec 2020 00:03:51 +0100 > Marco Sulla wrote: > > > On Wed, 16 Dec 2020 at 20:18, Paul Sokolovsky > > wrote: > > > But still, are there Python implementations which compile "(a.b)()" > > > faithfully, with its baseline semantic meaning? Of course > > > there're. > > > > OK, Paul, why don't you propose a PR and a bug report about it? > > But that's not what this talk is about! It's about a new exciting > (hmm, we'll see) feature which, turned out, was there all this time, > but was overlooked (so, no patches are needed right away). So, I'm > asking fellow Python programmers if they recognize it. If they do, we > can consider how to get more from that feature, and maybe some patches > will be useful. And if they don't, no patches would help. > > I like it. The idea of a 'method call operator' is quite cute, it's an alternate way of thinking about the situation that seems to be self-consistent (at least on the surface). BUT. It's only worth talking about alternate interpretations if there's a reasonable chance that introducing a new way of thinking about a problem will lead to some improvement: either functional enhancement, or better abstractions. Do you have concrete ideas about how treating this language construct as a new operator might end up bringing tangible benefits? Somewhat relatedly, I very much appreciate the simplicity and clean approach that Python takes with objects (experiencing Ruby briefly having written python made this benefit very clear). With python 3, the distinction between a function and a method has been further reduced, and any change that risks moving us away from `obj.meth()` being functionally equivalent to `getattr(obj, 'meth')()` or `getattr(Cls, 'meth')(obj)` would have to have incredibly strong benefits to outweigh the cost of doing so. Steve > > -- > Best regards, > Paul mailto:pmis...@gmail.com > ___ > Python-ideas mailing list -- python-ideas@python.org > To unsubscribe send an email to python-ideas-le...@python.org > https://mail.python.org/mailman3/lists/python-ideas.python.org/ > Message archived at > https://mail.python.org/archives/list/python-ideas@python.org/message/UF72G5Y7FIH4RIXRBDNRSQRJSTVAMQYA/ > Code of Conduct: http://python.org/psf/codeofconduct/ > ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/ZS7JMEBOHIYSMWUQJUOVKSOFDFDWXN5V/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 17/12/20 9:56 pm, Paul Sokolovsky wrote: It's about a new exciting (hmm, we'll see) feature which, turned out, was there all this time, ... I'm asking fellow Python programmers if they recognize it. If they do, we can consider how to get more from that feature, I don't see how we can get any more in this area than we already have, without making (a.b)() have different semantics from a.b() -- which is extremely unlikely to to ever happen. -- Greg ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/F6NCIUACCETE7RY4TA5KNQFBJ5RAWODW/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Thu, 17 Dec 2020 00:03:51 +0100 Marco Sulla wrote: > On Wed, 16 Dec 2020 at 20:18, Paul Sokolovsky > wrote: > > But still, are there Python implementations which compile "(a.b)()" > > faithfully, with its baseline semantic meaning? Of course > > there're. > > OK, Paul, why don't you propose a PR and a bug report about it? But that's not what this talk is about! It's about a new exciting (hmm, we'll see) feature which, turned out, was there all this time, but was overlooked (so, no patches are needed right away). So, I'm asking fellow Python programmers if they recognize it. If they do, we can consider how to get more from that feature, and maybe some patches will be useful. And if they don't, no patches would help. -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/UF72G5Y7FIH4RIXRBDNRSQRJSTVAMQYA/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Thu, Dec 17, 2020 at 10:47 AM Greg Ewing wrote: > A feature of Prothon was that a.b() and t = a.b; t() would do > quite different things (one would pass a self argument and the > other wouldn't). > > I considered that a bad thing. I *like* the fact that in Python > I can use a.b to get a bound method object and call it later, > with the same effect as if I'd called it directly. Ewww. Yes, that is definitely a bad thing. Just look at JavaScript, which has that exact distinction - a.b() will set 'this' to a, but t() would set 'this' to.. well, that depends on a lot of things, but it probably won't be a. JS's "arrow functions" behave somewhat more sanely, but at the expense of being per-instance, so the rules are a bit more complicated for constructing them. But at least you don't have to worry about lifting them out of an object. ChrisA ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/ZQTEPVPW7DFITMVM3VGEEWUE5MC43S26/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 17/12/20 8:16 am, Paul Sokolovsky wrote: With all the above in mind, Python3.7, in a strange twist of fate, and without much ado, has acquired a new operator: the method call, ".()". > CPython3.6 and below didn't have ".()" operator, and compiled it as > "attr access" + "function call", but CPython3.7 got the new operator, > and compiles it as such (the single operator). Um, no. There is no new operator. You're reading far more into the bytecode change than is actually there. So, why CPython3.7+ still compiles "(a.b)()" using LOAD_METHOD. Because a.b() and (a.b)() have the same semantics, *by definition*. That definition has NOT changed. Because they have the same semantics, there is no need to generate different bytecode for them. But still, are there Python implementations which compile "(a.b)()" faithfully, with its baseline semantic meaning? Of course there're. E.g., MicroPython-derived Python implementations compile it in the full accordance with the theory presented here: All that means is that Micropython is missing a potential optimisation. This is probably a side effect of the way its parser and code generator work, rather than a conscious decision. Now, it's quite possible to imagine a language in which a.b() and (a.b)() mean different things. Does anyone remember Prothon? (It's a language someone was trying to design a while back that was similar to Python but based on prototypes instead of classes.) A feature of Prothon was that a.b() and t = a.b; t() would do quite different things (one would pass a self argument and the other wouldn't). I considered that a bad thing. I *like* the fact that in Python I can use a.b to get a bound method object and call it later, with the same effect as if I'd called it directly. I wouldn't want that to change. Fortunately, it never will, because changing it now would break huge amounts of code. -- Greg ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/XG6U6NAWWBNDWPHPM54WUPUVWKKU5IQL/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Wed, 16 Dec 2020 at 20:18, Paul Sokolovsky wrote: > But still, are there Python implementations which compile "(a.b)()" > faithfully, with its baseline semantic meaning? Of course there're. OK, Paul, why don't you propose a PR and a bug report about it? ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/5HIBV5PZAJ4VB65D5YZSIQOIMLLVAPMZ/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Wed, 16 Dec 2020 00:50:27 +1300 Greg Ewing wrote: > On 16/12/20 12:24 am, Paul Sokolovsky wrote: > > > That's good answer, thanks. But... it doesn't correspond to the > > implementation reality. > > Why are we talking about implementation? You said you wanted > to keep to the conceptual level. At that level, there is NO > difference at all. I'm not sure how well I was able to convey, but even in the initial message I tried to ask for an asymptotically coherent theory which would apply across layers and would allow to explain all of the following: 1. Surface syntax, and intuitive-semantics difference between "a.b()" and "(a.b)()". (Can add just "a.b" for completeness.) 2. Deeper (i.e. abstract) syntax difference between the above. 3. Codegeneration difference for the above, or in more formal terms, small-step semantics for the above. I specifically write "asymptotically coherent", because we already know that a number of parts are missing (e.g. level 2 above is completely missing so far), and there can be random cases (mostly on bytecode codegeneration end) which don't fit into theory, and to explain which we'd need to apply to outside means like: "oh, we just didn't think about that" or "oh, that's bytecode optimization". Ok, so here's my theory: Which still starts with setting the stage. Besides those simple operators they teach in school, there're others, less simple. Some are still pretty familiar to programmers. For example, C's ternary conditional "?:" operator. It's indeed usually named "?:", but that doesn't tell us much about its actual syntax: expr1 ? expr2 : expr3 So, it's a ternary operator, unlike common unary or binary ones. And it's not prefix, postfix, or infix. Linguistics has a term for a generalized prefix/suffix/infix concept, so let's call such operators "affix". Python also has ternary conditional operator: expr2 if expr1 else expr3 Which shows: a) operator lexics doesn't have to consist of punctuation, letters work too; b) it has different order of expression comparing to C's version. Despite those striking differences, nobody even gets confused. Which shows human ability to see deeper similarity across surface differences, on which ability we're going to rely in the rest of our discussion. And we're almost there. The only intermediate step to consider is the call operator, "()". It's much older than conditional operator in Python, but always was a special one: expr(args) So, it's binary, and it's also affix, as we can't ignore that closing paren. A note about "binary": a *function* may take multiple arguments. However, a *call operator*, in its abstract syntax, takes just a single 2nd arg, of special type "args" (and syntax of that includes zero or more args, positional and keywords, starred and double-starred at trailing positions). With all the above in mind, Python3.7, in a strange twist of fate, and without much ado, has acquired a new operator: the method call, ".()". It's a ternary operator with the following syntax: expr.name(args) It's an affix operator, with its 3 constituent characters nicely spread around the expression it forms. Now, everything falls into its place: An expression like: expr.name is an "attribute access operator" which gets compiled to LOAD_ATTR instruction. expr() is a "call operator", which gets compied to CALL_FUNCTION and: expr.name() is a "method call operator", which gets compiled into LOAD_METHOD and complementary CALL_METHOD opcodes. CPython3.6 and below didn't have ".()" operator, and compiled it as "attr access" + "function call", but CPython3.7 got the new operator, and compiles it as such (the single operator). The ".()" operator is interesting, because it's compounded from existing operators. It thus can be "sliced" using parenthesis into individual operators. And the meaning of (a.b)() is absolutely clear - it says "first compute 'a.b', and then call it", just the same as "a + (b + c)" says "first compute 'b + c', and then add that to 'a'". So, why CPython3.7+ still compiles "(a.b)()" using LOAD_METHOD. The currently proposed explanation in this thread was "optimization", and let's just agree with it ;-). The real reason is of course different, and it would be nice to discuss it further. But still, are there Python implementations which compile "(a.b)()" faithfully, with its baseline semantic meaning? Of course there're. E.g., MicroPython-derived Python implementations compile it in the full accordance with the theory presented here: obj.meth() (obj.meth)() $ pycopy -v -v objmeth.py [] 00 LOAD_NAME obj (cache=0) 04 LOAD_METHOD meth 07 CALL_METHOD n=0 nkw=0 09 POP_TOP 10 LOAD_NAME obj (cache=0) 14 LOAD_ATTR meth (cache=0) 18 CALL_FUNCTION n=0 nkw=0 20 POP_TOP 21 LOAD_CONST_NONE 22 RETURN_VALUE Discussion? Criticism? Concerns? > > -- > Greg -- Best regards, Paul mailto:pmis...@gmail.com ___
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Tue, 15 Dec 2020 23:28:53 +1100 Steven D'Aprano wrote: > On Tue, Dec 15, 2020 at 01:16:21PM +0300, Paul Sokolovsky wrote: > > > You're now just a step away from the "right answer". Will you make > > it? I did. > > Sorry Paul, but you didn't. > > You fooled yourself by comparing chalk and cheese, and imagining that > because you can eat cheese (change the order of operation by using > parens, which is a semantic difference), you can also eat chalk > (imagine a semantic difference between `obj.method` and > `(obj.method)`). But that's not what I was talking about. I was talking about difference between `obj.method()` and `(obj.method)()`, but you in your recent emails keep reducing that to `obj.method` and `(obj.method)`. Sorry, but you can't just drop those parens at the end, they have the specific meaning (a call). And I initially optimized presentation for number of characters with examples like "a.b()", but perhaps I should have written "a.b(foo, bar, baz)", so the "(foo, bar, baz)" part was big and pronounced and there was no desire to drop it silently. > Your mistake was comparing `(obj.method)()` with `a + (b + c)` when > you should have compared it to `(a + b) + c`. No, the comparison was to show that placing parens *does* already change semantic meaning of operator sequences. The example could only confuse you if you try to compare chalk and cheese, sorry, "+" and method call, directly. They behave in regard to parens *similarly*, but not *exactly*, how you might have thought. > Not every source code > difference has a semantic difference: > > x = 1 > x=1 > x = 1 > x = 1 or None > x = (1) > > all mean the same thing. Putting parens around the final (1) changes > nothing. Yeah, but imagine if parens were put like: x(= 1). That would completely change the meaning. Like, it would become SyntaxError in current Python, but that actually means we could assign a new meaning to it! Indeed, how many proposals to use up that syntax we had here on python-ideas? 1, 2, 3? Zero you say? Oh, I'm sure someone will accept the challenge ;-). For example, I believe we had proposals for syntax like x(a=) already, no? > Let's get away from using round brackets for function call, because > it clashes with the use of round brackets for grouping. I wouldn't say they "clash". They are completely disambiguated syntax-wise. But based on the discussion we have here, it's fair to say that some people get confused seeing parens with different meaning together, for example you keep dropping one of the parens pair ;-). > All we really > need is a *postfix unary operator*. Warm! I'd even say "hot", but I know it won't click, even with the following clarifications: 1. Function call is not a postfix unary operator. It's binary operator. It's syntax is: expr(args) 2. It's not even an infix operator, like for example "+". You can't really ignore that closing paren - without it, the syntax is invalid. So, function call operator, "()" is such a funky operator which is "spread around" the new expression it forms. > x() # the brackets are "postfix unary zero-argument function > call" > > Let's use the factorial operator, "bang" `!` instead. > > obj.attr! > > has to be evaluated from left to right under Python's rules. You > can't apply the factorial operator until you have looked up attr on > obj, and you cannot lookup attr until you have looked up obj. Sounds good, for as long as they're separate operators. But look at the conditional Python operator: foo if bar else baz Getting warmer, no? > So the only possible order of operations is left to right. This is > not meaningful: > > # attr factorial first, then lookup obj, then the dot lookup > obj.(attr!) > > but while this is meaningful, the order of operations is unchanged: > > # lookup obj first, then dot lookup attr, then factorial > (obj.attr)! > > Replace the factorial postfix operator with the `()` call operator, > and the logic remains the same. No necessarily. Some operators are less simple than the others. Let the conditional operator be the witness. > -- > Steve [] -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/HJOGHL6457EGPHNPOHSTYJP4RWNDR63P/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Tue, 15 Dec 2020 at 17:41, Chris Angelico wrote: > I learned BOMDAS - Brackets, O (varies in expansion but always minor > things you don't often see), Multiplication, Division, Addition, > Subtraction. For some reason it's also written BODMAS, which has the > exact same meaning (since multiplication and division have the same > precedence) but is harder to pronounce. PEMDAS uses "parentheses" > instead of "brackets" (so it's probably an American English vs British > English thing), and "exponentiation" in place of the first vowel. This is the most interesting thing in the whole discussion, IMHO. ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/EBTHAXKGD3VZ3VSZIBOULATR23CKUXGI/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Tue, Dec 15, 2020 at 04:15:52PM +, David Mertz wrote: > It feels like a chimp trying to pantomime a philosopher, really. As > someone with a doctorate in philosophy, I feel triggered :-). Quoting "A Fish Called Wanda": Otto: Apes don't read philosophy. Wanda: Yes they do, Otto, they just don't understand it. Although I don't think that Paul is a Nietzsche-quoting ex-CIA hired killer. At least I hope not. In fairness, Paul has a lot of interesting ideas, even if they don't always pan out. But this thread is an excellent example of how *not* to engage and persuade an audience: - long and rambling, slow to get to the point; - expecting the readers to make the same "Aha!" moment you did when you could just explicitly state your observation; - patronising statements that your readers are just a step away from getting the right answer, but will they make it? - repeated hints that you have seen the correct answer and reached enlightenment, without telling the reader what the answer is; - comparisons and analogies that don't work; (under Python semantics, the closest analogy to `(obj.method)()` is not `a+(b+c)` but `(a+b)+c`) Paul, if you are reading this, you are coming across as neuro-atypical. If that is the case, trust me on this, the strategy you are taking in this thread is very unsuccessful as a persuasive and/or teaching tool. Under existing Python semantics, round brackets (parentheses) have a few different meanings, but the relevant one as far as I can tell is grouping, which changes the order that operations are performed. In expressions, I don't think that there are any cases where brackets change the semantics of operations: `(b + c)` remains the plus operator even with the brackets, it just changes the order of operation relative to any surrounding expression. The only counter-example I can think of where brackets changed the semantics of a statement was the old Python 2 `except ...` statement: except A, B, C, D: block except (A, B, C, D): block If I recall correctly, the first catches exceptions A, B and C, and binds the exception to D; the second catches exceptions A, B, C and D and doesn't bind to anything. As you can imagine, this was an extremely error-prone and surprising "Gotcha". In principle, we could give `(obj.method)()` a distinct meaning to the unbracketed form. But such a distinction would be surprising, it would clash with grouping: (obj.method or fallback_function)() and I have no idea what distinct meaning you want to give it, or why. If you are serious about continuing this thread, please get to the point of *what* change in semantics you want to give the bracketed form and *why* you think it would be useful. -- Steve ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/B7FFM2NZO6IERCNXOER5ULVKI7BERXLR/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
I'm going to answer the original question, even though I don't quite understand it: > Using explicit parenthesization to convey aspects of semantic meaning? Absolutely not -- for no other reason that it would break potentially a LOT of code. If there IS some new useful semantics that could be conveyed with a set of brackets, we're going to need to use another character. I still don't get what meaning might be called for, but even commonly used brackets, like [] or {} would be better because I don't think they can be currently used anywhere in which they have literally no meaning, like () does. -CHB On Tue, Dec 15, 2020 at 8:38 AM Chris Angelico wrote: > On Wed, Dec 16, 2020 at 3:16 AM David Mertz wrote: > > > > On Tue, Dec 15, 2020 at 11:22 AM Chris Angelico > wrote: > >> I'm pretty sure most of us learned *in grade school* about BOMDAS or > >> BODMAS or PEMDAS or whatever mnemonic you pick. > > > > I don't think I ever learned such acronyms! I mean, yes I learned about > order of operations in grade school. But never with a mnemonic. > > I learned BOMDAS - Brackets, O (varies in expansion but always minor > things you don't often see), Multiplication, Division, Addition, > Subtraction. For some reason it's also written BODMAS, which has the > exact same meaning (since multiplication and division have the same > precedence) but is harder to pronounce. PEMDAS uses "parentheses" > instead of "brackets" (so it's probably an American English vs British > English thing), and "exponentiation" in place of the first vowel. > > Whichever way you learned it, though, you probably learned a few > quirks of algebraic notation that don't really apply to programming > (such as the fraction bar), but for the most part, you'd have learned > the exact model that most expression evaluators use. > > ("Most" because, as always, there are exceptions, but it's a good > default to start with.) > > ChrisA > ___ > Python-ideas mailing list -- python-ideas@python.org > To unsubscribe send an email to python-ideas-le...@python.org > https://mail.python.org/mailman3/lists/python-ideas.python.org/ > Message archived at > https://mail.python.org/archives/list/python-ideas@python.org/message/7YMTCRDYK5KX3UA26AIQFC6Z5A4CIUIL/ > Code of Conduct: http://python.org/psf/codeofconduct/ > -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/EDAMT7QD4LXWNH3TTRDXLI7SUSSW6SHS/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Wed, Dec 16, 2020 at 3:16 AM David Mertz wrote: > > On Tue, Dec 15, 2020 at 11:22 AM Chris Angelico wrote: >> I'm pretty sure most of us learned *in grade school* about BOMDAS or >> BODMAS or PEMDAS or whatever mnemonic you pick. > > I don't think I ever learned such acronyms! I mean, yes I learned about order > of operations in grade school. But never with a mnemonic. I learned BOMDAS - Brackets, O (varies in expansion but always minor things you don't often see), Multiplication, Division, Addition, Subtraction. For some reason it's also written BODMAS, which has the exact same meaning (since multiplication and division have the same precedence) but is harder to pronounce. PEMDAS uses "parentheses" instead of "brackets" (so it's probably an American English vs British English thing), and "exponentiation" in place of the first vowel. Whichever way you learned it, though, you probably learned a few quirks of algebraic notation that don't really apply to programming (such as the fraction bar), but for the most part, you'd have learned the exact model that most expression evaluators use. ("Most" because, as always, there are exceptions, but it's a good default to start with.) ChrisA ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/7YMTCRDYK5KX3UA26AIQFC6Z5A4CIUIL/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Tue, Dec 15, 2020 at 11:22 AM Chris Angelico wrote: > Seriously, are you actually unaware of this fundamental, or are you > playing dumb to try to make a point? I'm still trying to figure out > your point here. I've already put him in my killfile, but probably unwisely, I still see the follow-ups by you and other people I respect and enjoy reading discussion from. It feels like a chimp trying to pantomime a philosopher, really. As someone with a doctorate in philosophy, I feel triggered :-). > I'm pretty sure most of us learned *in grade school* about BOMDAS or > BODMAS or PEMDAS or whatever mnemonic you pick. I don't think I ever learned such acronyms! I mean, yes I learned about order of operations in grade school. But never with a mnemonic. -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions. ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/BG46FIBVAQWE22N4FTOOYFKG6LE4DMCA/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Tue, Dec 15, 2020 at 02:24:48PM +0300, Paul Sokolovsky wrote: > As I showed right in my first mail, in "a.b()", > "a.b" doesn't get evaluated at all (since CPython3.7). `a.b` still has to be looked up, even with the new fast LOAD_METHOD byte-code. The difference is that it may be able to avoid instantiating a MethodType object, since that would be immediately garbage-collected once the function object it wraps is called. The lookup still has to take place: ``` >>> class Demo: ... def __getattribute__(self, name): ... if name == 'method': ... print("looked up method") ... return super().__getattribute__(name) ... def method(self): ... print("called method") ... >>> obj = Demo() >>> obj.method() looked up method called method ``` If the above example doesn't convince you that you are mistaken, how about this? ``` >>> class Demo2: ... def __getattr__(self, name): ... print("evaluating obj.%s" % name) ... return lambda: print("calling method") ... >>> obj = Demo2() >>> obj.method() evaluating obj.method calling method ``` In this second demonstration, obj.method *doesn't even exist* ahead of time and has to be created dynamically on attribute lookup, before it can be called. -- Steve ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/O4SKHDK67XZGSW7CPBOXH5UH4H4XFIU2/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Tue, Dec 15, 2020 at 01:16:21PM +0300, Paul Sokolovsky wrote: > You're now just a step away from the "right answer". Will you make it? > I did. Sorry Paul, but you didn't. You fooled yourself by comparing chalk and cheese, and imagining that because you can eat cheese (change the order of operation by using parens, which is a semantic difference), you can also eat chalk (imagine a semantic difference between `obj.method` and `(obj.method)`). Your mistake was comparing `(obj.method)()` with `a + (b + c)` when you should have compared it to `(a + b) + c`. Not every source code difference has a semantic difference: x = 1 x=1 x = 1 x = 1 or None x = (1) all mean the same thing. Putting parens around the final (1) changes nothing. Let's get away from using round brackets for function call, because it clashes with the use of round brackets for grouping. All we really need is a *postfix unary operator*. x() # the brackets are "postfix unary zero-argument function call" Let's use the factorial operator, "bang" `!` instead. obj.attr! has to be evaluated from left to right under Python's rules. You can't apply the factorial operator until you have looked up attr on obj, and you cannot lookup attr until you have looked up obj. So the only possible order of operations is left to right. This is not meaningful: # attr factorial first, then lookup obj, then the dot lookup obj.(attr!) but while this is meaningful, the order of operations is unchanged: # lookup obj first, then dot lookup attr, then factorial (obj.attr)! Replace the factorial postfix operator with the `()` call operator, and the logic remains the same. -- Steve ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/MYH6ZCW3RJYEUSTHYXHTLTBX3M72OROV/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Tue, Dec 15, 2020 at 12:49:26PM +0300, Paul Sokolovsky wrote: > > Are you asking for a semantic difference (the two statements do > > something different) or an implementation difference (the two > > statements do the same thing in slightly different ways)? > > I'm asking for semantic difference, it's even in the post title. So far all you have talked about is implementation differences such as whether intermediate results are put on a stack or not, and differences in byte-code from one version of Python to another. > But > the semantic difference in not in "what two statements do", but in > "what two statements mean". Right, so why are you wasting time talking about what they *do*, i.e. whether they put intermediate results on the stack, or in a register? > Difference in "doing" is entailed by > difference in "meaning". And there may be no difference in "doing", > but still difference in "meaning", as the original "1+2+3" vs > "1+(2+3)" example was intended to show. In the case of ints, there is no difference in meaning. For integers, addition is associative, and the order does not matter. So here you *say* you are talking about semantics, but you are actually talking about implementation. With integers, the semantics of all of these are precisely the same: 1 + 2 + 3 (1 + 2) + 3 1 + (2 + 3) 3 + (2 + 1) etc. The order in which you *do* the additions makes no difference to the semantics. > > Implementation differences are fairly boring (to me): > > Right. How implementation points are brought into the discussion is to > show the issue. As mentioned above, the actual progression is the > opposite: first there's semantic meaning, then it's implemented. So, > what's the semantic meaning of "a.b()" that it gets compiled with > LOAD_METHOD bytecode? - Look up the name "a" in the local namespace; - look up the attribute name "b" according to a's MRO, including the use of the descriptor protocol, slots, etc; - call whatever object gets returned. [...] > > For what it is worth, Python 1.5 generates the exact same byte code > > for both expressions; so does Python 3.9. However the byte code for > > 1.5 and for 3.9 are different. > > Right, and the question is what semantic (not implementational!) shift > happened in 3.7 (that's the point when it started to be compiled > differently). Absolutely none. There was a semantic shift, but it was back in Python 2.2 when new style classes and the descriptor protocol were introduced. > > However, the semantics of the two expressions are more or less > > identical in all versions of Python, regardless of the byte-code used. > > That's what I'd like to put under scrutiny. Okay, the major semantic differences include: - in Python 1.5, attribute name look-ups call `__getattr__` if the name is not found in the object's MRO; - in Python 3.9, attribute name look-ups first call `__getattribute__` before checking the MRO and `__getattr__`; - the MRO is calculated differently between 1.5 and 3.9; - in 3.9, the descriptor protocol may be invoked; - descriptors and the descriptor protocol did not exist in 1.5; - there are a few differences in the possible types of `obj.meth` between the versions, e.g. Python 1.5 had both bound and unbound instance methods, while Python 3.9 does not. There may be other differences, but those are the most important ones I can remember. > > (I say "more or less" because there may be subtle differences between > > versions, relating to the descriptor protocol, or lack there of, old > > and new style classes, attribute lookup, and handling of unbound > > methods.) > > Right, and exactly those "subtle differences" is what I'd like to > discuss. See above. > The level of abstraction I'm talking about is where you look not just > at "`(expression)` vs `expression`" but at: > > expression expression vs expression (expression) That is an extremely straight-forward change in execution order. Whether that makes any semantic difference depends on whether the operations involved are associative or not. > Where is an arbitrary operator. As we already concluded, those do > have difference, even considering such a simple operator as "+". > > So... what can we say about the difference between a.b() and (a.b)() > then? There isn't one. Even though the `.` (dot) and `x()` (call) are not actual operators, we can treat them as pseudo-operators. According to Python's precedence rules, the first expression `a.b()` is the same as: - lookup name a - lookup attribute b - call and the second `(a.b)()` is: - lookup name a - lookup attribute b - call which is precisely the same. The parens make no semantic difference. Paul, I think you have fooled yourself by comparing two *different* situations. You compare a use of parens where they change the order of operations: a + b + c a + (b + c) but you should be looking at this: (a + b) + c # parens are redundant
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 16/12/20 12:24 am, Paul Sokolovsky wrote: That's good answer, thanks. But... it doesn't correspond to the implementation reality. Why are we talking about implementation? You said you wanted to keep to the conceptual level. At that level, there is NO difference at all. -- Greg ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/E6LTQGEKPDOLSSMEQBGUTYNEOECFCG3M/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 15/12/20 11:28 pm, Paul Sokolovsky wrote: that table is not complete. For example, "," (comma) is a (context-dependent) operator in Python, yet that table doesn't have explicit entry for it. Unary "*" and "**" are other context-dependent operators. (Unary "@" too.) Those things aren't considered to be operators. The term "operator" has a fairly specific meaning in Python -- it's not just "any punctuation mark". It's true that the operator precedence table won't tell you the precedence of everything in Python -- you need to consult the grammar for the rest. -- Greg ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/GW3UFBDK6FBOKESIZ4KWJDUXUXCEMKH7/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 15/12/20 11:16 pm, Paul Sokolovsky wrote: I would suggest us rising up in abstraction level a bit, and think not in terms of "intermediate variables" but in terms of "intermediate storage locations". The fact that it's a *named* intermediate storage location is important, because it means the programmer can see it, and will expect it to hold a bound method. So the compiler can't optimise away the bound method creation in that case. Well, it could if it could prove that the intermediate value isn't used for anything else subsequently, but that seems like a very rare thing for anyone to do. Why bother naming it if you're only going to call it once and then throw it away? So the compiler only bothers with the most common case. You're now just a step away from the "right answer". Will you make it? I'll be interested to find out what you think the "right" answer is. Or what the actual question is, for that matter -- that's still not entirely clear. -- Greg ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/DEZ5SY2JP2LS6KIYGHB2YN4L26XNRSHR/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Tue, 15 Dec 2020 23:37:59 +1300 Greg Ewing wrote: > On 15/12/20 10:04 pm, Paul Sokolovsky wrote: > > Example 1: > > > > a + b + c vs a + (b + c) > > > > Question 1: > > Do you agree that there's a clear difference between left and right > > expression? > > Yes, because the default order of operations in Python is defined > so that a + b + c is the same as (a + b) + c. > > > Example 2: > > > > a.b() vs (a.b)() > > > > Question 2: > > Do you agree that there's a *similar* difference here as in Example > > 1? > > No, because the default order of operations here already has > a.b evaluated before making the call, so adding the parentheses > changes nothing. That's good answer, thanks. But... it doesn't correspond to the implementation reality. As I showed right in my first mail, in "a.b()", "a.b" doesn't get evaluated at all (since CPython3.7). Instead, something completely different gets evaluated. Ok, not "completely", but "sufficiently" different. We can continue to beat on the side of "it's only a bytecode optimization, there's total disconnection between what happens in the compiled bytecode and the language syntax". But what if not? > -- > Greg -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/CJIZHSV35NIYYOM74VWXK35NUE6ELKUV/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Tue, Dec 15, 2020 at 9:22 PM Paul Sokolovsky wrote: > On Tue, 15 Dec 2020 20:17:37 +1100 > Chris Angelico wrote: > > > On Tue, Dec 15, 2020 at 8:04 PM Paul Sokolovsky > > wrote: > > > So, let's try simple yes/no questions: > > > > > > Example 1: > > > > > > a + b + c vs a + (b + c) > > > > > > Question 1: > > > Do you agree that there's a clear difference between left and right > > > expression? Yes/no. > > > > Yes, there is a difference. > > > > > Example 2: > > > > > > a.b() vs (a.b)() > > > > > > Question 2: > > > Do you agree that there's a *similar* difference here as in Example > > > 1? Yes/no. > > > > No, there is no difference. > > > > > > > > Then of course depending on the outcome of the last question, there > > > would be further questions. Specifically: > > > > > > If yes: How to put a solid formal basis behind the difference in > > > Example 2 (because so far we're just riding on the similarity with > > > Example 1). And how to explain it to wider audience? > > > > > > > Uhh, it's called precedence and associativity? You know that (a + b + > > c) is equivalent to ((a + b) + c), not to (a + (b + c)). Is that > > formal enough? > > Yes. But you answered "no" to the Example 2. What makes you think that > (a + b + c) is not equivalent to (a + (b + c)), but (a.b()) is > equivalent to ((a.b)()), that's what I'm asking. > Precedence and associativity? Since the two operators have the same precedence (in this case it's the same operator twice), order of evaluation is defined by its left-to-right associativity. Seriously, are you actually unaware of this fundamental, or are you playing dumb to try to make a point? I'm still trying to figure out your point here. The parentheses in one example are changing order of evaluation. In the other, they're not. I do not understand why this is even a question. I'm pretty sure most of us learned *in grade school* about BOMDAS or BODMAS or PEMDAS or whatever mnemonic you pick. Or maybe you have to wait till high school to learn that exponentiation is right-to-left associative. Either way, it's not new knowledge to most programmers. I'm done arguing, unless you actually come up with a real argument. ChrisA ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/TIYWNVD2ZKQIZTDYPVAMGNLK4TXV5HX3/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 15/12/20 10:49 pm, Paul Sokolovsky wrote: the question is what semantic (not implementational!) shift happened in 3.7 (that's the point when it started to be compiled differently). There was no semantic shift. The change had *nothing* to do with semantics. It was *purely* an optimisation. I'm not sure what we can say to make this any clearer. I'm suggesting that there's difference between: expression expression vs expression (expression) Which is hopefully hard to disagree with. There is *sometimes* a difference, depending on exactly what the two expressions are, and what is. Then I'm asking, how consistent are we with understanding and appreciating that difference, taking the example of: a.b() vs (a.b)() There is no inconsistency. Note also that: 1 + 2 * 3 is the same as 1 + (2 * 3) because the default order of operations already has * evaluated before +. The same kind of thing is happening with a.b() vs (a.b)(). -- Greg ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/6GH7T3JJXMYT6QFW26IE4UFQTY5Z6XWV/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 15/12/20 10:04 pm, Paul Sokolovsky wrote: Example 1: a + b + c vs a + (b + c) Question 1: Do you agree that there's a clear difference between left and right expression? Yes, because the default order of operations in Python is defined so that a + b + c is the same as (a + b) + c. Example 2: a.b() vs (a.b)() Question 2: Do you agree that there's a *similar* difference here as in Example 1? No, because the default order of operations here already has a.b evaluated before making the call, so adding the parentheses changes nothing. -- Greg ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/BLVN4Y464MZKICZDWFY4MX7BVMATWKJN/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On 13/12/2020 22:09, Paul Sokolovsky wrote: Thanks for hanging with me so far, we're getting to the crux of the question: Do you think there can be difference between the following two expressions: obj.meth() (obj.meth)() ? No. The value of an expression in parentheses is the value of the expression inside the parentheses, and in this case does not affect the order of evaluation. python3.6 -m dis meth_call.py python3.7 -m dis meth_call.py Then, to try to explain the difference at the suitable level of abstraction. If that doesn't provide enough differentiation, it might be helpful to add the 3rd line: t = obj.meth; t() And run all 3 lines thru CPython3.7, and see if the pattern is now visible, and a distortion in the pattern too. What would be the explanation for all that? The explanation is an optimisation introduced in 3.7 that the use of an intermediate variable prevents. The compiler applies it when it can see the only use of the attribute is an immediately following call. Having burrowed into the implementation, I'm certain it tries hard to be indistinguishable from the unoptimised implementation (and succeeds I think), even to the point of failing in the same way when that is the programmed outcome. LOAD_METHOD goes far enough down the execution of LOAD_ATTR to be sure the bound object would be a types.MethodType containing a pair of pointers that CALL_FUNCTION would have to unpack, and pushes the pointers on the stack instead of creating the new object. Otherwise it completes LOAD_ATTR and pushes a bound object and a NULL, which is what CALL_METHOD uses to decide which case it is dealing with. The meaning of the code is what it does detectably in Python, not what it compiles to (for some definition of "detectably" that doesn't include disassembly). Jeff Allen ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/GUMGCGGBUR73DKEWOOOYLMLO7QBQVLQZ/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Tue, 15 Dec 2020 20:18:11 +1100 Chris Angelico wrote: > On Tue, Dec 15, 2020 at 8:08 PM Paul Sokolovsky > wrote: > > > > Hello, > > > > On Mon, 14 Dec 2020 02:17:52 -0500 > > David Mertz wrote: > > > > > On Sun, Dec 13, 2020, 5:11 PM Paul Sokolovsky d > > > > > > > a + b + c vs a + (b + c) > > > > > > > > Here, there's even no guarantee of the same result, if we have > > > > user objects with weirdly overloaded __add__(). > > > > > > > > > > 0.1 + 0.2 + 0.3 != 0.1 + (0.2 + 0.3) > > > > > > Right, thanks. But the original question was about somewhat > > different matter: if you agree that there's difference between "a + > > b + c" vs "a + (b + c)", do you agree that there's a similar in > > nature difference with "a.b()" vs "(a.b)()"? If no, then why? If > > yes, then how to explain it better? (e.g. to Python novices). > > > > https://docs.python.org/3/reference/expressions.html#operator-precedence No worries, that table is not complete. For example, "," (comma) is a (context-dependent) operator in Python, yet that table doesn't have explicit entry for it. Unary "*" and "**" are other context-dependent operators. (Unary "@" too.) > > ChrisA -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/344LG23UBZWOQMQTDEHXEZSUNGSO3ETO/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Tue, 15 Dec 2020 20:17:37 +1100 Chris Angelico wrote: > On Tue, Dec 15, 2020 at 8:04 PM Paul Sokolovsky > wrote: > > So, let's try simple yes/no questions: > > > > Example 1: > > > > a + b + c vs a + (b + c) > > > > Question 1: > > Do you agree that there's a clear difference between left and right > > expression? Yes/no. > > Yes, there is a difference. > > > Example 2: > > > > a.b() vs (a.b)() > > > > Question 2: > > Do you agree that there's a *similar* difference here as in Example > > 1? Yes/no. > > No, there is no difference. > > > > > Then of course depending on the outcome of the last question, there > > would be further questions. Specifically: > > > > If yes: How to put a solid formal basis behind the difference in > > Example 2 (because so far we're just riding on the similarity with > > Example 1). And how to explain it to wider audience? > > > > Uhh, it's called precedence and associativity? You know that (a + b + > c) is equivalent to ((a + b) + c), not to (a + (b + c)). Is that > formal enough? Yes. But you answered "no" to the Example 2. What makes you think that (a + b + c) is not equivalent to (a + (b + c)), but (a.b()) is equivalent to ((a.b)()), that's what I'm asking. > > ChrisA > -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/XXTXKADGXRDEEUSOHLQQ5SDWMOBIWN3P/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Tue, 15 Dec 2020 08:25:25 + Jeff Allen wrote: > On 13/12/2020 22:09, Paul Sokolovsky wrote: > > Thanks for hanging with me so far, we're getting to the crux of the > > question: > > > > Do you think there can be difference between the following two > > expressions: > > > > obj.meth() > > (obj.meth)() > > > > ? > > No. The value of an expression in parentheses is the value of the > expression inside the parentheses, and in this case does not affect > the order of evaluation. You're on the right track. (Well, I mean you're on the same track as me.) So, what's the order of evaluation and what's being evaluated at all? > > python3.6 -m dis meth_call.py > > python3.7 -m dis meth_call.py > > > > Then, to try to explain the difference at the suitable level of > > abstraction. If that doesn't provide enough differentiation, it > > might be helpful to add the 3rd line: > > > > t = obj.meth; t() > > > > And run all 3 lines thru CPython3.7, and see if the pattern is now > > visible, and a distortion in the pattern too. > > > > What would be the explanation for all that? > > The explanation is an optimisation introduced in 3.7 that the use of > an intermediate variable prevents. Right. But I would suggest us rising up in abstraction level a bit, and think not in terms of "intermediate variables" but in terms of "intermediate storage locations". More details in my today's reply to Chris Angelico. > The compiler applies it when it > can see the only use of the attribute is an immediately following > call. You're now just a step away from the "right answer". Will you make it? I did. And sorry, the whole point of the discussion if to see if the whole path, each step on it, and the final answer is as unavoidable as I now imagine them to be, so I can't push you towards it ;-). > Having burrowed into the implementation, Great! As I mentioned in the other replies, I brought implementation matters (disassembly) to represent the matter better. But the proper way is to start with semantics, and then consider how to implement it (and those considerations can have feedback effect on desired semantics too of course). So, regardless of whether it was done like that or not in that case (when LOAD_METHOD was introduced), let's think what semantics [could have] lead to LOAD_METHOD vs LOAD_ATTR implementation? [] > > Jeff Allen > -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/GHPXATZX5NHYH6EATP45BO7CFLLOSMBT/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Tue, Dec 15, 2020 at 8:49 PM Paul Sokolovsky wrote: > Right, and the question is what semantic (not implementational!) shift > happened in 3.7 (that's the point when it started to be compiled > differently). Have you read the release notes? https://docs.python.org/3/whatsnew/3.7.html#optimizations Method calls are now up to 20% faster due to the bytecode changes which avoid creating bound method instances. (Contributed by Yury Selivanov and INADA Naoki in bpo-26110.) It is an *optimization*. There are NO semantic differences, other than the ones you're artificially creating in order to probe this. (I don't consider "the output of dis.dis()" to be a semantic difference.) Why do you keep bringing up irrelevant questions that involve order of operations? The opcode you're asking about is *just* an optimization for "look up this method, then immediately call it" that avoids the construction of a temporary bound method object. The parentheses are a COMPLETE red herring here. What is your point? ChrisA ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/FA34CSD4R3R77GRBUFMHCQE4KBO7GY3M/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Tue, Dec 15, 2020 at 12:04:44PM +0300, Paul Sokolovsky wrote: > I certainly agree. But the level at which I'm trying to discuss this > matter is more "abstract interpretation"'ish. For example, "+" is a > binary operator, you can't calculate "a + b + c" in one step. There're > 2 "+", and thus 2 steps. And an intermediate result should be "stored > somewhere". In different computation models that "somewhere" would be > different, e.g. in the stack machine model, intermediate result would be > stored in a location on the value stack, and in the register machine > model - in ("temporary") register. But from abstract interpretation > PoV, all those means of storage are equivalent: a named user variable, > a stack location, a temporary variable. (They have differences beyond > the "storage" aspect, sure.) But they aren't equivalent: by definition, a named user variable has a user-visible side-effect, while other forms of storage may not: in high-level languages, stack locations, registers, and temporary variables (in a non-user visible namespace) have no visible side- effects. So this is a critical distinction that you are not making: - there are computations where such intermediate results are visible to the user; - and there are other computations where such intermediate results are not visible to the user. Those two classes are not equivalent, except approximately. > So, let's try simple yes/no questions: > > Example 1: > > a + b + c vs a + (b + c) > > Question 1: > Do you agree that there's a clear difference between left and right > expression? Yes/no. Are we talking about Python? Then yes, there is a clear difference. In the first example, `a + b + c`, execution proceeds left to right: `a + b` first, then the result of that has c added on the right. The second example changes the order of operations: `b + c` is computed first, then a is added on the left. > Example 2: > > a.b() vs (a.b)() > > Question 2: > Do you agree that there's a *similar* difference here as in Example 1? > Yes/no. If we are still talking about Python, then no, there is no difference between the two. In the first example, the name "a" is looked up, then the attribute "b", and then the result of that is called. In the second example, the brackets have no effect: first the name "a" is looked up, then the attribute "b", then the result of that is called. In this case, the brackets do not change the order of operation. It is like comparing `a + b + c` versus `(a + b) + c`. Or for that matter: a + b + c versus (((a) + (b + (((c)) You can add all the redundant parentheses you like without changing the order of operation. Does this conversation have a point? You keep dropping hints that you want to introduce a semantic difference between `obj.meth` and `(obj.meth)`. Care to tell us what that difference is supposed to be? -- Steve ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/632ZWF5Y3ZONF55NCIT6WDE66TR6HETH/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Mon, 14 Dec 2020 19:39:27 +1100 Steven D'Aprano wrote: > On Mon, Dec 14, 2020 at 01:09:56AM +0300, Paul Sokolovsky wrote: > > > Do you think there can be difference between the following two > > expressions: > > > > obj.meth() > > (obj.meth)() > > > > ? > > Okay, I'll bite. > > Of course there is a difference: the first statement is ten > characters long, the second is 12 characters long. Fair enough. > Are you asking for a semantic difference (the two statements do > something different) or an implementation difference (the two > statements do the same thing in slightly different ways)? I'm asking for semantic difference, it's even in the post title. But the semantic difference in not in "what two statements do", but in "what two statements mean". Difference in "doing" is entailed by difference in "meaning". And there may be no difference in "doing", but still difference in "meaning", as the original "1+2+3" vs "1+(2+3)" example was intended to show. > Implementation differences are fairly boring (to me): Right. How implementation points are brought into the discussion is to show the issue. As mentioned above, the actual progression is the opposite: first there's semantic meaning, then it's implemented. So, what's the semantic meaning of "a.b()" that it gets compiled with LOAD_METHOD bytecode? > it might happen > to be that some Python interpreters happen to compile the first > statement into a slightly different set of byte codes to the second. > I don't care too much about that, unless there are large performance > (speed or memory) differences. > > For what it is worth, Python 1.5 generates the exact same byte code > for both expressions; so does Python 3.9. However the byte code for > 1.5 and for 3.9 are different. Right, and the question is what semantic (not implementational!) shift happened in 3.7 (that's the point when it started to be compiled differently). > However, the semantics of the two expressions are more or less > identical in all versions of Python, regardless of the byte-code used. That's what I'd like to put under scrutiny. > (I say "more or less" because there may be subtle differences between > versions, relating to the descriptor protocol, or lack there of, old > and new style classes, attribute lookup, and handling of unbound > methods.) Right, and exactly those "subtle differences" is what I'd like to discuss. I'd like to start however with more of abstract model of difference meaning, but afterwards, if the common ground is found, it would be interesting to check specific not-exactly-on-surface Python features which you list. [] > > python3.6 -m dis meth_call.py > > python3.7 -m dis meth_call.py > > > > Then, to try to explain the difference at the suitable level of > > abstraction. > > At a suitable level of abstraction, there is no difference. The > suitable level of abstraction is at the level of the Python execution > model, where `(expression)` and `expression` mean the same, where the > brackets are used for grouping. The level of abstraction I'm talking about is where you look not just at "`(expression)` vs `expression`" but at: expression expression vs expression (expression) Where is an arbitrary operator. As we already concluded, those do have difference, even considering such a simple operator as "+". So... what can we say about the difference between a.b() and (a.b)() then? > > it might > > be helpful to add the 3rd line: > > > > t = obj.meth; t() > > That clearly has different semantics from the first two: it has the > side-effect of binding a value to the name t. But that's yet another good argument to introduce block-level scoping to Python (in addition to already stated great arguments), because then, (a.b)() will be *exactly* equivalent to (inside a function): if 1: const t = a.b t() This neither gets affected by the surrounding environment (all variables introduced are new, regardless of their names), nor affects it (all variables are block-local, and not visible outside the block). > I'm not sure where you think this question is going to lead us. > Wherever it is, I wish you would get to the point. I'm sorry if this looks like a quiz, that's not the intention. But I really would like to see if other people can spot in this stuff what I spotted (after pondering about it), and I don't want to bias you in any way by jumping to my "conclusions". I do believe we'll get there, but then I don't want to be biased myself either. That's why it's step-by-step process, and I appreciate the people here are willing to walk it. > Are you suggesting that we give a *semantic* difference to: > > ( expression ) > > compared to the unbracketed `expression`? Hopefully, that was answered above. To end the post with the summary, I'm suggesting that there's difference between: expression expression vs expression (expression) Which is hopefully hard to disagree with. Then I'm asking, how
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Tue, Dec 15, 2020 at 8:08 PM Paul Sokolovsky wrote: > > Hello, > > On Mon, 14 Dec 2020 02:17:52 -0500 > David Mertz wrote: > > > On Sun, Dec 13, 2020, 5:11 PM Paul Sokolovsky d > > > > > a + b + c vs a + (b + c) > > > > > > Here, there's even no guarantee of the same result, if we have user > > > objects with weirdly overloaded __add__(). > > > > > > > 0.1 + 0.2 + 0.3 != 0.1 + (0.2 + 0.3) > > > Right, thanks. But the original question was about somewhat different > matter: if you agree that there's difference between "a + b + c" vs "a + > (b + c)", do you agree that there's a similar in nature difference with > "a.b()" vs "(a.b)()"? If no, then why? If yes, then how to explain it > better? (e.g. to Python novices). > https://docs.python.org/3/reference/expressions.html#operator-precedence ChrisA ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/FXGAW2QMQN6S46WV7522KOAXOLVU2G5D/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Tue, Dec 15, 2020 at 8:04 PM Paul Sokolovsky wrote: > So, let's try simple yes/no questions: > > Example 1: > > a + b + c vs a + (b + c) > > Question 1: > Do you agree that there's a clear difference between left and right > expression? Yes/no. Yes, there is a difference. > Example 2: > > a.b() vs (a.b)() > > Question 2: > Do you agree that there's a *similar* difference here as in Example 1? > Yes/no. No, there is no difference. > > Then of course depending on the outcome of the last question, there > would be further questions. Specifically: > > If yes: How to put a solid formal basis behind the difference in > Example 2 (because so far we're just riding on the similarity with > Example 1). And how to explain it to wider audience? > Uhh, it's called precedence and associativity? You know that (a + b + c) is equivalent to ((a + b) + c), not to (a + (b + c)). Is that formal enough? ChrisA ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/D4C3A3AHHPASX4BWVYXJ4BQIKSU7K3YY/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Mon, 14 Dec 2020 02:17:52 -0500 David Mertz wrote: > On Sun, Dec 13, 2020, 5:11 PM Paul Sokolovsky d > > > a + b + c vs a + (b + c) > > > > Here, there's even no guarantee of the same result, if we have user > > objects with weirdly overloaded __add__(). > > > > 0.1 + 0.2 + 0.3 != 0.1 + (0.2 + 0.3) Right, thanks. But the original question was about somewhat different matter: if you agree that there's difference between "a + b + c" vs "a + (b + c)", do you agree that there's a similar in nature difference with "a.b()" vs "(a.b)()"? If no, then why? If yes, then how to explain it better? (e.g. to Python novices). -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/7DUK4HN7ACH2W6FOUP3KQEINSNJBXELD/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Mon, 14 Dec 2020 18:05:07 +1100 Chris Angelico wrote: > On Mon, Dec 14, 2020 at 5:57 PM Paul Sokolovsky > wrote: > > > > But that's what the question was about, and why there was the intro! > > Let's please go over it again. Do you agree with the following: > > > > a + (b + c) <=> t = b + c; a + t > > > > ? > > > > Where "<=>" is the equivalence operator. I do hope you agree, > > because it's both basis for evaluation implementation and for > > refactoring rules, and the latter is especially important for > > line-oriented language like Python, where wrapping expression > > across lines requires explicit syntactic markers, which some people > > consider ugly, so there should be clear rules for splitting long > > expressions which don't affect there semantic. > > It really depends on what you mean by "equivalent". For instance, I'm > sure YOU will agree that they have the semantic difference of causing > an assignment to the name 't'. I certainly agree. But the level at which I'm trying to discuss this matter is more "abstract interpretation"'ish. For example, "+" is a binary operator, you can't calculate "a + b + c" in one step. There're 2 "+", and thus 2 steps. And an intermediate result should be "stored somewhere". In different computation models that "somewhere" would be different, e.g. in the stack machine model, intermediate result would be stored in a location on the value stack, and in the register machine model - in ("temporary") register. But from abstract interpretation PoV, all those means of storage are equivalent: a named user variable, a stack location, a temporary variable. (They have differences beyond the "storage" aspect, sure.) > Additionally, Python will evaluate a > before b and c in the first example, but must evaluate b and c, add > them together, and only after that evaluate a. So, no, they aren't > entirely equivalent. Obviously, in many situations, the programmer > will know what's functionally equivalent, but the interpreter can't. > > Clarify what you mean by equivalence and I will be able to tell you > whether I agree or not. (It's okay if your definition of equivalent > can't actually be described in terms of actual Python code, just as > long as you can explain which differences matter and which don't.) So, let's try simple yes/no questions: Example 1: a + b + c vs a + (b + c) Question 1: Do you agree that there's a clear difference between left and right expression? Yes/no. Example 2: a.b() vs (a.b)() Question 2: Do you agree that there's a *similar* difference here as in Example 1? Yes/no. Then of course depending on the outcome of the last question, there would be further questions. Specifically: If yes: How to put a solid formal basis behind the difference in Example 2 (because so far we're just riding on the similarity with Example 1). And how to explain it to wider audience? If no: What is the explanation of such a striking distinction in treatment of Example 1 vs Example 2? > > ChrisA [] -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/IKF27IGMHFCH7OBE4XJX54ZSEKGQ347U/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Mon, Dec 14, 2020 at 01:09:56AM +0300, Paul Sokolovsky wrote: > Do you think there can be difference between the following two > expressions: > > obj.meth() > (obj.meth)() > > ? Okay, I'll bite. Of course there is a difference: the first statement is ten characters long, the second is 12 characters long. Are you asking for a semantic difference (the two statements do something different) or an implementation difference (the two statements do the same thing in slightly different ways)? Implementation differences are fairly boring (to me): it might happen to be that some Python interpreters happen to compile the first statement into a slightly different set of byte codes to the second. I don't care too much about that, unless there are large performance (speed or memory) differences. For what it is worth, Python 1.5 generates the exact same byte code for both expressions; so does Python 3.9. However the byte code for 1.5 and for 3.9 are different. However, the semantics of the two expressions are more or less identical in all versions of Python, regardless of the byte-code used. (I say "more or less" because there may be subtle differences between versions, relating to the descriptor protocol, or lack there of, old and new style classes, attribute lookup, and handling of unbound methods.) [...] > The question is definitely with a trick (why else there would be the > intro), and first answer which comes to mind might not be the right > one. As a hint, to try to get a grounded answer to that question, it > would be useful to look at the difference in disassembly of the above > code in CPython3.6 vs CPython3.7 (or later): > > python3.6 -m dis meth_call.py > python3.7 -m dis meth_call.py > > Then, to try to explain the difference at the suitable level of > abstraction. At a suitable level of abstraction, there is no difference. The suitable level of abstraction is at the level of the Python execution model, where `(expression)` and `expression` mean the same, where the brackets are used for grouping. > it might > be helpful to add the 3rd line: > > t = obj.meth; t() That clearly has different semantics from the first two: it has the side-effect of binding a value to the name t. I'm not sure where you think this question is going to lead us. Wherever it is, I wish you would get to the point. Are you suggesting that we give a *semantic* difference to: ( expression ) compared to the unbracketed `expression`? -- Steve ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/P2AGWYTH7FYCKFCGOVV5SLL2SKOMUILR/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Sun, Dec 13, 2020, 5:11 PM Paul Sokolovsky d > a + b + c vs a + (b + c) > > Here, there's even no guarantee of the same result, if we have user > objects with weirdly overloaded __add__(). > 0.1 + 0.2 + 0.3 != 0.1 + (0.2 + 0.3) > ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/F7NE7HPSKZUHTYLDM7OTZBUYHCDZDD7Y/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Mon, Dec 14, 2020 at 5:57 PM Paul Sokolovsky wrote: > > But that's what the question was about, and why there was the intro! > Let's please go over it again. Do you agree with the following: > > a + (b + c) <=> t = b + c; a + t > > ? > > Where "<=>" is the equivalence operator. I do hope you agree, because > it's both basis for evaluation implementation and for refactoring > rules, and the latter is especially important for line-oriented > language like Python, where wrapping expression across lines requires > explicit syntactic markers, which some people consider ugly, so there > should be clear rules for splitting long expressions which don't affect > there semantic. It really depends on what you mean by "equivalent". For instance, I'm sure YOU will agree that they have the semantic difference of causing an assignment to the name 't'. Additionally, Python will evaluate a before b and c in the first example, but must evaluate b and c, add them together, and only after that evaluate a. So, no, they aren't entirely equivalent. Obviously, in many situations, the programmer will know what's functionally equivalent, but the interpreter can't. Clarify what you mean by equivalence and I will be able to tell you whether I agree or not. (It's okay if your definition of equivalent can't actually be described in terms of actual Python code, just as long as you can explain which differences matter and which don't.) ChrisA ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/AV6WOKO6NFIDI63FMTVMEF7UOEFHI7LH/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
Hello, On Mon, 14 Dec 2020 09:37:42 +1100 Chris Angelico wrote: > > 2 8 LOAD_NAME0 (obj) > > 10 LOAD_METHOD 1 (meth) ... > > > > 3 16 LOAD_NAME0 (obj) > > 18 LOAD_ATTR1 (meth) ... > Creating bound method objects can be expensive. Python has a history > of noticing ways to improve performance without changing semantics, > and implementing them. Details here: > > https://docs.python.org/3/library/dis.html#opcode-LOAD_METHOD Thanks for the response. And I know all that. LOAD_METHOD/CALL_METHOD was there in MicroPython right from the start. Like, the very first commit to the project in 2013 already had it: https://github.com/micropython/micropython/commit/429d71943d6b94c7dc3c40a39ff1a09742c77dc2#diff-ce2e872144bcba2e9a4c2e84c748fa6f3bba4f08406b27e69a88415944bf0c0eR108 > If you force the bound method object to be created (by putting it in a > variable), But that's what the question was about, and why there was the intro! Let's please go over it again. Do you agree with the following: a + (b + c) <=> t = b + c; a + t ? Where "<=>" is the equivalence operator. I do hope you agree, because it's both basis for evaluation implementation and for refactoring rules, and the latter is especially important for line-oriented language like Python, where wrapping expression across lines requires explicit syntactic markers, which some people consider ugly, so there should be clear rules for splitting long expressions which don't affect there semantic. So ok, if you agree with the above, do you agree with the below: (a.b)() <=> t = a.b; t() ? And I really wonder what depth of non-monotonic logic we can reach on trying to disagree with the above ;-). Python does have cases where syntactic refactoring is not possible. The most infamous example is super() (Which reminds that, when args to it were made optional, it would have been much better to make it just "super.", there would be much less desire to "refactor" it). But the more such places a language has, the less regular, hard to learn, reason about, and optimize the language is. And poorer designed too. So, any language with aspiration to not be called words should avoid such cases. And then again, what can we tell about: "(a.b)() <=> t = a.b; t()" [] > This is why lots of us are unimpressed by your strict mode - CPython > is perfectly capable of optimizing the common cases without changing > the semantics, so why change the semantics? :) But please remember that you're talking with someone who takes LOAD_METHOD for granted, from 2013. And who takes inline caches for granted from 2015. So, what what would be the reason to take all that for granted and still proceeding with the strict mode? Oh, the reasons are obvious: a) it's the natural extension of the above; b) it allows to reach much deeper (straight to the machine code, again), and by much cheaper means (machine code for call will contain the same as in C, no 10x times more code in guards). For comparison, CPython added LOAD_METHOD in 2016. And lookup caching started to be added in 2019. And it took almost 1.5 years to extend caching from a single opcode to 2nd one. 1.5 years, Chris! commit 91234a16367b56ca03ee289f7c03a34d4cfec4c8 Date: Mon Jun 3 21:30:58 2019 +0900 bpo-26219: per opcode cache for LOAD_GLOBAL (GH-12884) commit 109826c8508dd02e06ae0f1784f1d202495a8680 Date: Tue Oct 20 06:22:44 2020 +0100 bpo-42093: Add opcode cache for LOAD_ATTR (GH-22803) And 3rd one, LOAD_NAME, isn't covered, and it's easy to see why: instead of using best-practice uniform inline caches, desired-to-be-better Python semantics spawned the following monsters: co->co_opcache_map = (unsigned char *)PyMem_Calloc(co_size, 1); typedef struct { PyObject *ptr; /* Cached pointer (borrowed reference) */ uint64_t globals_ver; /* ma_version of global dict */ uint64_t builtins_ver; /* ma_version of builtin dict */ } _PyOpcache_LoadGlobal; All that stuff sits in your L1 cache, thrashes something else in and out all the time, and makes it all still slow, slow, slow. "Perfectly capable" you say? Heh. -- Best regards, Paul mailto:pmis...@gmail.com ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/NXDYVHKKMBL5EVXE4B5PORAPV3XHXRGW/ Code of Conduct: http://python.org/psf/codeofconduct/
[Python-ideas] Re: Using explicit parenthesization to convey aspects of semantic meaning?
On Mon, Dec 14, 2020 at 9:11 AM Paul Sokolovsky wrote: > What would be the explanation for all that? > > > For reference, the disassembly of the 3 lines with CPython3.7 is > provided: > > 1 0 LOAD_NAME0 (obj) > 2 LOAD_METHOD 1 (meth) > 4 CALL_METHOD 0 > 6 POP_TOP > > 2 8 LOAD_NAME0 (obj) > 10 LOAD_METHOD 1 (meth) > 12 CALL_METHOD 0 > 14 POP_TOP > > 3 16 LOAD_NAME0 (obj) > 18 LOAD_ATTR1 (meth) > 20 STORE_NAME 2 (t) > 22 LOAD_NAME2 (t) > 24 CALL_FUNCTION0 > 26 POP_TOP > ... Creating bound method objects can be expensive. Python has a history of noticing ways to improve performance without changing semantics, and implementing them. Details here: https://docs.python.org/3/library/dis.html#opcode-LOAD_METHOD If you force the bound method object to be created (by putting it in a variable), the semantics should be the same, but performance will be lower. Consider: rosuav@sikorsky:~$ python3.10 -c 'import dis; dis.dis(lambda obj: (obj.meth,)[0]())' 1 0 LOAD_FAST0 (obj) 2 LOAD_ATTR0 (meth) 4 BUILD_TUPLE 1 6 LOAD_CONST 1 (0) 8 BINARY_SUBSCR 10 CALL_FUNCTION0 12 RETURN_VALUE rosuav@sikorsky:~$ python3.10 -c 'import dis; dis.dis(lambda obj: (obj.meth(),)[0])' 1 0 LOAD_FAST0 (obj) 2 LOAD_METHOD 0 (meth) 4 CALL_METHOD 0 6 BUILD_TUPLE 1 8 LOAD_CONST 1 (0) 10 BINARY_SUBSCR 12 RETURN_VALUE rosuav@sikorsky:~$ python3.10 -m timeit -s 'x, f = 142857, lambda obj: (obj.bit_length(),)[0]' 'f(x)' 200 loops, best of 5: 101 nsec per loop rosuav@sikorsky:~$ python3.10 -m timeit -s 'x, f = 142857, lambda obj: (obj.bit_length,)[0]()' 'f(x)' 200 loops, best of 5: 124 nsec per loop rosuav@sikorsky:~$ python3.6 -m timeit -s 'x, f = 142857, lambda obj: (obj.bit_length(),)[0]' 'f(x)' 1000 loops, best of 3: 0.124 usec per loop rosuav@sikorsky:~$ python3.6 -m timeit -s 'x, f = 142857, lambda obj: (obj.bit_length,)[0]()' 'f(x)' 1000 loops, best of 3: 0.123 usec per loop Measurable improvement in 3.10, indistinguishable in 3.6. This is why lots of us are unimpressed by your strict mode - CPython is perfectly capable of optimizing the common cases without changing the semantics, so why change the semantics? :) ChrisA ___ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/5KBT5DI6NKKLS4QEDAYNIHKOFBGL7PFP/ Code of Conduct: http://python.org/psf/codeofconduct/