Hey! This is my first post here, so hopefully it’s up-to-standard. Also, There are many ideas here and I hope I convey them clearly - am open to questions and corrections.
TL;DR: I propose a new postfix operator =
, which creates an instance of a class NameValuePair
, which will get formatted in f-strings the same was the debug op gets formatted today, but at the same acts as a pair tuple (short_name, value)
where short_name
is some short qualified name string for the value, facilitating shorter dict subset assignments. This will coincidentally replace the debug op for f-strings.
Many times I’d like to get a qualified name of a variable. While this is very useful also in debugging, as recognized by the inclusion of the f-string debug op, I argue it is useful in other use-cases as well, namely when assigning dicts. This is common practice in JavaScript, and many examples exist in the Python codebase (I naively counted over 2,000 such cases using a simple grep, see below). Brevity is not the only advantage on this - there are other places where using the qualified name in a manner that withstands refactoring (i.e. renaming of variables) is useful, e.g. in error messages regarding attribute names.
I suggest creating a syntax for producing key-value pairs, where the key is the qualified name of the object that the operator acts on. There are many ways in which this can be achieved, and I put down some considerations below.
Form of result: the most natural thing comes to my mind is either just the name itself, or a pair (tuple) (name, value)
. The former would be equivalent to getting the first part of the result of the f-string debug op (depending on what we mean by qualified name). Some alternatives might allow for direct assignment - how often have you seen self.x = x
or x = self.x
. However the latter seems like subject of another suggestion (de-scoping or re-scoping variables), so for simplicity I’ll assume we have some operator that when acts on x
returns a tuple, (x_qualified_name, x)
. In fact, it stands to reason that it should create a instance of a class that derives from tuple (see suggested syntax below).
What does qualified name mean: More often than not, it’s the last part of the fully qualified name that’s of interest, so this should be the default (as opposed to how f-string debug op works). However, there are use-cases for producing a “fully” qualified name, whatever this means. Intuitively, it means one of two things: (a) the full expression used to access the value (a-la f-string debug op); (b) the placement of the value in the type heirarchy - i.e., for a variable a
which is an instance of the class A
, a.b
might result in the string 'A.b'
.
Syntax for the operator: This depends on the form of the result. The use of the equal symbol (=
) as a postfix operator just as used in the f-string debug op is the best choice as far as I am conderned, for several reasons: (a) it’s already used in f-string debug op; (b) the equal sign would most likely appear in any existing expression taht would be used to shorten with this syntax. In fact, it might have been preferable to assign the value of the qualified name itself to the expression a=
and then a new format for f-string debug ops might be f'{a=}={a}'
; or maybe add support for displaying a pair in formatting, which might suggest a syntax such as f'{a=!=}
, which, while clumsy, seems a bit more pythonic. Nonetheless, using the equal-postfix operator might be confusing because (a) if used for producing a pair, it does not intuitively read as “produce a pair here” (if indeed we take that as the expression’s result); (b) in f-string debug op it would have to change, or serve a special case. A middle ground might be found - the a=
expression might produce a NameValuePair
object, whose repr
is f'a={a}'
. This enables reasonable back-compatibility and possdibly richer syntax, e.g. a=.name
(of course, some delicate handling must be done here regarding what is considered the qualifying name).
Currently, I have the following code patterns occuring in my code:
def qname(v):
# this is a very naive implementation that will fail if v has any '=' in its string representation
return v.split("=", 1)[0].rsplit(".", 1)[1]
a = {k: b[k] for k in some_subset_of_b_keys}
a = {k: getattr(b, k) for k in some_subset_of_b_attrs}
a = A(**{k: getattr(b, k) for k in some_subset_of_b_attrs})
a = dict(x=b.x, y=b.y)
a = A(x=b.x, y=b.y)
raise ValueError(qname(f'{some_arg=}'))
I would like to know, is there any kind of support for this kind of idea? Has it been discussed before? If there is support, should I go ahead to put together a PEP and maybe a pull-request?
Yuval
P.S. grep command mentioned above: egrep --include '*.py' -o -r '[, ([]([a-z0-9_]+) *= *([^.]+[.]|)\1,' cpython | wc -l