Open zplizzi opened 2 years ago
What you want is impossible for the reason alone that you can't apply decorators to type annotations. Type annotations don't really exist in the classical sense (you can't use them for our decorator syntax of defaults and validators for that reason).
I'm also not 100% sure what you're trying to achieve. Do you want to crate an abstract class that defines fields and then fill them out in the body of the child class? Why not use an instance?
P.S. are you aware that you can just do @attr.frozen
(or@attrs.frozen
)?
I don't know if this helps, but one thig I have done when I really want to have an abstract "attribute/field" (in quotes because I'm sort of faking it here) is:
from abc import ABC, abstractmethod
import attrs
@attrs.frozen(kw_only=True, slots=False)
class Parent(ABC):
@abstractmethod
def param(self):
pass
@attrs.frozen(kw_only=True, slots=False)
class Child(Parent):
param = attrs.field(default="test")
Yeah, the abstractmethod isn't attrs-specific (or even attribute/field specific), but so far it has worked for me and made I don't forget to define a given attribute/field in a subclass.
HTH, KC
Yeah, understood - I figured this wouldn't really be possible at least with the syntax I suggested. But @hkclark I appreciate your suggestion - definitely hacky but it gets at what I was looking for. Maybe there is some other syntax that could be added to get this functionality in a neater way?
Here's a little example of what I'm trying to do (sorry for the silly example, it's lunchtime!):
@attr.frozen
class FoodTruck:
@abstract
food_kind: str
num_wheels: int = 4
has_grill: bool = False
serves_alcohol: bool = False
@attr.frozen
class HalalFoodTruck(FoodTruck):
@override
food_kind = "halal"
@override
has_grill = True
rice_kind: str = "basmati"
sauce_kind: List[str] = ["white"]
@attr.frozen
class DeluxeHalalFoodTruck(HalalFoodTruck):
@override
num_wheels = 8
@override
serves_alcohol = True
@override
sauce_kind = ["white", "spicy", "teriyaki"]
The reason for not using an instance is, for example, you can't further subclass an instance, but you can with a class. And with the subclass approach, you can both change default attributes / fill in abstract attributes, and also add new attributes, all in the same "container".
I'm just on my phone, but what happens if you declare the attributes as typing.ClassVar[str]
? Attrs will leave them alone and that should be what you're asking for?
Hmm, sorta - but not quite. I lose the ability to do Child(param="stuff")
, and it also doesn't solve the "avoiding typos" or "making clear which attrs in the child are overrides vs new". And a reader wouldn't know that typing.ClassVar[str]
means "this parameter must be overridden".
I'd love to do something like:
where you wouldn't be able to create an instance of a class with
@abstract
decorators, and you wouldn't be able to put the@override
decorator on something that wasn't defined in the parent.I commonly use
attrs
classes to hold configuration parameters, and allowing this functionality would make it much safer and more readable. Currently it's too easy to accidentally think you're overriding something inChild
that's actually not defined inParent
(typo, got removed, etc) - and it's not clear when readingChild
whether an attribute is new to the child or just overriding a default value from the parent. And leaving a value without a default inParent
is a little annoying because you have to put all the attrs without default first (which is not a big deal, but disrupts the logical grouping of members of the class).I have no idea if this is remotely possible, so feel free to close if not!