pythonmetaprogramming

Determine if a function/method has an implementation that isn't "pass"


I have a class that declares a property in Python, the default implementation for this method is pass, for example:

class A:

@property
def something(self):
    pass

This property is intended to declare a functionality for extending classes, and I want to use it to declare a field, where if extending classes implemented it, the field is initialized to some value, and if they don't then the value of the field is None.

How can I check if users did implement it?

I tried using __code__ and hasattr() but that didn't work.


Solution

  • Check in the initialiser, where you are setting the default value for the attribute, if the something property has been modified on the instance's class or if it is unmodified from the base class's implementation and set the attribute's value based on the result of that check:

    from __future__ import annotations
    
    class A:
        def __init__(self) -> None:
            self.attribute = None if type(self).something is A.something else 'Initialised'
            
        @property
        def something(self) -> str | None:
            pass
    
    class B(A):
        @property
        def something(self) -> str | None:
            return "X"
    
    class C(A):
        @property
        def something(self) -> str | None:
            print("Has side-effect")
            return None
    
    class D(A):
        ...
    
    print(f"A: {A().attribute}")
    print(f"B: {B().attribute}")
    print(f"C: {C().attribute}")
    print(f"D: {D().attribute}")
    

    Which outputs:

    A: None
    B: Initialised
    C: Initialised
    D: None
    

    Alternatively, you could use a property to check each time the attribute is queried:

    class A:
        @property
        def attribute(self) -> None:
            return None if type(self).something is A.something else 'Initialised'
            
        @property
        def something(self) -> str | None:
            pass
    

    Or, if you want a class attribute (rather than an instance attribute), set the attribute on the sub-class in __init_subclass__:

    class A:
        attribute: str | None = None
    
        def __init_subclass__(cls) -> None:
            cls.attribute = None if cls.something is A.something else 'Initialised'
    
        @property
        def something(self) -> str | None:
            pass