Search code examples
pythonpython-3.xclassgetattrclass-attributes

Python - Transform class attributes at call time


Suppose I have the following class

class Headings:
    standard_heading = {
        'height': 3.72,
        'width': 25.68,
        'left': 1.65,
        'top': 0.28
    }

As an example, I want the following results where all values have been multiplied by 10:

Headings.standard_heading

>>> {
            'height': 37.2,
            'width': 256.8,
            'left': 16.5,
            'top': 2.8
        }

Is there no way to override the calling of a class attribute by adding a method similar to this to the class:

def __getattribute__(cls, attr):
    return {k:v*10 for k,v in attr.items()

I won't ever be creating instances of this class. I just use it for grouping purposes.

Thanks


Solution

  • You almost had it - just define it the getter as a class method (also you had a small syntax error, attr is a string here):

    class Headings:
        standard_heading = {
            'height': 3.72,
            'width': 25.68,
            'left': 1.65,
            'top': 0.28
        }          
        @classmethod
        def __getattribute__(cls,attr):
            return {k:v*10 for k,v in cls.__dict__[attr].items()}
    
    print(Headings().standard_heading)
    

    Note you do need an actual instance for this to work, but that's what you use in your example. This will also ruin get attribute for object specific fields defined within any method of the object (such as __init__), so careful with this. An easy fix is to override also:

    @classmethod
    def __getattribute__(cls,attr):
        try:
            return {k:v*10 for k,v in cls.__dict__[attr].items()}
        except: raise AttributeError(attr)
    def __getattr__(self,attr):
        return object.__getattribute__(self,attr)
    

    So now if you have:

    def __init__(self): self.a = 'abc'
    

    then

    print(Headings().a)
    

    will also work. Explanation:

    1. First __getattribute__ is called as a class method.
    2. If no class variable exists, then __getattr__ is invoked, now as a regular method, so with the actual object (and object members).
    3. Call the object __getattribute__ to fall back to normal behavior.

    Last note - other than your specific question, if you just want to define a special getter for one class member, a safer way that will only affect said member is using @property and @getter -as explained for example in How does the @property decorator work?. Thanks Adonis for pointing that out.