1. Grammar (PEG)
I thought of this token (&) because in C it’s an address operator; it’s not the same thing, but there’s a similarity.
primary:
| primary '.' '&'? NAME
| primary genexp
| primary '(' [arguments] ')'
| primary '[' slices ']'
| atom
2. Motivation
There are classes with methods that don’t return the instance itself, instead returning None, which forces me to create a variable in certain situations.
Before:
>>> received_checksum = s.recv_exact(2)
>>> crc = CRC16(b'foo')
>>> crc.update(b'bar')
>>> calculated_checksum = crc.digest()
>>> assert received_checksum == calculated_checksum
It’s not very attractive code, is it? I was forced to create a variable in order to use the update() method.
I spend more time thinking of a good variable name than actually coding, but there are times when you don’t want to create a variable or fill your code with utility functions.
After:
>>> received_checksum = s.recv_exact(2)
>>> calculated_checksum = CRC16(b'foo').&update(b'bar').digest()
>>> assert received_checksum == calculated_checksum
3. Behavior
When using . + & token, should returns the evaluated object before the getter.
This should not change the result of __getattribute__, but the expression should force the return of the evaluated object.
Internally, Python should evaluate the expression preceding the getter, complete the getter, but return just what was evaluated before the getter.
Examples
SHA1 (can be any hashlib class)
>>> from hashlib import sha1
>>>
>>> """
... Signature: sha1(data=b'', *, usedforsecurity=True, string=None)
... Docstring: Returns a sha1 hash object; optionally initialized with a string
... """
>>>
>>> sha1()
<sha1 _hashlib.HASH object at ...>
>>>
>>> sha1(b'foo').update(b'bar') # None
>>>
>>> sha1(b'foo').&update(b'bar')
<sha1 _hashlib.HASH object at ...>
>>>
>>> sha1(b'foo').&update(b'bar').hexdigest()
'8843d7f92416211de9ebb963ff4ce28125932878'
Queue
>>> from queue import Queue
>>>
>>> """"
... Init signature: Queue(maxsize=0)
... Docstring: Create a queue object with a given maximum size.
... """
>>>
>>> # Creates a queue containing 3 items.
>>> q = Queue(); q.put(1); q.put(2); q.put(3)
>>> # Or:
>>> q = Queue(); for n in (1, 2, 3): q.put(n)
>>> # Or:
>>> q = Queue().&put(1).&put(2).put(3)
>>>
>>> # Drop two items, get the next.
>>> x = q.get(); q.get(); x = q.get()
>>> # Or:
>>> for _ in range(3): x = q.get()
>>> # Or:
>>> x = q.&get().&get().get()
sorted() vs list.sort() or reversed() vs list.reversed()
>>> # You probably prefer using `sorted()` rather than `list.sort`, but it always creates a new list, therefore it's slower and uses more memory.
>>> sorted([3, 2, 1])
[1, 2, 3]
>>>
>>> # Here, the list itself is sorted without a copy (much faster).
>>> [3, 2, 1].&sort()
[1, 2, 3]
>>>
>>> # This result is equivalent to sorted(), but subtly slower, prefer sorted().
>>> [3. 2. 1].copy().&sort()
[1, 2, 3]
4. Considerations
- Obviously its use is debatable and there would be cases where someone would overuse it, but that didn’t stop the walrus operator (:=) from being added.
- It is (or should be) a rule that methods created solely to change the object’s state should return
Noneinstead ofselfto differentiate them from methods that create copies of the object and return them. - I discourage the use of this token in some methods that return anything other than
None, as it could cause confusion:>>> mylist = [1, 2, 3, 4] >>> mylistcopy = mylist.©() >>> assert mylist != mylistcopy, 'are the same list' --------------------------------------------------------------------------- Traceback (most recent call last) ... AssertionError: are the same list - Should Python be too strict to prevent the user from making mistakes? I don’t think so; perhaps this task could be assigned to linters as a bad practice. I believe that natively preventing this would make it less flexible, less free.