On Tue, 22 Dec 2020 18:27:01 +1100 Steven D'Aprano email@example.com wrote:
On Mon, Dec 21, 2020 at 04:36:21PM +0300, Paul Sokolovsky wrote:
I posted on python-dev a question regarding builtin vars() vs .__dict__ attribute dichotomy: https://firstname.lastname@example.org/thread/JAFIBBUU5...
It could as well be one among the earliest cases of the violation of "There should be one-- and preferably only one --obvious way to do it."
(1) `vars` predates the Zen of Python (vars goes back to at least Python 1.4 in 1996; the Zen dates back to 1999);
(2) and `vars` *is* the "one obvious way" to access an object's internal namespace (dunder names are reserved for the Python interpreter; the public API to access an object's symbol table is `vars(obj)`)
Thanks for the reply and these points, but the point for .__dict__ is missing. And I don't know if I made my preference see-thru enough, but .__dict__ is what I hate ;-).
I don't think that it counts as a violation. But even if it does, well, the Zen is mostly intended to be light-hearted, almost a joke, and not as gospel; we take the koans overly seriously at our peril.
Sure, but even if the koan of Python appeared after vars(), it's common sense that such koans don't appear all at once and represent U-turn in how things are done. Vice-versa, they appear to capture existing practices. Which gets us back to the question - which of vars() vs .__dict__ is "more primary" and why they managed to coexist for so long, without anyone doing something to that duality (successfully at least).
And note that I'll definitely end up grepping the old commits, but I don't rush with that so far, because I know that old commits are usually not detailed enough - there're likely will be big code drop with both of them, that's it. And I actually more interested not to establish an objective fact (vars() appeared 199X-XX-XX, .__dict__ on 199Y-YY-YY), but what old-timers have in memory about that, including any attempts to address that situation.
(Seriously, how much *more obvious* can we get than a builtin function? You don't have to import it, it is always available.)
That's true, and a few StackOverflow questions in first google hits are quite unanimously suggest vars(). The question is again what's in minds of majority of Python users though.
On the other hand, *obvious* is not the same as *well known*. I expect that many people aren't aware of, or forget, the existence of `vars` and use `obj.__dict__` directly. That's so common that I expect we have no hope of making `__dict__` a *private* implementation detail, even though not all objects, or even all classes, have a `__dict__`.
Well, that's why I in the original python-dev post wrote "CPython 10". But there's another side of the story - various subsets and dialects. E.g., what Brighton https://brython.info/ (random example, I don't know its state/stance re: that) should implement, assuming they have an ambition to implement "only the right one" (as you write programs for such impls from scratch anyway, or *port* existing code)?
Such earlier, that it could as well turn out that this principle was never thoroughly followed by Python at all.
Anyway, how the question pops up, is that there's very rare when there's a need to access *writable* object namespace as dictionary. One of such cases is executing code in a module context, where you need to pass module's globals to exec(), and so I wonder what's "the most canonical way" of getting that - var(mod) or mod.__dict__.
I expect the most common way is `globals()`.
globals() returns current module's globals. I was talking on how to execute code in the context of another module (such a task is mundane and avoidable in import handling, e.g. writing custom importers).
The only way to switch globals to another module is to execute a function from it. But we start with empty module and try to bootstrap it when we perform an import. Actually wait, we can stuff a "bootstrap" function in a module just to switch to the module context, and then execute code in now-current context with something like:
mod.__init__ = lambda source: exec(source) mod.__init__(source) del mod.__init__
That's why such discussions are useful - I wouldn't think about that possibility unless I elaborated counter-arguments to what you wrote ;-).
But thinking about it, the problem lies on the exec()'s side. If exec could take a module object directly as its "globals" param, there wouldn't be a need to expose internal namespace implementation of the module object.
I expect that there is plenty of third-party code that expects to be able to access `module.__dict__` either directly or via `vars`, since that is something that has been documented as working for many releases.
Yes, but the question for what they use it. Above, it seemed that access to module's namespace dict is unavoidable to perform an import and yet (relatively elegant) workaround was found. __dict__/vars is clearly needed for reflection, but there're tons of programs can be written without reflection (as all languages which lack it suggest).
It's all about the question "What is the core of Python?" https://snarky.ca/what-is-the-core-of-the-python-programming-language/ In this regard, neither vars() nor .__dict__ appear to be in that core, as even such an advanced matter as executing code in another module context can be done without them.
So, I wonder what old-timers would think of the following signature:
exec(object[, globals_or_module[, locals]])
I don't hate it, I just don't see any advantage to adding such complexity to the signature just for the sake of avoiding a function call or dot lookup.
Per above, it actually would be simplification, as you can drop vars() and .__dict__ from language and still perform import handling. But yeah, looks that I don't need to change anything on the Pycopy side right away, and better explore that "switch-to-module-and-exec" idiom above.