Andrei Moiseev
Andrei Moiseev

Reputation: 4084

A group of constants in Python

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:

Python enums do not seem to be suitable, because:

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

Upvotes: 2

Views: 1250

Answers (3)

qouify
qouify

Reputation: 3920

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.

Upvotes: 1

Axe319
Axe319

Reputation: 4365

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))

Upvotes: 2

jsbueno
jsbueno

Reputation: 110561

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

Upvotes: 3

Related Questions