HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Enum usage to restrict class attribute values

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
enumusageattributevaluesrestrictclass

Problem

I am using enums in python3 for the first time but unsure if I am using them correctly.

I want to restrict the 'allowed values' for a class attribute ArticleMetadata.article_type to those defined within an ArticleTypes Enum class.

Here's my code:

from enum import Enum

class ArticleMetadata(object):

    ArticleTypes = Enum('ArticleTypes', 'original modified', module=__name__)

    def is_in_enum(self, value, Enum):
        """Verify that a value is contained within a defined Enum object.

        Raises:
            ValueError: If the value is not contained within the defined Enum.
        """
        if value in Enum.__members__:
            return True
        else:
            raise ValueError("Value {0} not in Enum list.".format(value))

    def __init__(self, **kwargs):
        """Set class attributes."""

        self.article_type = (kwargs['article_type'] if 'article_type' in kwargs.keys()
                              and self.is_in_enum(kwargs['article_type'], self.ArticleTypes)
                              else None
                              )


Usage

> foo = ArticleMetadata(article_type='original')
> foo.article_type
'original'
> bar = ArticleMetadata(article_type='invalid')
*** ValueError: Value invalid not in Enum list.


This code works, but having to write an is_in_enum method seems clunky, and I would have thought that this functionality would be natively part of enums somehow??

Also, no errors are raised if I later run foo.article_type = 'invalid', which doesn't seem right!

Am I missing a common usage pattern?

Solution

I think for it to be completely safe (even against re-assigning later), you should make AtricleMetadata.article_type a property:

from enum import Enum

class ArticleMetadata(object):

    ArticleTypes = Enum('ArticleTypes', 'original modified', module=__name__)

    def __init__(self, **kwargs):
        """Set class attributes."""
        self.article_type = kwargs.get('article_type', None)

    @property
    def article_type(self):
        return self._article_type

    @article_type.setter
    def article_type(self, value):
        if value in self.ArticleTypes.__members__:
            self._article_type = value
        else:
            raise ValueError("Value {} not in ArticleType list.".format(value))


In this case you don't need your helper function any more (at the price of losing this general function and having to write a getter and setter for every property for which you want this kind of protection). Note that I used dict.get to make your initialization easier.

Also note that this can still be tricked, because Python has no real private members. So you can still do this:

>>> a = ArticleMetadata(article_type="original")
>>> a._article_type = "invalid"
>>> a.article_type
'invalid'

Code Snippets

from enum import Enum

class ArticleMetadata(object):

    ArticleTypes = Enum('ArticleTypes', 'original modified', module=__name__)

    def __init__(self, **kwargs):
        """Set class attributes."""
        self.article_type = kwargs.get('article_type', None)

    @property
    def article_type(self):
        return self._article_type

    @article_type.setter
    def article_type(self, value):
        if value in self.ArticleTypes.__members__:
            self._article_type = value
        else:
            raise ValueError("Value {} not in ArticleType list.".format(value))
>>> a = ArticleMetadata(article_type="original")
>>> a._article_type = "invalid"
>>> a.article_type
'invalid'

Context

StackExchange Code Review Q#162513, answer score: 3

Revisions (0)

No revisions yet.