'Is it good practice to store attributes in an Enum?

I have an Enum representing 16 features that appear in a 7x6 grid, where each cell contains two features. Each feature appears 5 times in the grid. The grid is static, i.e. each cell always has the same features in it.

I was then wondering what is the best way (in terms of design) to store this mapping. In the Enum itself as an attribute or in the Grid class as a class attribute? See examples below:

1: Enum attribute

from enum import Enum
from __future__ import annotations
from typing import Tuple

class Feature(Enum):
    FOO: ('LONG_NAME_OF_FOO', ((0,0), (1,5), (3,4), (4,0), (5,3)))
    
    def __new__(cls, *args, **kwargs) -> Feature:
        entry = object.__new__(cls)
        entry._value_ = len(cls.__members__) + 1
        return entry

    def __init__(self, long_name: str, coords: tuple) -> None:
        self.long_name = long_name
        self.coords = coords

    @classmethod
    def find_features(cls, row: int, col: int) -> Tuple[Feature]:
        found = []
        for feature in list(cls):
            if (row,col) in feature.coords:
                found.append(feature)
            if len(found) == 2:
                return tuple(found)
        raise ValueError(f"Only found {len(found)} features. Expected 2.")

class Cell:
    def __init__(self, row: int, col: int) -> None:
        self.row = row
        self.col = col
    def __repr__(self) -> str:
        return f"<Cell at ({self.row},{self.col})>"

class Grid:
    def __init__(self) -> None:
         self.cells = [[Cell(x,y) for y in range(6)] for x in range(7)]

2: Grid attribute

from enum import Enum
from __future__ import annotations
from typing import Tuple

class Feature(Enum):
    FOO: 'LONG_NAME_OF_FOO'

    def __new__(cls, *args, **kwargs) -> Feature:
        entry = object.__new__(cls)
        entry._value_ = len(cls.__members__) + 1
        return entry

    def __init__(self, long_name: str) -> None:
        self.long_name = long_name

class Cell:
    def __init__(self, row: int, col: int) -> None:
        self.row = row
        self.col = col
    def __repr__(self) -> str:
        return f"<Cell at ({self.row},{self.col})>"

class Grid:
    feature_map: {
        Feature.FOO: ((0,0), (1,5), (3,4), (4,0), (5,3))
    }
    
    def __init__(self) -> None:
        self.cells = [[Cell(x,y) for y in range(6)] for x in range(7)]

    def find_features(self, row: int, col: int) -> Tuple[Feature]:
        found = []        
        for feature, coords in self.feature_map.items():
             if (row,col) in coords:
                 found.append(feature)
             if len(found) == 2:
                 return tuple(found)
        raise ValueError(f"Only found {len(found)} features. Expected 2.")

So which one is generally the better way? On one hand, the Feature itself doesn't need to know where it is in the Grid, so it seems more natural to have the mapping be an attribute of Grid. On the flip side, I feel like making the mapping an attribute of Feature makes thing more neatly, organized (or encapsulated, if I'm not misusing the term here).

What do you think?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source