On Wed, Dec 30, 2020 at 5:01 PM Steven D'Aprano <steve@pearwood.info> wrote:
On Tue, Dec 29, 2020 at 09:02:10AM -0800, Guido van Rossum wrote:
> On Tue, Dec 29, 2020 at 8:11 AM Steven D'Aprano <steve@pearwood.info> wrote:

> To the contrary, vars() is something I added to the language for the
> benefit of REPL users (like dir()), and other usages look suspect to me. I
> find that using `__dict__` is more direct about the purpose, and also it is
> the prevailing style.

I cannot argue with your historical perspective on this, and I agree
that `vars()` is not as well known as I believe it should be.

So you are right on the bare facts. I still think you are wrong on the
aesthetics :-)

Your comment also shines some light on why `vars()` with no argument
returns `locals()`, which otherwise seems strange to me.

This is the first hint that vars() is not what it seems.

Nevertheless, I have to ask what could possibly be "suspect" about using
vars() programmatically? It isn't like dir().

But it is dir()'s cousin, and the fact that its meaning hasn't changed doesn't mean they couldn't: Christopher Barker is already asking for such a change. This is the second hint.
 
dir() is certainly a convenience function for interactive use, and is
documented as returning "the most relevant, rather than complete,
information".

This note is repeated again later in the docs: "Because dir() is
supplied primarily as a convenience for use at an interactive prompt, it
tries to supply an interesting set of names more than it tries to supply
a rigorously or consistently defined set of names, and its detailed
behavior may change across releases."

On the other hand, `vars()` has no such warnings. There's no wiggle-
room: it either returns the instance `__dict__` or it raises TypeError.
So aside from the difference in exceptions (AttributeError versus
TypeError) I don't think that there is any possible difference between
direct attribute access and the output of vars(). Am I wrong?

That's just because vars() didn't turn out to be so useful, or perhaps because people didn't think of improving it when dir() was improved. I suspect that if you look at the original docs, dir() and vars() had pretty similar documentation. (And according to PEP 361, `__dir__` was originally added in 3.0 -- though it seems to have been backported to 2.x at some point.)
 
Speaking of slots, I've often been annoyed that there is no abstraction
that hides the difference between instances that use a dict as symbol
table, and those that use slots. (And those that use both.) If you need
an object's symbol table, for introspection or otherwise, you're out of
luck if it uses slots.

There doesn't seem to be any way to handle these two implementations in
the same way, and objects with both slots and a dict can give surprising
results if naive code expects `__dict__` to be the symbol table:


>>> class A:
...     __slots__ = ('spam', '__dict__')
...
>>> obj = A()
>>> obj.spam = True
>>> 'spam' in obj.__dict__
False
>>> obj.__dict__.update(spam=False)
>>> obj.spam
True


So there's no way to get an object's symbol table in an implementation-
independent way. Whether you use `obj.__dict__` or `vars(obj)` it only
gives you the symbol table for objects that use a dict. There's nothing
that works for objects that use slots.

So far I've worked around this in an ad-hoc fashion by testing for
`__slots__` and treating that case as special, but it would be nice to
ignore the implementation details and just have a "symbol table" object
to work with. What do you think?

That you're providing an excellent argument to evolve vars() independently from `__dict__`. :-)
 
> > Do you prefer to write `mylist.__len__()` over `len(mylist)`? Then you
> > will probably prefer `obj.__dict__` over `vars(obj)` too :-)
>
> Not a valid analogy.

I think it is. Apart from a matter of taste, what part of the analogy
do you feel is invalid?

len() is an important abstraction for containers, and its usage deserves a short name (just like unary minus and abs() for numbers). This is crucial even though you have to use its "true name" (https://xkcd.com/2381/) to define the implementation for a particular class.

OTOH, `__dict__` is the opposite of an abstraction -- whether you spell it vars(x) or `x.__dict__`, the matter remains that you're looking inside the implementation of the object, and what you get is not an abstraction -- as you've pointed out for `__slots__`, and as is also apparent for e.g. namedtuple or fundamental objects like numbers, strings and built-in container types.

By making the dominant spelling `__dict__`, we remind people that when they use this, they're tinkering with the implementation. Don't get me wrong, this can be fun and useful, but it's not a clean abstraction. The more fundamental abstraction is getattr()/setattr(), since it is supported by *all* objects, not just most classes.

--
--Guido van Rossum (python.org/~guido)