0

I'm searching for a collection that can be used to store multiple constants and also can be used to get a list of them?

EDIT: It does not have to be constants, it can be variables.

I want to get a value this way:

Constants.first_one

But also get a list of values like:

Constants.values 

Which returns something like:

['First one', 'Second one' .... ]

Using a class and class attributes is good for the first usage but not for the second one:

class Constants:
    first_one = 'First one'
    second_one = 'Second one' 

To get a list of attributes I'd need to make a method that does that.

I'm also considering named tuple but I again don't know how to get the list of values:

class Constants(typing.NamedTuple):
    first_one = 'First one'
    second_one = 'Second one' 

EDIT:

Enum does not seem to have such option neither and moreover I need to get values this way Constants.first_one.value

EDIT2:

Basically, all I want is to know if there is some collection that behaves like this:

class Properties:
    PHONE = 'phone'
    FIRSTNAME = 'firstname'
    LASTNAME = 'lastname'

    @classmethod
    @property
    def values(cls):
        return [x for x in dir(cls) if not x.startswith('__') and x != 'values']


print(Properties.PHONE)
> phone
print(Properties.FIRSTNAME)
> firstname
print(Properties.values)
> ['phone', 'firstname', 'lastname']
Milano
  • 18,048
  • 37
  • 153
  • 353
  • 1
    Does this answer your question? [How to make dictionary read-only in Python](https://stackoverflow.com/questions/19022868/how-to-make-dictionary-read-only-in-python) – Thomas Weller Dec 08 '21 at 11:35
  • @ThomasWeller I don't think so. It does not have to be non-editable but I want to be able to call values as attributes (not by getitem) as I want IDE to autocomplete the variables. – Milano Dec 08 '21 at 11:37
  • Then I got the word "constant" wrong. For me, a constant is non-editable. The IDE syntax highlighting and autocompletion is also an aspect that is not mentioned in the question yet. – Thomas Weller Dec 08 '21 at 11:42
  • [`dir()`](https://docs.python.org/3/library/functions.html#dir) may be related: it lists all names. You just need to exclude the dunder ones (`__`) – Thomas Weller Dec 08 '21 at 11:45
  • @ThomasWeller Yes, that's one way to do that. I was curious if there is something built-in or some module that does that. I've added an example at the bottom of the question now. – Milano Dec 08 '21 at 11:50

2 Answers2

0

Given that no one answered yet heres my two cents:

A) You're going the wrong way around.

Instead of a @property returning a list of your attributes, implement a __getattr__/__setattr__ to access your stored data by attribute (by dot-notation).

B) Take a look at something called a 'bunch'.

Bunches are a subclass of a dict, that expose their content as attribute while retaining the benefits of a dictionary. E.g. they provide the .values() method, allow key-access and attribute-access, and they are true dictionaries. There are multiple libraries implementing their own versions of it, for example "bunch" on pypi or github.

Example

The easiest implementation of a bunch, which I can come up with, is done by mangling the instance into its own __dict__ slot. Given its simplicity, I imagine it being somewhat common, even though it is certainly not the best:

class Bunch(dict):
    def __init__(self, *args, **kwargs):
        super(Bunch, self).__init__(*args, **kwargs)  # make us behave like a dict
        self.__dict__ = self  # we are our namespace => dict_access IS attr_access

A better version would add the actual data in a (possibly private) attribute and implement custom __getattr__/__setattr__/__delattr__ as mentioned above. This need no longer be a 'bunch', and indeed in your case a bunch would probably be unnecessary...

class NotABunchCollection:
    def __init__(self, *args, **kwargs):
        self.data = dict(*args, **kwargs)
    def __getattr__(self, key):
        """attribute-access. Only called if key is not found in normal places."""
        try:
            return self.data[key]
        except KeyError:  # missing keys => attribute error, not key error
            raise AttributeError(key)
    def __setattr__(self, key, value):
        """To attribute-style assign"""
        self.data[key] = value
    def __setattr__(self, key, value):
        """To attribute-style delete"""
        del self.data[key]
    def values_list(self):
        """Wraps values to a list. could also be a @property"""
        return list(self.data.values())

... but as stated above in B) there are libraries that do that for you. The 'bunch' type would solve your problem (even my crude one) and the 'bunch'-library mentioned above would add functionality you might find useful (like 'bunchify', 'to_yaml', 'from/to dict', etc.).

There may also be options with pydantic or with data classes.

Teck-freak
  • 127
  • 8
0

The collections.namedtuple almost fit your definition, except for an alternate way to get the values.

import collections


def make_collection(**kwargs):
    Blueprint = collections.namedtuple("Collection", kwargs)
    collection = Blueprint(**kwargs)
    return collection


contact = make_collection(
    PHONE="phone",
    FIRSTNAME="firstname",
    LASTNAME="lastname",
)

print(f"Contact: {contact}")
print(f"Phone: {contact.PHONE}")
print(f"First Name: {contact.FIRSTNAME}")
print(f"Last Name: {contact.LASTNAME}")
print(f"Values: {list(contact)}")
print(f"Fields: {contact._fields}")
print(f"As Dictionary: {contact._asdict()}")

Output:

Contact: Collection(PHONE='phone', FIRSTNAME='firstname', LASTNAME='lastname')
Phone: phone
First Name: firstname
Last Name: lastname
Values: ['phone', 'firstname', 'lastname']
Fields: ('PHONE', 'FIRSTNAME', 'LASTNAME')
As Dictionary: {'PHONE': 'phone', 'FIRSTNAME': 'firstname', 'LASTNAME': 'lastname'}

Notes

  • The make_collection function is there for your convenient. It provides a simple way to return a named tuple
  • In order to get a list of values, just call list() on the collection
Hai Vu
  • 37,849
  • 11
  • 66
  • 93