Home > Software design >  How to get all values of variables in class?
How to get all values of variables in class?

Time:03-20

class NiceClass():
    some_value = SomeObject(...)
    some_other_value = SomeOtherObject(...)

    @classmethod
    def get_all_vars(cls):
        ...

I want get_all_vars() to return [SomeObject(...), SomeOtherObject(...)], or more specifically, the values of the variables in cls.

Solutions tried that didn't work out for me:

  • return [cls.some_value, cls.some_other_value, ...] (requires listing the variable manually)
  • subclassing Enum then using list(cls) (requires using some_value.value to access the value elsewhere in the program, also type hinting would be a mess)
  • namedtuples (nope not touching that subject, heard it was much more complicated than Enum)
  • [value for key, value in vars(cls).items() if not callable(value) and not key.startswith("__")] (too hacky due to using vars(cls), also for some reason it also includes get_all_vars due to it being a classmethod)

CodePudding user response:

There are two ways. This is a straight answer to your question:

class Foo:
    pass


class Bar:
    x: int = 1
    y: str = 'hello'
    z: Foo = Foo()

    @classmethod
    def get_all(cls):
        xs = []
        for name, value in vars(cls).items():
            if not (name.startswith('__') or isinstance(value, classmethod)):
                xs.append(value)
        return xs

This is what I suggest:

from dataclasses import dataclass, fields


class Foo:
    pass


@dataclass
class Bar:
    x: int = 1
    y: str = 'hello'
    z: Foo = Foo()

    @classmethod
    def get_defaults(cls):
        return [f.default for f in fields(cls)]

    @classmethod
    def get_all(cls):
        return [getattr(cls, f.name) for f in fields(cls)]

results:

Bar.get_defaults() == Bar.get_all()
# True -> [1, 'hello', __main__.Foo]

Bar.x = 10
Bar.get_defaults() == Bar.get_all()
# False -> [1, 'hello', __main__.Foo] != [10, 'hello', __main__.Foo]

CodePudding user response:

You can create a list of values and define individual attributes at the same time with minimal boilerplate.

class NiceClass():
    (some_value,
     some_other_value,
    ) = _all_values = [
        SomeObject(...),
        SomeOtherObject(...)
    ]
    

    @classmethod
    def get_all_vars(cls):
        return cls._all_values

The most obvious drawback to this is that it's easy to get the order of names and values out of sync.

Ideally, you might like to do something like

class NiceClass:

    _attributes = {
        'some_value': SomeObject(...),
        'some_other_value': SomeOtherObject(...)
    }

    @classmethod
    def get_all_vars(cls):
        return cls._attributes.values()

and have some way to "inject" the contents of _attributes into the class namespace directly. The simplest way to do this is with a mix-in class that defines __init_subclass__:

class AddAttributes:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.__dict__.update(cls._attributes)


class NiceClass(AddAttributes):
    # As above
    ...

CodePudding user response:

This might sound like a https://xyproblem.info/ but my solution might work in the other case as well. You can get the fields of an object by using __dict__ or vars (which is considered more pythonic given: Python dictionary from an object's fields)

You could do something like:

class ClassTest:
   def __init__(self):
      self.one = 1
      self.two = 2
list(vars(ClassTest()).values())

But you will see that it has some limitations. It doesn't recognize the not in self.variable_name defined variables like you have used above. It might help you nonetheless, because you can simply define them in init.

  • Related