Operator to get qualified name

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

Can’t you use the __qualname__ method?

In what way? My suggestion was to make expressions such as (from Python’s pickle):

return _Unpickler(file, fix_imports=fix_imports, buffers=buffers,
                 encoding=encoding, errors=errors).load()

Into

return _Unpickler(file, **dict(fix_imports=, buffers=, encoding=, 
                 errors=)).load()

In fact, this prompts me to suggest that it should also be allowed for this NameValuePair to somehow be interpolated seamlessly into kwargs (e.g. when they are provided as “literals”), so that the above could be turned into:

return _Unpickler(file, fix_imports=, buffers=, encoding=, 
                 errors=).load()

Have you seen Shorthand notation of dict literal and function call - Ideas - Discussions on Python.org?

This topic comes up quite a bit.

Thanks! That has been discussed I see. Esp. here. I’ll continue discussion there.

Be aware that no-one has yet managed to put a convincing case for the idea. You’ll need to make sure you’re aware of, and can address, the objections that have already been made, otherwise you won’t be doing much more than adding a “me too” to an idea that’s not really going anywhere at the moment.

2 Likes

Redirecting to Allow identifiers as keyword arguments at function call site (extension of PEP 3102?) - #20 by mousehand at the suggestion of the OP.