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.