2

I am trying to create a class that returns the class name together with the attribute. This needs to work both with instance attributes and class attributes

class TestClass:
    obj1 = 'hi'

I.e. I want the following (note: both with and without class instantiation)

>>> TestClass.obj1
('TestClass', 'hi')
>>> TestClass().obj1
('TestClass', 'hi')

A similar effect is obtained when using the Enum package in python, but if I inherit from Enum, I cannot create an __init__ function, which I want to do as well

If I use Enum I would get:

from enum import Enum
class TestClass2(Enum):
    obj1 = 'hi'

>>> TestClass2.obj1
<TestClass2.obj1: 'hi'>

I've already tried overriding the __getattribute__ magic method in a meta class as suggested here: How can I override class attribute access in python. However, this breaks the __dir__ magic method, which then wont return anything, and furthermore it seems to return name of the meta class, rather than the child class. Example below:

class BooType(type):
    def __getattribute__(self, attr):
        if attr == '__class__':
            return super().__getattribute__(attr)
        else:
            return self.__class__.__name__, attr

class Boo(metaclass=BooType):
    asd = 'hi'

>>> print(Boo.asd)
('BooType', 'asd')
>>> print(dir(Boo))
AttributeError: 'tuple' object has no attribute 'keys'

I have also tried overriding the __setattr__ magic method, but this seems to only affect instance attributes, and not class attributes.

I should state that I am looking for a general solution. Not something where I need to write a @property or @classmethod function or something similar for each attribute

oskros
  • 3,101
  • 2
  • 9
  • 28
  • You can simply use some new method, e.g. .view() to return your desired output. Overloading gettrib does not seem to necessary, same for using Enum. – Evgeny Mar 27 '18 at 10:49
  • 1
    The problem is that the behavior you want to implement is provided by each attribute itself, not `TestClass`. You could use the metaclass `__prepare__` method to "preprocess" your class attributes (e.g., make them all instances of some custom descriptor). Instance attribute could be handled similarly using `__setattr__` instead of `__prepare__`. – chepner Dec 08 '20 at 14:11
  • Right, so I need to define this behaviour through a metaclass. Using `__prepare__` would mean instead of the value, a dictionary with the value is returned instead? I suppose that could work, even though it wont give me back the tuple as shown in my example. Is this easiest through `__prepare__`, or would using `__new__` also be possible? – oskros Dec 08 '20 at 14:20

2 Answers2

0

I got help from a colleague for defining meta classes, and came up with the following solution

class MyMeta(type):
    def __new__(mcs, name, bases, dct):
        c = super(MyMeta, mcs).__new__(mcs, name, bases, dct)
        c._member_names = []
        for key, value in c.__dict__.items():
            if type(value) is str and not key.startswith("__"):
                c._member_names.append(key)
                setattr(c, key, (c.__name__, value))
        return c

    def __dir__(cls):
        return cls._member_names

class TestClass(metaclass=MyMeta):
    a = 'hi'
    b = 'hi again'

print(TestClass.a)
# ('TestClass', 'hi')
print(TestClass.b)
# ('TestClass', 'hi again')
print(dir(TestClass))
# ['a', 'b']
oskros
  • 3,101
  • 2
  • 9
  • 28
-1

Way 1

You can use classmethod decorator to define methods callable at the whole class:

class TestClass:
    _obj1 = 'hi'

    @classmethod
    def obj1(cls):
        return cls.__name__, cls._obj1


class TestSubClass(TestClass):
    pass


print(TestClass.obj1())
# ('TestClass', 'hi')
print(TestSubClass.obj1())
# ('TestSubClass', 'hi')

Way 2

Maybe you should use property decorator so the disered output will be accessible by instances of a certain class instead of the class itself:

class TestClass:
    _obj1 = 'hi'

    @property
    def obj1(self):
        return self.__class__.__name__, self._obj1


class TestSubClass(TestClass):
    pass

a = TestClass()
b = TestSubClass()

print(a.obj1)
# ('TestClass', 'hi')
print(b.obj1)
# ('TestSubClass', 'hi')
akarilimano
  • 1,034
  • 1
  • 10
  • 17
  • I know this is an old reply. I have recently just edited my question for more clarity of what I am looking for, and unfortunately this answer does not solve my issue due to the need of writing a `@property` or `@classmethod` function for each attribute – oskros Dec 08 '20 at 13:43