0

In Python, it's no secret that you can implement attributes of an instance of a class dynamically. Here's what that looks like:

class MyClass(object):
    def __getattribute__(self, name):
        if name == 'dynamic_attribute':
            return "dynamic attribute value"
        return super().__getattribute__(name)

# Create an instance of my class
obj = MyClass()

# I've implemented the 'dynamic_attribute' attribute dynamically, so I can
# reference it without ever having assigned it a value
print(obj.dynamic_attribute)

# As I've only implemented that one attribute dynamically, other attributes
# behave in the standard way
try:
    print(obj.regular_attribute)
except AttributeError as ex:
    print(f"EXPECTED ERROR: {ex}")
obj.regular_attribute = "regular attribute value"
print(obj.regular_attribute)

Result:

dynamic attribute value
EXPECTED ERROR: 'MyClass' object has no attribute 'regular_attribute'
regular attribute value

Is there any way to do this same thing with a class attribute rather than a class instance attribute? Here's an example:

print(MyClass.dynamic_class_attribute)

I want this line of code to return a value that is computed somewhere in my code without me having to have defined a 'dynamic_class_attribute' attribute explicitly on the MyClass class. Can this be done? TIA.

PS: I've never done anything with metaclasses, but I know of their existence. I read up a little on them hoping for an obvious answer to this question. I didn't find one.

UPDATE:

Someone asked why I wanted this. Maybe others would find my use case interesting. I was basically just being a bit anal about simplifying an API I'm creating. It isn't quite as simple as this, but it's basically a singleton thing, and all I'm really doing is trying to replace this:

SomeSingletonClass.get().some_method()

with this:

SomeSingletonClass.some_method()

At any moment, there's exactly one SomeSingletonClass instance, and I reference that instance a lot, so I wanted to simplify the global reference to it as much as possible. With Raymond H's answer, I did it. It works great.

CryptoFool
  • 21,719
  • 5
  • 26
  • 44
  • 1
    Why do you want to do this? – MattDMo Aug 27 '22 at 23:11
  • 1
    Because many people ask how to implement obscure scenarios, when in fact the problem they want to solve actually has a mainstream solution. – Tim Roberts Aug 27 '22 at 23:16
  • Ah...yeah, gotcha. I'm doing this because I want an API I'm developing to take on a very specific form. I know there are other ways I can do this. I'm already doing them. I just want something even briefer and cleaner. – CryptoFool Aug 27 '22 at 23:19
  • Yes, you need a metaclass. See [What are metaclasses in Python?](/q/100003/) for a general overview. For instance attributes, you set up the logic in the class. Classes are objects; they are instances of `type` by default. A class attribute is simply an instance attribute of something that happens to be a class. So you would have to set up the logic in the class's class, i.e. a metaclass. You cannot alter `type`, but you can make the class use a different metaclass instead (likely one that subclasses `type`). – Karl Knechtel Aug 27 '22 at 23:21

1 Answers1

7

Is there any way to do this same thing with a class attribute rather than a class instance attribute?

Yes, just move the logic into a metaclass:

class MyMetaClass(type):
    def __getattribute__(self, name):
        if name == 'dynamic_attribute':
            return "dynamic attribute value"
        return super().__getattribute__(name)

class C(metaclass=MyMetaClass):
    pass

print(C.dynamic_attribute)

The basic idea is that regular classes control the behavior of instances and metaclasses control the behavior of classes.

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • 1
    Ah...nice. This may very well do it. Thanks! The only question I have is if I have enough context to do what I want to do. But that should be reachable via the `self` parameter. I'll give it a shot. Thanks! – CryptoFool Aug 27 '22 at 23:22
  • Maybe it is worth mentioning this works for ordinary methods and attributes, but won't work for special "dunder" methods. That is, if you create an `__add__` method dynamically in the metaclass by returning it in `__getattribute__`, instances of that class will not be able to operate with the `+`: for the behavior of the operator defining and other special methods, Python checks the methods directly in the class slots, and don't go through `__getattribute__`. – jsbueno Aug 28 '22 at 02:47