A group of constants in Python

Viewed 592

I want something like this:

class Fruits:
   APPLE = 'APPLE'
   ORANGE = 'ORANGE'
   MANGO = 'MANGO'

# I want to access members easily
print(Fruits.APPLE)

# I want to list all members
print(list(Fruits))

# I want to check if a string belongs to the set
print('POTATO' in Fruits)

I want plain string constants, grouped into a class with:

  • member check
  • listing all values

Python enums do not seem to be suitable, because:

  • Enums are not plain strings, introduce a new type
  • Enums require using .value
  • Not directly serializable and are otherwise clumsy.

Is this implementable in Python? I suppose I might need some magic methods or metaclasses for this?

3 Answers

You want Python "enums":

In [7]: from enum import Enum                                                                    

In [8]: class Fruits(Enum): 
   ...:    APPLE = 'APPLE' 
   ...:    ORANGE = 'ORANGE' 
   ...:    MANGO = 'MANGO' 
   ...:                                                                                          

In [9]: print (Fruits.APPLE)                                                                     
Fruits.APPLE

In [10]: list(Fruits)                                                                            
Out[10]: [<Fruits.APPLE: 'APPLE'>, <Fruits.ORANGE: 'ORANGE'>, <Fruits.MANGO: 'MANGO'>]

For containment checking, though, you can't use the string directly - evaluating "APPLE" in Fruits directly would raise a TypeError, but this is possible:


In [18]: "APPLE" in Fruits.__members__                                                           
Out[18]: True

# or:
In [15]: Fruits("APPLE")                                                                         
Out[15]: <Fruits.APPLE: 'APPLE'>

And, as for the part of your questions that goes:

Is this implementable in Python? I suppose I might need some magic methods or metaclasses for this?

Yes, it is. And yes, it needs both - but it was all done back by Python 3.4

If you want to implement yourself you can do so with a metaclass and an __iter__ method.

class Meta(type):
    def __new__(cls, name, bases, dct):
        # this just iterates through the class dict and removes
        # all the dunder methods
        cls.members = [v for k, v in dct.items() if not k.startswith('__') and not callable(v)]
        return super().__new__(cls, name, bases, dct)
    
    # giving your class an __iter__ method gives you membership checking
    # and the ability to easily convert to another iterable
    def __iter__(cls):
        yield from cls.members

class Fruits(metaclass=Meta):
    APPLE = 'APPLE'
    ORANGE = 'ORANGE'
    MANGO = 'MANGO'


print(Fruits.APPLE)
print('APPLE' in Fruits)
print(list(Fruits))

A very basic solution with a meta-class:

class EnumClass(type):

    def __iter__(cls):
        for key in cls.__dict__:
            if not key.startswith('__'):
                yield cls.__dict__[key]

    def __contains__(cls, item):
        return item in cls.__dict__ and not item.startswith('__')


class Fruits(metaclass=EnumClass): 
    APPLE = 'APPLE'
    ORANGE = 'ORANGE'
    MANGO = 'MANGO'

# I want to access members easily
print(Fruits.APPLE)

# I want to list all members
print(list(Fruits))

# I want to check if a string belongs to the set
print('POTATO' in Fruits)
print('APPLE' in Fruits)

It's very simplistic and has flaws (e.g., you cannot add methods in Fruits) but it's ok for the use cases you mention.

Related