On Thu, Sep 17, 2020 at 08:34:26PM -0000, Joseph Perez wrote: > A lot of libraries use string for attribute names to do some "dynamic" > things. A typical example are SQLAlchemy > [validators](https://docs.sqlalchemy.org/en/13/orm/mapped_attributes.html#simple-validators): > ```python > from sqlalchemy.orm import validates > > class EmailAddress(Base): > __tablename__ = 'address'
Dunder names are reserved for use by the Python interpreter. Your code here is already on very shaky ground. > id = Column(Integer, primary_key=True) > email = Column(String) > > @validates('email') # Here > def validate_email(self, key, address): > assert '@' in address > return address > > ``` > In this example of SQLAlchemy documentation, email validator use > `"email"` string in order to associate the validator to the `email` > column. > > However this dynamic things don't play well with all static tools like > linters, but especially IDE (for usages finding, navigation, > refactoring, etc.), and of course type checkers. This is the trouble with writing "clever" code with large amounts of implicit state and/or dynamicism, it makes it difficult for static tools and the human reader. > This issue could be solved with a "magic attribute" `__attrs__` (name > can be discussed), used the following way: I don't see how this would solve the issue. The problem is that your code is too dynamic for static tools, so you are proposing to make it **even more dynamic**. > ```python > @dataclass > class Foo: > bar: int > > foo = Foo(0) > assert getattr(foo, Foo.__attrs__.bar) == 0 > assert getattr(foo, foo.__attrs__.bar) == 0 > ``` Presumably these would work too: assert getattr(foo, None.__attrs__.bar) == 0 assert getattr(foo, foo.__attrs__.baz, 999) == 999 Your proposal is to have a magic dunder, spelled '__attrs__` (note the two additional dots) which converts a attribute access into a string: spam.__attrs__.eggs => 'eggs' so you can pass the 'eggs' string to getattr: # Instead of this: getattr(spam, 'eggs') # this is more magical: getattr(spam, spam.__attrs__.eggs) Apart from typing 15 chars to avoid 2 quotation marks, and making a runtime attribute lookup plus method call to avoid a literal, how will this help reduce dynamicism? If linters etc have difficulty dealing with getattr() using a string literal, how will they do better at even more complex, even more dynamic, code? > To make it usable in class declaration, the `__attrs__` symbol should > be added to class declaration namespace: > > ```python > class Foo: > bar: int > @validator(__attrs__.bar) > def validate(self): > ... > ``` So here we have `__attrs__` is not just a dunder attribute, but also a dunder built-in name. > No check would be done by `__attrs__`, they are let to linters which > would integrate this language feature and check for the presence of > the attribute in the class/instance. So linters that cannot cope with `@validate('email')` will be able to cope with `@validate(__attrs__.email)`, because ... why? I don't think that static tools have trouble with dynamic code because they cannot parse a string literal like `'email'`. Removing that literal with more dynamic code like `__attrs__.email` is not going to solve the problem. [...] > To sum up, `__attrs__` would be a pseudo-static wrapper to attribute > name retrieving in order to benefit of static tools (refactoring, > usages finding, navigation, type checking), with a straightforward and > backward compatible implementation. You have made a lot of claims that this will help static tools. I do not believe it will help. I do not expect that static tools that cannot analyse dynamic code when given a string literal in a dynamic context will magically start working when you replace that string literal with an extra level of dynamic code. To me, this just adds one more step to any static tool: any time the tool, or the human reader, sees __attrs__.spam mentally erase the "__attrs__." and quote the remaining: 'spam' and then proceed as normal from that point. Likewise for attributes: foo.__attrs__.spam If `foo` exists, erase "foo.__attrs__." and quote the remaining part, then proceed as normal. Otherwise raise NameError. Essentially this `__attrs__` is a verbose way of spelling string literals, and can be removed with a pretty simple text substitution. -- Steve _______________________________________________ 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/O5MGEXUMVXDDZZUTVCLKC5U7SN7SQYGY/ Code of Conduct: http://python.org/psf/codeofconduct/