A new suggestion for Python
Hi, My name is Jonatan and i am programming in Python for about 4 years, I have a great idea, there are iX` methods, such as __ior__, __iadd__, __iand__ etc.., which implements the |=, +=, &= behavior, it would be nice if you could implement also __igetattr__ or something, which means: instead of con = "some text here" con = con.replace("here", "there") we could do con = "some text here" con .= replace("here", "there") Please let me know what do you think about it, Jonatan
On 2020-09-30 08:41, Jonatan wrote:
instead of con = "some text here" con = con.replace("here", "there")
we could do
con = "some text here" con .= replace("here", "there")
(Your message had some odd formatting but thankfully there was a plain-text version included without the issue.) In regards to the idea, I generally like it. I can imagine the arguments against, such as the X= augmented assignments are not the most readable. But if the others are beneficial, why not this one? It appears mostly useful for strings however, any thought to other types or AttributeErrors? -Mike
Hi Konatan, welcome! Comments below. On Wed, Sep 30, 2020 at 06:41:04PM +0300, Jonatan wrote: [...]
it would be nice if you could implement also __igetattr__ or something, which means:
instead of con = "some text here" con = con.replace("here", "there")
we could do
con = "some text here" con .= replace("here", "there")
An interesting idea. You should have a read of the original PEP for augmented assignment and see how well this idea fits the original proposal: https://www.python.org/dev/peps/pep-0203/ You can ignore anything about `__coerce__`, that method is gone from Python now. I think this might make good sense for string methods: mystring = mystring.upper() mystring .= upper() but less so for arbitrary objects with methods returning arbitrary values, or methods that operate in place. There's also the factor that the dot operator is not very visually distinctive. "Syntax shouldn't look like grit on Uncle Tim's monitor", although of course we already have the dot for attribute access, so this isn't a really strong argument. But more importantly, unless the right hand side is severely limited, it is going to be very hard to be unambiguous. The standard augmented assignments take *any expression at all* for the right hand side: value += 2*x - y but this could not: value .= 2*x - y I'm not entirely sure what would be allowed. mystring .= (strip() or default).upper() Would you expect that to work? -- Steve
The dot has recently been used a lot kotlin: for loop 0..9 Js: ...array .= seems cool enough Btw i saw this on Kotlin's doc, the first time i see a direct reference from one 'recent' language concerning another. Kotlin's loops are similar to Python's. for iterates over anything that is *iterable* (anything that has an iterator() function that provides an Iterator object), or anything that is itself an iterator: The for in loop influenced quite some langs which makes me think that the spirit of Python as well as it's community is curiously awesome. Kind Regards, Abdur-Rahmaan Janhangeer https://www.github.com/Abdur-RahmaanJ Mauritius sent from gmail client on Android, that's why the signature is so ugly.
-1. Fluent programming is uncommon in Python, and hence few methods return a call of the same or similar type. Methods on strings are an exception here, but they are unusual (partly because strings are immutable). Methods in Python tend to do one of two things: 1. Mutate in place, returning None lst = [1, 4, 8, 3, 2, 9] lst .= sort(). # lst is None now 2. Return something of an entirely different type, often some some kind of summary of the original object. For example, even with a string: haystack = "XFGHXDEXAWR" numX = haystack.count("X") Under the proposal, the following would be legal, but suffer awful code smell: haystack .= count("X") The suggested syntax would be a bug magnet far more often than it would be useful. On Wed, Sep 30, 2020, 5:45 AM Jonatan <pybots.il@gmail.com> wrote:
Hi, My name is Jonatan and i am programming in Python for about 4 years, I have a great idea, there are iX` methods, such as __ior__, __iadd__, __iand__ etc.., which implements the |=, +=, &= behavior, it would be nice if you could implement also __igetattr__ or something, which means:
instead of con = "some text here" con = con.replace("here", "there")
we could do
con = "some text here" con .= replace("here", "there")
Please let me know what do you think about it, Jonatan _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@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/QAU3UR... Code of Conduct: http://python.org/psf/codeofconduct/
If I can, I want to back up the conversation a bit. Instead of starting with a solution, what's the problem? I believe the issue that this is trying to solve is that some functions that operate on an object return a new object, and we would like to use them to modify an object. Setting aside the fact that a new object is not the same as a modified object and other references to that object won't change, what this means is that we want to have a shorthand way to write: (some reference to an object) = (an expression that operates on <some reference to an object)) Note that the := operator solves a similar problem: (an expression that references (some subexpression) multiple times) where we can rewrite blah blah (some subexpression) blah blah (some subexpression) blah (some subexpression) blah as blah blah (X := (some subexpression)) blah blah X blah X blah That won't help us here because the two <some references to an object> are respectively an lvalue and an rvalue. Given that, I'll throw out a straw proposal. Since an lvalue can be converted to an rvalue but not vice versa, suppose we rewrite this as: (some reference) := X = <some expression using X> example: thing =: T = T.replace(before, after) But looking at that, it really isn't much better unless the left hand side is a very complicated expression. If we really needed a feature like this it would be better to use a special symbol like: thing = <>.replace(before,after) Even then, how frequent is this pattern that it would be worth doing? I think the answer to that question is going to be necessary to convince anyone that the problem is worth solving. --- Bruce
On Wed, Sep 30, 2020 at 1:43 PM David Mertz <mertz@gnosis.cx> wrote:
Fluent programming is uncommon in Python, and hence few methods return a call of the same or similar type.
I think that if you include built-in operators as (shorthand for) method calls, and you count the number of occurrences in typical Python programs rather than the number of distinct species, it's common. I don't know if I necessarily support this idea, but it makes sense as a generalization of the existing augmented-assignment syntax to operations that are conceptually similar, but are written with method-call syntax because Python lacks operators for them. The fact that methods are inconsistent about mutating in place versus returning a new object, and it's sometimes hard to guess from the name which will happen, is unfortunate but I don't think this proposal makes the problem any worse. Here are a few cases where the syntax would be useful for values other than strings: named_tuple .= _replace(...) pil_image .= crop(...) numpy_array .= transpose() node .= get_{parent,left_child,right_child}() mutable_thing_from_caller .= copy() What I dislike about this syntax is that it makes no grammatical sense since . isn't a binary operator and the thing on the right isn't an expression.
On Wed, Sep 30, 2020 at 4:24 PM Ben Rudiak-Gould <benrudiak@gmail.com> wrote:
On Wed, Sep 30, 2020 at 1:43 PM David Mertz <mertz@gnosis.cx> wrote:
Fluent programming is uncommon in Python, and hence few methods return a call of the same or similar type.
I think that if you include built-in operators as (shorthand for) method calls, and you count the number of occurrences in typical Python programs rather than the number of distinct species, it's common.
I think this misses the point. Yes, the methods that implement operators are ALL "fluent" for Python numeric types. I can write: a + b * c / d % e ** f - g Subject to operator precedence, every one of those "method calls" represented by the symbols produces a new value of the same or similar type (sometimes we coerce up the numeric hierarchy). Of course if you were to write that out using a bunch of dunder methods, you'd get a "fluent programming" style. The dunders are very self-consciously different from "regular" methods. One of the biggest and clearest differences is that most/many dunders are specifically designed for fluent programming... but they are designed that way because the methods themselves are rarely used directly, but rather the mathematical symbols are instead.
The fact that methods are inconsistent about mutating in place versus returning a new object, and it's sometimes hard to guess from the name which will happen, is unfortunate but I don't think this proposal makes the problem any worse.
They are inconsistent, but not THAT inconsistent. Mutation of mutable collections almost always returns None. Almost everything that doesn't return None on a mutable object returns an aggregation or reduction of the collection, not a transformation. Yes, I can find exceptions, and so can you. But as a mental model, that comes pretty close. That is to say, in PYTHON. Yes, Pandas and some other libraries encourage a fluent style. But very little within Python itself does (except strings). Here are a few cases where the syntax would be useful for values other than
strings: named_tuple .= _replace(...)
In all the years I've used and taught namedtuples, I think I've never used the ._replace() method. The leading underscore is a hint that the method is "private" (that said, I've used the "private" ._asdict() fairly often).
pil_image .= crop(...)
Not Python, but an external library. Pillow doesn't really encourage a fluent style either, but most methods do return some sort of image, admittedly.
numpy_array .= transpose()
More often than not in NumPy (again, not Python itself), you will be led astray trying to do stuff like this. Yes, .transpose() works. On the other hand, why on earth would you ever not just spell that `numpy_array.T` which is so much easier?!
node .= get_{parent,left_child,right_child}()
I'm not sure if this is meant to refer to some particular library or a hypothetical. Yes, I can imagine this working with a recursive descent into a binary tree. But it's no big win. I'd be less likely to want to rebind the descent to the same name, but maybe.
mutable_thing_from_caller .= copy()
This will DEFINITELY fail code review. Why on earth would you replace a name with a copy of its initial object like that?! I'm sure there is SOME scenario where that would be desirable, but it's got to be unusual (and more likely a bug than an intent). If this feature were somehow to get into the language, yes I can imagine a few places where it would be usable. But I can imagine vastly more cases where it would be an attractive nuisance. -- 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.
On 2020-09-30 13:42, David Mertz wrote:
-1.
Fluent programming is uncommon in Python, and hence few methods return a call of the same or similar type. Methods on strings are an exception here, but they are unusual (partly because strings are immutable).
This argument is mentioned a lot on here, but I increasingly think it should be reconsidered. Method chaining is a good way to do things. Lots of things. I'm more and more convinced that some of the things Python does without method chaining would be better done with method chaining, rather than just rejecting method chaining because "that's not how Python does it". (This is tangential to the idea under discussion here, though.) -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown
On 1/10/20 4:25 pm, David Mertz wrote:
In all the years I've used and taught namedtuples, I think I've never used the ._replace() method. The leading underscore is a hint that the method is "private"
Usually that would be true, but namedtuple is a special case. The docs make it clear that the underscore is there to prevent it from clashing with a potential field name, not to suggest privateness. -- Greg
On Wed, Sep 30, 2020 at 7:30 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
On 1/10/20 4:25 pm, David Mertz wrote:
In all the years I've used and taught namedtuples, I think I've never used the ._replace() method. The leading underscore is a hint that the method is "private"
Usually that would be true, but namedtuple is a special case. The docs make it clear that the underscore is there to prevent it from clashing with a potential field name, not to suggest privateness.
OK, that's a good point. I kinda hadn't thought about that fact. But nonetheless, I haven't been shy to use ._asdict(), so I wasn't avoiding ._replace() out of concerns for a "private" declaration. It's just not a thing I've needed. Which makes me feel like having a way to spell it that is a few characters shorter is not an important life concern for me. :-) -- 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.
-1 for various reasons expressed by several authors. But I'm not sure I agree with this: Steven D'Aprano writes:
I think this might make good sense for string methods:
mystring = mystring.upper() mystring .= upper()
but less so for arbitrary objects with methods returning arbitrary values, or methods that operate in place.
l = [0] l *= 5 l [0, 0, 0, 0, 0]
Operators are just functions. In most cases, we try to define them as algebraic operations, but occasionally there's reason to do otherwise. When we do, often the augmented assignment operators do sane things, too. I don't see why that wouldn't be true for '.', although I don't think that's a slam dunk either, '.' is different from most operators.
On Fri, Oct 02, 2020 at 11:49:02AM +0900, Stephen J. Turnbull wrote:
-1 for various reasons expressed by several authors. But I'm not sure I agree with this:
Steven D'Aprano writes:
I think this might make good sense for string methods:
mystring = mystring.upper() mystring .= upper()
but less so for arbitrary objects with methods returning arbitrary values, or methods that operate in place.
l = [0] l *= 5 l [0, 0, 0, 0, 0]
I'm not sure that I understand your example here. Or rather, I understand your example, I don't understand why you think it disputes my comment above. Your example uses the list repetition operator, which returns a list; it is neither something that returns an arbitrary value, nor a method that operates in place. Here are two hypothetical examples: a = [10, 20, 30, 40, 50] a .= index(40) assert a == 3 a = b = [1, 2, 3, 4, 5] a .= reverse() assert b == [5, 4, 3, 2, 1] assert a is None It's not clear to me that either of these would make good examples of augmented assignment: - the first replaces the list `a` with an int; - the second modifies the list in place, and then replaces it with None. Both are working as designed, in the sense that list.index() returns an int and list.reverse() modifies in place. It might even be that this is precisely what the writer intended. But they are surely *weird* examples for augmented assignment. -- Steve
On 30/09/2020 19:02, Steven D'Aprano wrote:
But more importantly, unless the right hand side is severely limited, it is going to be very hard to be unambiguous. The standard augmented assignments take *any expression at all* for the right hand side:
value += 2*x - y
but this could not:
value .= 2*x - y
I think this is closest on the thread to explaining why this doesn't work, or at least is a semantic nightmare. I've been steeped in descriptors these last several weeks, so I can put it another way: "." is not really an operator, it is attribute access. Or if one is forced to think of it as an operator, its right operand may only be the name that follows. In translating the "operator", the compiler quotes the right argument as a litereal, rather than evaluating it. Thus:
'hello'.replace('ell', 'ipp')
means the same as:
('hello'.replace)('ell', 'ipp')
and the same as:
getattr('hello', 'replace')('ell', 'ipp')
and not:
'hello'.(replace('ell', 'ipp'))
which doesn't mean anything at all, because "replace('ell', 'ipp')" cannot be the right hand side of ".". Jeff Allen
Steven D'Aprano writes:
I'm not sure that I understand your example here. Or rather, I understand your example, I don't understand why you think it disputes my comment above. Your example uses the list repetition operator, which returns a list; it is neither something that returns an arbitrary value, nor a method that operates in place.
My point is that given that the operation involves a non-list and the operation is in fact defined to be commutative, it *is* something that returns an arbitrary value -- if your frame of reference is a binary operation on a set, which involves two operands and a result, all members of the same set.
Here are two hypothetical examples:
[...]
It's not clear to me that either of these would make good examples of augmented assignment:
Sure, but that's not the way this works, picking out obviously bad examples. You need to do some work to make the plausible. In the case of the list index, renaming the assignment target: ndx = [10, 20, 30, 40, 50] ndx .= index(40) To me that looks like a statement-oriented version of the fluent style. For the "returning None" example, using .= with a method that is *defined* to return None is pretty silly. But consider a binary tree where the attributes Node.left and Node.right are None if no subtree is attached: # 1776, not Hamilton def cool_cool_conservative_men(federalist_heap): judge = federalist_heap while judge: appoint_justice(judge) judge .= right Note that this example could be modified to use a null Node (ie, bool(null_node) == False) instead of None, in which case it could be in-place. Are those styles Pythonic? Probably not. Are they even practically useful? I'm not sure. But they're much more the kind of example we need to look at to understand this proposal.
participants (12)
-
Abdur-Rahmaan Janhangeer
-
Ben Rudiak-Gould
-
Brendan Barnwell
-
Bruce Leban
-
David Mertz
-
Greg Ewing
-
Jeff Allen
-
Jonatan
-
Marco Sulla
-
Mike Miller
-
Stephen J. Turnbull
-
Steven D'Aprano