How to limit possible options to be chosen when instantiating class objects? Let's say we have a class like below and we'd like to restrict value
values only to be predefined as in the valueOptions
list. My implementation attempt is as below. Is there more Pythonic way to achieve the same result?
class TestClass:
valueOptions = ['A value', 'The value', 'Another value']
def __init__(self, value):
self.value = value
@property
def value(self):
return self._value
@value.setter
def value(self, val):
if val not in self.valueOptions:
raise ValueError('Value not in available options.')
self._value = val
def __str__(self):
return f'Value: {self.value}'
v1 = TestClass('A value') # line 20
v2 = TestClass('Value') # line 21
After run:
Traceback (most recent call last):
File "<string>", line 21, in <module>
File "<string>", line 5, in __init__
File "<string>", line 14, in value
ValueError: Value not in available options.
CodePudding user response:
Use Enum instead of str if you need a limited values
The core issue is that you (implicitly; your code isn't typed) define value
as being a string type. But you don't need a string: you need a limited list of possible values. String is not the right variable type to code that.
The enum module allows you to define a enumeration type (class) with a restricted number of options / values.
This actually also solves another issue: the values in your example are hard coded, creating "magic numbers" (or in this case: "magic strings") in your code. Your code will eventually lead to scattered instances of 'A value', 'The value' and 'Another value' in lots of different places. If you (at some future point) decide to replace 'The value' to 'The best value', you have a code maintenance problem...
Here is a code example using enums (and type hints):
from enum import Enum
class ValueOptions(Enum):
A_VALUE = 'A value',
THE_VALUE = 'The value',
ANOTHER_VALUE = 'Another value'
class TestClass:
def __init__(self, value: ValueOptions) -> None:
self.value = value
def __str__(self) -> str:
return f'Value: {self.value.name}'
v1 = TestClass(ValueOptions.A_VALUE)
print(v1)
v2 = TestClass(ValueOptions.ILLEGAL_VALUE)
print(v2)
This leads to valid code for v1, and to an error for v2:
Traceback (most recent call last):
File ".code.tio", line 19, in <module>
v2 = TestClass(ValueOptions.ILLEGAL_VALUE)
File "/usr/lib64/python3.7/enum.py", line 349, in __getattr__
raise AttributeError(name) from None
AttributeError: ILLEGAL_VALUE
This error will not only show at runtime, but (even better!) when writing your code. Any Python code checker (such as mypy
) will detect that I used an illegal attribute:
❯ mypy ./enum_test.py
enum_test.py:21: error: "Type[ValueOptions]" has no attribute "ILLEGAL_VALUE"
Found 1 error in 1 file (checked 1 source file)
Best of all: this code is a bit shorter than your version! ;-)