On Mon, May 31, 2021 at 09:12:42PM -0400, David Mertz wrote:
I think you are going against the design of the language here. With only
a handful of critical exceptional cases (None, True, False are the only
ones that come to mind), names can be rebound.
The following genuinely surprised me. I was trying to show something different in reply, but I think the actual behavior makes the point even more:
This is *strange* behavior. I don't expect every sequence of characters to round trip `eval(repr())`, but I kinda expect it to be mostly idempotent.
It's pretty standard behaviour for Python.
>>> err = ValueError('something went wrong') >>> ValueError = list >>> eval(repr(err)) ['s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', ' ', 'w', 'e', 'n', 't', ' ', 'w', 'r', 'o', 'n', 'g']
The repr of an object tells you what the object thinks it is called; but `eval` evaluates the given source code according to whatever name bindings happen to exist at the time, which can shadow or monkey-patch the names that were used to generate the object in the first place.
>>> eval(repr(NotImplemented)) NotImplemented >>> NotImplemented = len >>> eval(repr(str.__lt__('', None))) <built-in function len>
The lesson here is not to rebind names if you don't want eval to use the rebound names :-)
Right now, `eval(repr(...))` works unless you have shadowed the name Ellipsis. If we make the proposed changed, it will call whatever arbitrary object was bound to Ellipsis, so the best you can hope for is a TypeError:
>>> eval('Ellipsis (...)') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1, in <module> TypeError: 'ellipsis' object is not callable
Unless we make the ellipsis object `...` callable. But that doesn't help us if the name Ellipsis has been rebound. Then it will call the rebound object.
I have to emphasise that this is standard, normal behaviour, and I still don't know why we are disturbed by Ellipsis behaving like nearly everything else in Python. The only oddity is that unlike most things, Ellipsis also has a special symbol `...` that most objects don't have.
Let's change the behavior of the Ellipsis object slightly to have either a .__call__() or .__getitem__() method that returns itself, no matter what argument is passed.
"Errors should never pass silently."
class Ellipse(Shape): ...
myshape = Ellipsis(mysquare) # oops # much later on... myshape.draw() # TypeError
Okay, it's a bit of a contrived set of circumstances, but it demonstrates the idea that functions should not ignore their arguments. Exceptions should, if possible, occur as close as possible to the actual error (the use of Ellipsis instead of Ellipse).
If the parameter to a function has no effect, it shouldn't be a parameter. If you pass an invalid argument to the (hypothetical) callable Ellipsis, it should raise TypeError immediately, not ignore the argument.
So we're trying to fix an issue of next to no practical importance, that doesn't really affect anyone, by building in more complexity and weird corner cases in the language. I don't think that's a good trade off. Shadowing and monkey-patching are advanced techniques, and we shouldn't demand the language protect newbies who accidentally shadow the built-in Ellipsis and then try to use eval.
Not every builtin needs a mollyguard to protect against misuse.