Using a typing.NamedTuple object
, what is the best way to enforce additional constraints on how it can be declared?
Let's say I have a Undergraduate class where the students have have a major but I want to enforce that 'undeclared' is an unacceptable value for the major.
from typing import NamedTuple
class Undergraduate(NamedTuple):
name: str
major: str
def check_major(self):
if self.major == "undeclared":
raise ValueError("must declare a major")
if __name__ == "__main__":
u1 = Undergraduate("Jane", "computer science") # no errors
u1.check_major() # no errors
u2 = Undergraduate("John", "undeclared") # no errors
u2.check_major() # ValueError
This works fine but I would like for check_major()
to run every time I declare a new object, ie:
u1 = Undergraduate("John", "undeclared") # immediate ValueError raised
Is this possible using only a NamedTuple (I know how to do it using traditional classes)?
Note: I read this related question. These solutions provide somewhat of a working solution, but like the OP I want to be able to instantiate the objects without requiring additional class methods to be called.
NamedTuple
protects both __init__
and __new__
from being replaced at declaration. However, they can be replaced after the class was created.
class Radial2D(NamedTuple):
angle: float
length: float
def _verify_attributes_(self, *args):
if self.length < 0 or not 0 < self.angle < 360:
raise ValueError('Arguments out of range')
Radial2D.__init__ = Radial2D._verify_attributes_
print(Radial2D(90, 15.5)) # Radial2D(angle=90, length=15.5)
print(Radial2D(12, -5)) # ValueError: Arguments out of range
This pattern can be simplified using a class decorator:
from typing import Type, NamedTuple
def verify(tp: Type[NamedTuple]):
verifier = tp._verify_attributes_
tp.__init__ = verifier
return tp
@verify
class Undergraduate(NamedTuple):
name: str
major: str
def _verify_attributes_(self, *args):
if self.major == "undeclared":
raise ValueError("must declare a major")
print(Undergraduate("Jane", "computer science")) # Undergraduate(name='Jane', major='computer science')
print(Undergraduate("John", "undeclared")) # ValueError: must declare a major