I will think about more interesting examples weekend, though my time will probably be more family focused so I might not get any messages of appreciable length out until sometime next week.
I do get what you are saying on the const vs immutability thing, but I was thinking that because python works the way it does they would need to be related. If I have a module level variable, that was intended to be a constant, like below, it would need both. SAFETY_THRESHOLD = 100 def func(sys_temp): if sys_temp > SAFETY_THRESHOLD: turn_on_fan() Would not want someone outside the module (or someone adding new code years later in the module) redefining SAFETY_THRESHOLD. Now in something like C, a const on SAFETY_THRESHOLD would be fine without interior mutability, because a number is just a type, and thus cant be reassigned. In python it is a class instance with methods and such (substitute in string aka char or something). However in this case __setself__ (__assign__) would be plenty as the instances are immutable already. I assumed that there would be no __setself__ on an int, so to get the same sort of const like behavior (const + fundamental type) one would need to define MyInt, and make it interior immutable like python str or int, and also const via __setself__.
Now its possible that I am being silly and not thinking through things correctly again, I keep stealing moments here or there to work on this project. If so I am happy to have someone correct me.
Typing this out though does make me think of an interesting idea. If there was something like __getself__ in addition to __setself__, you could implement things like MyInt. __getself__ would look something like:
class MyInt: def __init__(self, value): self.value = value def __getself__(self): return self.value def __setself__(self, value): raise ValueError("Cant set MyInt") x = MyInt(2) print(x) -> 2 type(x) -> MyInt
Now I have not really thought through how this would work, if it could work, and how setself and getself may play with each other in execution, and it that would introduce even more weird behavior. It was just something that occurred to me when typing above. If they did play well together though, you could do something interesting like creating a variable that tracked its history such as:
class HistoricVar: def __init__(self, initval): self._value self.history =  def __getself__(self): return self._value def __setself__(self, value): self.history.append(self._value) self._value = value def go_back_n(self, n): for i in range(n): self.history.pop() self._value = self.history[-1]
x = HistoricVar(1) print(x) -> 1 x = "hello world' print(x) -> "hello world" set_var_to_empty_dict(x) print(x) -> Dict x = "14" getattr(x, 'go_back_n')(2) # <- might need special handling for getattr to account for getself print(x) -> "hello world" try: for i in range(10) x = x + i if x > 17: raise ValueError('x cant be larger than 17') except: getattr(x, 'go_back_n')(1)
Caveat to anyone not paying attention ^ that code has not been tried in any interpreter and the changes have been not made to support it anywhere, if it is at all possible.
Now I know these are silly examples that could be done in other ways, but they are musings that fit into the scope of typing out in an email. Like I said I will see if more interesting things come to me over the weekend. If anything this thread is a fun and interesting thought experiment and a useful learning experiment for me to better understand the workings of python, and for that I thank you for your feed back and attention. Nate
On Fri, Jun 21, 2019 at 6:24 PM Andrew Barnert firstname.lastname@example.org wrote:
On Jun 21, 2019, at 14:36, nate lust email@example.com wrote:
I think it is only surprising because it is not something that is familiar.
Part of the problem may be that in your toy example, there really is no reason to overload assignment, so it’s surprising even after you get what it’s doing. If you worked out a more realistic example and demonstrated that with your patch, it might feel a lot less disconcerting.
I feel even less convinced by your other potential uses, but again, if one of them were actually worked out, it might be quite different..
It's not like python python doesn't already have other assignment with different behaviors, for instance: a = "hello world" a = 6 print(a) -> 6
class Foo: @property def x(self): return self._internal @x.setter def x(self, value): self._internal *= value def __init__(self, x) self._internal = x
a = Foo(4) print(a.x) a.x = 6 print(a.x) -> 24
This is surprising, but it doesn't mean properties are not useful.
If this were the example given for why properties with setters should exist, it would probably get a -1 from everyone.
But it doesn’t take much to turn this into an example that’s a lot more convincing. For example:
self.ui.slider = BoundedIntegralSlider(0, 100) self.ui.slider.value = 101 print(self.ui.slider.value) # 100 self.ui.slider.value = sqrt(10) print(self.ui.slider.value) # 3
The same kind of thing may be true for your change.
A few interesting things I thought to do with this behavior are:
I think this is confusing consts (values that can’t be replaced) with immutable values (values that can’t be changed). Overriding __setattr__ has nothing to do with constness, while it goes 80% of the way toward immutability.
But also, I think it’s a bit weird for constness to be part of the value in the first place. (In C++ terms, a const variable doesn’t necessarily have a const value or vice-versa.) I think trying to fit it into the value might be what encourages confusing const and immutable.
Also, shouldn’t classes and instances (even of __slots__ or @dataclass) “declare” constants just like modules? If you have to write the same thing completely differently, with different under-the-covers behavior, to accomplish the same basic concept, that’s a bit weird. A module-level @property or module __setattr__ seems like it would be a lot more consistent. (Although I’m not sure how well that ports to locals.)
Making send look like assignment feels a lot less natural, not more. I’d expect something like <- (as used by most Erlang-inspired languages), but, more importantly, something different from =. Especially since you often do want to pass around and store coros, pipes, channels, etc., and if doing so actually send the coro to another coro because you’d reused the name, that would be very confusing.
If this had existed from the start or early on, it would be perfectly natural to read and use, but for now it would be somewhat shocking (in so far as it gets actually used) and I think that is the biggest minus.
I’m not sure that’s true.
If we were talking about a language that has an lvalue objects-and-variables model but doesn’t allow assignment overloading (like Scala, I think?), sure. Some such languages allow it, some don’t, and it’s a pretty simple readability trade off.
But languages with Smalltalk-style object models (especially those without declarations), not being able to overload assignment feels like a natural consequence: variables don’t have types, so a variable’s type can’t take control. You can’t do it in Smalltalk, or Ruby, or any of the Lisp object systems I know of, etc. So, even if Python had a clever workaround to that from the start, I think it would still feel surprising to most people.
Of course the way descriptors, metaclasses, and a few other things work under the hood feels surprising until you get the point, but Python only has a very small number of such things—and they’re all used to build less surprising surface behavior (e.g., @property makes total sense to a novice, even if the implementation of it looks like black magic).
On Fri, Jun 21, 2019 at 4:17 PM Ethan Furman firstname.lastname@example.org wrote:
On 06/20/2019 01:25 PM, nate lust wrote:
--> class Foo: ... def __init__(self, o): ... self.o = o ... def __setself__(self, v): ... self.v = v ...
--> f = Foo(5) --> print(f) <__main__.Foo object at 0x7f486bb8d300>
--> print(f.o) 5
print(f.v) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Foo' object has no attribute 'v'
--> f = "hello world" --> print(f.v) hello world
--> print(f) <__main__.Foo object at 0x7f486bb8d300>
Thank you for doing the work of a proof-of-concept (and to Andrew Barnert
for his excellent write-up). I think this shows exactly why it's a bad
idea -- even though I knew what you were doing, having
f not be a string
after the assignment was extremely surprising.
Python-ideas mailing list -- email@example.com To unsubscribe send an email to firstname.lastname@example.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://email@example.com/message/AFPG55... Code of Conduct: http://python.org/psf/codeofconduct/
-- Nate Lust, PhD. Astrophysics Dept. Princeton University
Python-ideas mailing list -- firstname.lastname@example.org To unsubscribe send an email to email@example.com https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://firstname.lastname@example.org/message/CVYU43... Code of Conduct: http://python.org/psf/codeofconduct/