On Jan 31, 2020, at 19:30, Soni L. <fakedme+py@gmail.com> wrote:

On 2020-02-01 12:00 a.m., Andrew Barnert wrote:
On Jan 31, 2020, at 14:51, Soni L. <fakedme+py@gmail.com> wrote:
>>> On 2020-01-31 2:36 p.m., Andrew Barnert wrote:
>> That does work, but that means foo.bar has to exist and have a value before you look up the name. Consider these cases:
>>    class Spam:
>>        def __init__(self):
>>            self.eggs = 42
>>    print(nameof Spam.eggs) # AttributeError
>>    class Foo: pass
>>    foo0 = Foo()
>>    setattr(foo0, nameof foo0.bar, 42) # AttributeError

> All of these seem to be working exactly as I'd expect nameof to work. In fact these are exactly how I'd prefer it to work, personally. I'm not OP tho.

Really? These seem like exactly the kinds of places I’d want to use nameof (so the IDE can magically refactor for me), and I can’t do that if it raises exceptions at runtime instead of working. It only seems like the right thing for the trivial case that, as Greg Ewing says, would already work fine without a check anyway.

Yes, really. It's dynamic, so it must pay attention to the context it's being used in.

Why? The name of a variable does not depend on its runtime context.

Which is a good thing, because by the time you get to that runtime context you don’t even _have_ a variable; there is no such thing as variables in the Python data model.

> > But here's the thing -- C# is statically checked. Python is dynamically checked. (and yes, Python *does* have type checking, among other things.)

Not so much.

You _can_ do dynamic type checking in Python, but 80% of the time you don’t, you just accept anything that quacks as the right type. In C#, if you pass a complex or a string to a function that wanted an int, you get a compile-time type error. Python doesn’t have any equivalent runtime error, unless you manually write code to check isinstance and raise. And even that can’t handle all types that really exist in Python—there is no way to tell if something is an Iterator[int] for example. Of course if the function tries to return param+1, that will raise a TypeError if you passed a string—but not if you passed a complex.

And attributes are if anything even more “ducky” than function signatures. While you _can_ restrict the set of attributes for instances of a type with a custom __setattr__ or with __slots__ or whatever, usually you don’t; most classes’ instances can have whatever attributes they want.

> I figured this would be the correct way to adapt a statically checked syntactic construct into an *equivalent* dynamically checked syntactic construct.

But it’s not equivalent. In all those examples I gave, you statically know whether foo.bar exists, and therefore the compiler can check that nameof is correct, but you dynamically don’t know, so the runtime cannot check that nameof is correct. It could check something significantly different, and less useful, but it can’t check the equivalent thing, because there _is_ no equivalent thing to “Foo instances have an attribute named bar” in Python, even at runtime.


Okay here's one of the examples you gave:

    class Spam:
        def __init__(self):
            self.eggs = 42
    print(nameof Spam.eggs) # AttributeError

I don't know how C# does it, exactly, *but I'd expect this to be an error!*

I don’t think it is. I don’t have a C# compiler handy, but from the spec (sadly the “examples” section is just “TODO”…):

The meaning of the named_entity of a nameof_expression is the meaning of it as an expression; that is, either as a simple_name, a base_access or a member_access. However, where the lookup described in Simple names and Member access results in an error because an instance member was found in a static context, a nameof_expression produces no such error.


nameof_expression is a constant expression of type string, and has no effect at runtime. Specifically, its named_entity is not evaluated, and is ignored for the purposes of definite assignment analysis (General rules for simple expressions). Its value is the last identifier of the named_entity before the optional final type_argument_list, transformed

So an instance member in a static context is not an error, and the value is the name of the member, unless I’m reading something wrong.

Also, this is the same way operators like sizeof, type of, and offsetof work in C#, C++, and C, isn’t it?

Additionally those languages have null, so you could do something along the lines of:

    Spam dummy = null;
    nameof dummy.eggs

How is that relevant? Clearly the result should be the string “eggs” in C#, right? And in my static-only version of the proposal, the same would be true in Python, but in your version it would be an error. And a refactoring tool should rename this eggs when the member is named, which is the whole point of the proposal. So why is doing something different from C# that breaks the point of the feature better?

IDEs are very much capable of refactoring strings.

No they’re not. They have to guess at it using heuristics, which are complicated, and often get it wrong. Because it’s inherently ambiguous whether the string or substring “spam” is meant as the name of an attribute or as normal English text.

On the other hand, runtime errors would benefit those of us who use e.g. plain vim and no IDE.

I mostly use emacs rather than an IDE. But I can still use refactoring tools. There’s nothing about a refactoring tool that demands that it be built into an IDE just because it could be—in the same way that PyCharm integrating a static type checker doesn’t stop me from using mypy.