8

I had namedtuple variable which represents version of application (its number and type). But i want and some restriction to values:

Version = namedtuple("Version", ["app_type", "number"])
version = Version("desktop") # i want only "desktop" and "web" are valid app types
version = Version("deskpop") # i want to protect from such mistakes

My solution for now is primitive class with no methods:

class Version:
    def __init__(self, app_type, number):
        assert app_type in ('desktop', 'web')

        self.app_type = app_type
        self.number = number

Is it pythonic? Is it overkill?

Reblochon Masque
  • 33,202
  • 9
  • 48
  • 71
keran
  • 103
  • 5
  • Why are you assigning `Version` to a namedtuple, and also declaring a class with that same variable? – JacobIRR Jun 26 '19 at 19:46
  • 1
    @JacobIRR I assume the class version is an *alternative* implementation – Chris_Rands Jun 26 '19 at 19:56
  • 1
    If the only two valid app types are `"desktop"` and `"web"` consider making an `Enum` class to specify that. Then instead of passing a raw string into your `namedtuple` you pass in a `AppType` enum. – Kyle Parsons Jun 26 '19 at 20:05
  • 4
    A class seems fine. If you were using a namedtuple to save memory, just use `__slots__` – juanpa.arrivillaga Jun 26 '19 at 20:12
  • I agree with @juanpa — class is the way to do it. If you're using Python 3.7+, consider using a `dataclass` — see [Validating detailed types in python dataclasses](https://stackoverflow.com/questions/50563546/validating-detailed-types-in-python-dataclasses). – martineau Jun 26 '19 at 20:19
  • 3
    I would avoid using `assert` in this case. Raising a `ValueError` seems more appropriate here. https://stackoverflow.com/a/945135/10863327 – Kyle Parsons Jun 26 '19 at 20:26
  • 1
    However you do it, consider making `app_type` and [`enum`](https://docs.python.org/3/library/enum.html#module-enum) to limit the permissible values. – martineau Jun 26 '19 at 20:28
  • People ask "is this Pythonic?" instead of asking "is this good OOD?" now. Sigh. – spinkus Jun 26 '19 at 20:31
  • @spinkus I think "pythonic" can be treated as "good OOD in python" – keran Jun 27 '19 at 10:42
  • Not sure that's possible ... ;). – spinkus Jun 27 '19 at 20:25

1 Answers1

9

You could use enum.Enum, and typing.NamedTuple instead of collections.namedtuple:

Maybe something like this:

from typing import NamedTuple
import enum

class AppType(enum.Enum):
    desktop = 0
    web = 1

class Version(NamedTuple):
    app: AppType


v0 = Version(app=AppType.desktop)
v1 = Version(app=AppType.web)

print(v0, v1)

output:

Version(app=<AppType.desktop: 0>) Version(app=<AppType.web: 1>)

A undefined AppType raises an AttributeError:

v2 = Version(app=AppType.deskpoop)

output:

AttributeError: deskpoop
martineau
  • 112,593
  • 23
  • 157
  • 280
Reblochon Masque
  • 33,202
  • 9
  • 48
  • 71
  • 1
    Thank you for reminding use enums. I'll accept this) – keran Jun 27 '19 at 10:45
  • 1
    It may not be important, but using `Version(app=AppType.deskpoop)`, similar to what was done in first two examples, would have resulted in an `AttributeError: deskpoop`. The `NameError` is occurring because you used `Version(AppType=deskpoop)` in the third example. – martineau Jun 27 '19 at 21:37
  • Thank you for that @martineau, good catch. I think it is important, I modified my answer. Incidentally, the `AttributeError` message raised by `Enum` is rather terse: the traceback is okay, but the message could contain the name of the class it was raised from. – Reblochon Masque Jun 28 '19 at 00:48