Sorry, sent early… ignore that and try this version.


On Jan 31, 2020, at 19:58, Andrew Barnert <abarnert@yahoo.com> wrote:

Here’s a concrete proposal.

tl;dr: `nameof x == "x"`, and `nameof x.y == "y"`.

Rationale:

Even though as far as Python is concerned `nameof x.y` is identical to `"y"`, it can make a difference to human readers—and to at least two classes of automated tools.

When refactoring code to rename something, there is no way to tell whether a string—or a substring in a larger string—that matches the name should be changed. But a nameof expression is unambiguously a reference to the name, and therefore the tool knows that it needs to change.

For example:

    class Spam:
        cheese = 10
        def __repr__(self)
            return f"<{type(self).__name__} at {id(self)} {nameof self.cheese}: {self.cheese}>"

If I ask my IDE to rename cheese to beans, that `nameof self.cheese` is unambiguous: it clearly should be changed to `nameof self.beans`. If I had just included the substring “cheese” within the string, it would need some kind of heuristics or trained AI or something to figure that out.

Similarly for this example:

    rpc.register(nameof spam, spam)

Again, if I just used the string "spam", renaming the function spam to eggs would be ambiguous.

And if I used spam.__name__, this would be unambiguous, but potentially incorrect. For example:

    spam = SpamHandler.handle
    rpc.register(spam.__name__, spam) # oops

A second use is static type checkers. If a type checker sees `nameof spam.eggs`, it can apply whatever rules it would use for `spam.eggs` itself. For example, if it can figure out the type or `spam`, and that type declared its attributes, and there is no attribute named `eggs`, this can be a static type error.

Grammar:

    atom ::= identifier | literal | enclosure | nameof_atom
    nameof_atom ::= "nameof" nameof_name
    nameof_name ::= identifier | attributeref

Semantics:

The value of nameof_atom is a string: the name of the identifier itself for the first case, or of the identifier after the dot for the second case. The string value is semantically identical to a literal for the same string, so `nameof foo.bar` and `"bar"` should be compiled to identical code. There is no difference to Python, only to human readers and automated tools.

Compatibility:

Because `nameof` is a valid identifier today, there may be code that uses it as a variable name. This means the usual 3-version __future__ schedule is probably needed. And a search through public code to make sure it isn’t a lot more common than you’d expect.

Alternatives:

Why no parentheses? Because in Python, only function calls take parentheses. In C# (where this feature originated) there are other compile-time operators like sizeof that take parens, and ultimately this goes back to making those operators look like calls to preprocessor macros in 1970s C; that isn’t relevant in Python, so requiring parens would just be confusing for no benefit.

Why no runtime behavior? Because there’s nothing that could reasonably be checked. Unlike C#, Python doesn’t have “variables” at runtime; it just has names bound to objects in namespaces. Given an object, there is no way to know what name you used to access it. Also, Python attributes are completely dynamic—in C#, `spam.eggs` means something like “the int-typed value found between 4 and 8 bytes after the start of `spam` in memory”; in Python, it means “the result of calling getattr(spam, 'eggs'), which could be any arbitrary code”. Even if it were possible, it’s not clear what the benefit would be, and without someone making a case for a benefit, it’s not worth trying to come up with something clever in the first place.