I had a similar idea ~8 years ago while working on a RAD (rapid application development) framework [*] that had to manage business objects using a variety of frameworks: an ORM (SQLAlchemy), a full-text engine (Whoosh), as well as a specifically developed CRUD Web UI framework, permission system, audit system, etc.
In this context, variable decorators, in addition to type annotations, could bring a whole new level of internal DSL expressivity, including for instance:
- Annotation to express data access and structural constraints (support for DBC e.g. "@constraints" or alternative constructs for some Attrs / Dataclass features)
- Annotations to express ORM features (à la Java's JPA): @Id, @NotNull, @OneToOne, @ManyToMany, etc.
- Annotations to express serialization (think Marshmallow or Pydantic)
- Annotation to express UI hints (e.g.: @widget(type=RichText, size="300px", max_length=500, color=GREEN, required=True, ...))
- Annotations to express full-text search indexability (e.g. @indexed)
- Annotation to express field-level permissions: @acl(...) or @permission(...)
This could provide more elegant, and better decoupled, syntax for things that are currently done using either metaclasses (e.g. Django ORM or SQLAlchemy ORM) or the "class Meta" idiom used by frameworks such as Django or Marshmallow.
[*]: since this was not available, we eventually went with an external DSL in combination with the "info" argument to the Column declaration in SQLAlchemy, used to pass metadata useful for our framework. This worked, but the code could have been much more elegant with variable decorators (in other words, it was quite ugly in some places and harder than necessary to maintain).
On Tue, May 25, 2021 at 3:39 AM micro codery email@example.com wrote:
Variable decorators have been suggested here before, as have new statements that could also achieve the same level of access to the binding name. However I propose a much more restricted syntax that would make for less edge cases where what is actually passed to the decorator callable may be ambiguous.
Basically this would add syntax to python that would transform @decorator("spam this") variable into variable = decorator("variable", "spam this")
The decorator would be limited to being on the same line as the variable name. Additionally, the variable can not also be assigned to on the same line, so @decorator("spam this") variable = eggs would be a SyntaxError. Annotating these variable would be allowed @decoratopr("spam this") variable: Food The actual decorator would follow the same rules as current decorators in that the parentheses can be omitted if no other arguments are required to the function and any arbitrary expression can be used, however the same restraint should be cautioned as is for current decorators post PEP 614.
The most straightforward use of this decorating would be that str() could be used without change to declare variables that are strings of the same name, generally cutting down on repeating yourself when declaring constants.
#instead of GREEN = "GREEN" @str GREEN
The classic example of a factory that needs its target name as a string value is namedtuple. I don't think this example is enough alone to sway this list but I bring it up as it has also been at the front of past ideas to allow variable decorating.
#instead of Point = namedtuple("Point", "x y z") @namedtuple("x y z") Point
I have sometimes heard that dataclasses make this no longer an issue, implying that dataclasses can be used in place of namedtuple in any new code. Putting aside that dataclasses are not a strict replacement for namedtuple, the dataclasses module continues this factory design with the make_dataclass function.
#instead of Point = make_dataclass("Point", [("x", int), ("y", int), ("z", int)]) @make_dataclass([("x", int), ("y", int), ("z", int)]) Point
Enum is one more case where there is a factory equivalent to the class declaration.
#instead of Colors = Enum("Colors", "RED GREEN BLUE") @Enum("RED GREEN BLUE") Colors
If you want each enum member value to match its name there is even more repetition, however by using the original example for decorated variables, this can be reduced when using the more common class style.
class Colors(Enum): @str RED @str GREEN @str BLUE
One final standard library module that I will mention because it seems to keep adding similar factory functions is typing. It already has the factory functions NewType, TypeVar, ParamSpec, TypedDict, NamedTuple. Their use with the new variable decorator would look much the same as earlier examples, but here would be one case where type hinting the variable in such a statement could aid the decorator function
#instead of UUIDType = NewType("UUIDType", str) @NewType UUIDType: str
If adopted, no change to these functions would be necessary to allow their use as a variable decorators as they all already take the name as a string in the first argument. Additionally, as variable decorators are actually callables just like the current decorators, no new restrictions are being forced on users of the language. If a user really wants to create a namedtuple with a typename of Foo but assign the result to a variable named ___trippleS33cret___name__here_ the language will not stop them.
I would very much like to know if any module maintainers are interested in this syntax. Convincing arguments from the standard lib (whether you consider the above convincing or not) are probably not enough to get a syntax change into python. But if enough third party modules want to use this functionality I believe that would be the real push to get it done.
Specifically this idea first started growing when I read on another ideas message that using the module sympy would often start with a line like x = symbol('x') which, with no change, could be used as a variable decorator @symbol x Now what may be even more typical (I really have never used the module myself) is defining all needed symbols on one line with x, y, z = symbol('x y z') which could conceivably be rewritten like @symbol x, y, z However I have not yet been able to decide if multi-assignment would be overall beneficial to this new syntax. Probably not, at least at this stage. It would not be hard for a decorator function to return multiple values, but how would the multiple names be passed in? A string with whitespace like this original, and namedtuple's field_names, is possible as no valid identifier can have a space in it. However, it forces any would-be variable decorator to know this and check if a space is in its first argument, even if it cannot produce multiple values. Another option is to pass in an iterable, but the same additional effort would be placed on decorators that otherwise don't want to deal with multi-assignment. Unpacking the names as the first n arguments would mean that decorators that just want one variable can get just that and those that may want more will also get them, but then practically no variable decorator could take additional non-keyword arguments.
Regards ~ Jeremiah _______________________________________________ Python-ideas mailing list -- firstname.lastname@example.org To unsubscribe send an email to email@example.com https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://firstname.lastname@example.org/message/2FOIN5... Code of Conduct: http://python.org/psf/codeofconduct/