On Jan 30, 2020, at 11:20, Johan Vergeer <johanverg...@gmail.com> wrote:
> 
> It is a couple of days later, but I managed to create a more expanded 
> proposal.
> 
> This proposal is about having a simple and consistent way of getting the name 
> of an object.
> Whether it is a class, type, function, method or variable or any other object.

I think this is glossing over something very important.

You really aren’t trying to get the name of an object at all, you’re trying to 
get the name of a _variable_.

After you write foo.bar = 2, the object in foo.bar is just the number 2, an 
object of type int that’s probably the same object that’s referenced in dozens 
of other places, both named and anonymous, in your program’s current state. It 
can’t possibly have the name “bar”.

What _can_ have the name “bar” is the variable that references that value. But 
that variable isn’t a thing that exists in Python at runtime.

A compile-time transformation could convert nameof(foo.bar) into “bar”. In 
fact, the compiler already pretty much has to do exactly that to emit the code 
for foo.bar, which is effectively 'push the object in too, then push the string 
“bar”, then do an attr lookup with those two things'.

So nameof cannot be a function. It has to be a special compiler-time operator. 
The code emitted for nameof(foo.bar) has to be effectively the same code 
emitted for the literal “foo”.

And this affects a whole lot of your other points below:

> ```python
> def my_factory(class_name: str):
>    if class_name == "Foo":
>        return Foo()
>    if class_name == "Bar":
>        return Bar()
> ```

The way you already do this today is usually to look up the class name in a 
dict—either directly, or as part of a namespace lookup (e.g., getattr(self, 
class_name)). Having nameof wouldn’t really help here.

However, it could help on the _caller_ side: you could call 
my_factory(nameof(Foo)). In this trivial case, there’s no reason for that, 
because if you have the name Foo you already have the class object Foo and 
don’t need a factory in the first place. But there might be less trivial cases 
where it might be useful.

> I know this is a very simple example that can be solved with the example 
> below, but it would be nice if we can do the same with methods, functions and 
> attributes.

But you can already write that factory that way. Methods are attributes, and 
both can be looked up with getattr. Functions are attributes of the module 
object, and members of globals, so they can be looked up either way. You don’t 
need nameof for this kind of dynamic lookup, and it doesn’t help to add it. You 
only need nameof got static (compile-time) lookup, to serve the purposes from 
your initial paragraph, like making sure the name gets caught by refactoring 
tools (which can be sure that nameof(Foo) must rename along with Foo, but can’t 
know whether the string literal “Foo” needs to change).

> ## Class names
> 
> When you want to get the name of a class, you should just use the name. So 
> `nameof(Foo)` becomes `"Foo"` and `nameof(type(foo))` also becomes `"Foo"`.
> The results are basically the same as `Foo.__name__` and `type(foo).__name__`.

So what happens here:

    class Foo: pass
    Bar = Foo
    print(nameof(Bar))

If you compile nameof(Bar) into a call to a normal (runtime) nameof function 
that returns param.__name__, you’re going to print out Foo.

If you compile it to just Bar.__name__, you’re still going to print out Foo.

If you compile it to the string "Bar", you’re going to print out Bar. And I 
think that’s what you actually want here.

Meanwhile, I don’t see how nameof(type(foo)) can work. At compile time, we have 
no idea what the type of foo is. Consider this case:

    foo = Foo() if spam else Bar()

Even if the compiler were smart enough to know that Foo and Bar are guaranteed 
to be names for the same object (which they actually might not be even after 
Bar = Foo—you can write a globals dict whose __setitem__ does something crazy… 
but ignore that), it still can’t know which name we used here without knowing 
the runtime value of spam, which it can’t possibly have.

As a side note, this implies that making nameof look like a function call (with 
parentheses around an argument) is misleading. Everything that looks like a 
function call in Python is actually compiled as a function call, and I don’t 
think you want to break that. And there’s no real reason to—Python already has 
spelled-out prefix operators like not, so why shouldn’t nameof be the same? (In 
C# things are different.)

> ## Attribute names
> 
> You should be able to get the name of an attribute. 
> 
> ```python
> class Foo:
>    bar: str = "Hello"
> 
>    def __init__(self):
>        self.baz = "World"
> 
>    def __str__(self):
>        return f"{nameof(self.bar)}: {bar}, {nameof(self.baz): {baz}}"  # 
> Returns "bar: Hello, baz: World"
> 
> foo = Foo()
> 
> nameof(foo)  # Returns "foo"
> nameof(foo.bar) # Returns "bar"


If you compile nameof(foo.bar) to a function call, that function gets called 
with the string "Hello". There’s no way you can get anything else out of that 
string at runtime. (You can use horrible frame hacks to inspect the caller’s 
context and try to guess what it was trying to pass you, which will work in 
many cases, but if you’re proposing a new language feature why make it depend 
on that?)

What you want to compile this to is pretty clearly just the string “bar”. Which 
is immediately available at compile time.

You do need to go through the whole grammar to decide exactly what productions 
you should be able to use in a nameof and what the result should be. But that 
shouldn’t be too hard. (And it’s a necessary first step toward an 
implementation anyway.)

> ## Stuff to think about
> 
> There are also situations that I haven't been able to make a decision about 
> and I think these should be discussed further down the road.
> 
> ```python
> nameof(None)
> nameof(type)
> nameof(int)

Why should these be any different from anything else?

For type and int, these are just names like any other (except for usually being 
found in builtins rather than the module’s globals), and the things they name 
are types like any other. (They’ve even got a __name__ like other type values.)

None is a bit special because it’s sort of a keyword to prevent people from 
accidentally rebinding the name to a different value, but other than that it’s 
still basically just a builtin name, and the thing it names is a value no 
different from “hello” or 2 in your other examples.

> _foo = Foo()
> nameof(_foo) #Should this return "_foo" or "foo"?

How could it possibly return “foo”? Unless you’re suggesting that the 
occasionally-followed convention of naming a generic instance of a type with 
the lowercase version of the type’s name should be enshrined in the language?

> ## How should the interpreter handle it?
> 
> I think the interpreter should handle it as anything else that is passed to a 
> function, 

But it can’t be.

> so when the value passed to the `nameof()` function doesn't exist, an error 
> should be thrown.

But you can’t know that at compile time.

This is actually the biggest problem. In C#, the compiler has to know the names 
of every variable, attribute, etc. In Python, it can’t. There’s nothing 
stopping me from doing this:

    if spam:
        foo = Foo()

… and now the variable foo may or may not exist. Or I can even do this:

    globals()[name] = 3

… and if name happens to be holding the string “foo” then foo will exist.

There is a special case here related to local variables, but if you want this 
to work for globals and attributes (which I assume you do, because all of your 
examples are globals or attributes) that isn’t relevant.

> # How can this be built in the Python language (or a library)
> 
> Some people responded this will be very hard to implement because of the way 
> Python is built.
> To be honest, I don't know nearly enough about the internals of Python to 
> make a solid statement on that.

You don’t need to know the internals, at least if you can convince someone who 
_does_ know the internals to do the work for you.

But you do need to know the basics of what variables are in Python as opposed 
to C#.

> # What about `__name__`?
> 
> Most of the examples I wrote above can be solved by just using `__name__`, 
> which solves most use cases, except for variable and attribute names, 
> which cannot be resolved right now because objects don't have names.

That’s not right. Classes and functions are objects, and they have names. But 
the real issue is that you don’t _want_ the name of the object, you want the 
name of the variable. And no object knows the name of the variable you used to 
access it. Even for classes and functions, the fact that class statements and 
def statements happen to produce a variable with the same name that’s stored in 
the object doesn’t mean you want the name stored in the object, because it’s 
trivial to create a different name for the same object (assignment, argument 
passing, import as, …), and when you do, it’s that different name that you want.

_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/6OBKUTDK235NL3VN5KAOV4ENL6WAIK7L/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to