Search code examples
pythoninstance-variables

Access instance variable by using instance name


I have a class like this:

Class PropertyExpr:
    def __init__(self, value, expr):
        self.value = value
        self.expr = expr

Let's say I initialize the class like this:

prop1 = PropertyExpr(5, "test")

I understand that if I want to get access to value or expr I would call prop1.value or prop1.expr.

However, I want to make it so that whenever I call prop1, it will automatically return prop1.value. prop1.expr would still get access to the expr variable

Since value can be any datatype, I don't think using __str__ would work.

Thank you for any help!

EDIT: This was my current approach, but it only work for int, and I need to extend this so that it work for other data type as well, such as list:

class PropertyExpr(int):

    def __new__(self, value: int, expr: Optional[str]=None, *args, **kwargs):
        return int.__new__(self, value, *args, **kwargs)

    def __init__(self, value: int, expr: Optional[str]=None, *args, **kwargs):
        int.__init__(value, *args, **kwargs)
        if expr is None:
            self.expr = str(value)
        else:
            self.expr = expr

So when I create an instance:

prop1 = PropertyExpr(5, "test")

So when I use prop1, it would return 5, and prop1.expr = "test".


Solution

  • If by "call" you actually mean call and not just "access" you can simply implement a __call__ method:

    class PropertyExpr:
        def __init__(self, value, expr):
            self.value = value
            self.expr = expr
    
        def __call__(self):
            return self.value
    
    prop1 = PropertyExpr(5, "test")
    
    val = prop1()
    print(val)
    

    Output:

    5
    

    In that case, the result of calling prop1() can be really anything.

    Other than that, what you want is not possible. You could override the __new__ method, but that will also change the type of what you're creating. So if you're returning 5 your object will be 5, but it will also be an int and no longer an instance of PropertyExpr and all your additional attributes will be lost:

    class PropertyExpr():
        def __new__(cls, value, expr):
            return value
    
        def __init__(self, value, expr):
            self.value = value
            self.expr = expr
    
    prop1 = PropertyExpr(5, "test")
    
    print(prop1, type(prop1))
    try:
        print(prop1.expr)
    except Exception as e:
        print(e)
    

    Output:

    5 <class 'int'>
    'int' object has no attribute 'expr'
    

    After some trying around, I've figured out a way to dynamically change the type of the constructor, however I would advise against actually using this:

    class PropertyExpr:
        def __new__(cls, tp, value, *args, **kwargs):
            return type("FakeType", (tp,), kwargs)(value)
    
    prop1 = PropertyExpr(int, 5, expr="expr int")
    print(prop1, " -> ", prop1.expr)
    
    prop2 = PropertyExpr(list, "5", expr="expr list")
    print(prop2, " -> ", prop2.expr)
    
    prop3 = PropertyExpr(str, "abc", expr="expr string")
    print(prop3, " -> ", prop3.expr)
    

    Output:

    5  ->  expr int     
    ['5']  ->  expr list
    abc  ->  expr string
    

    You can pass the type you wish to sub-class as the first parameter, the second parameter should be a value accepted by the type's contructor and then you can pass in arbitrary kwargs that will be added as attributes to the created object.


    is there a way to make is so that type(prop1) would still be PropertyExpr? Edit: for example, if we can do isinstance(prop1, PropertyExpr) = True then everything would be perfect

    I could not get that to work (that does not mean that others cannot), but I managed to make it work with multi inheritance, so you can use isinstance(prop1, PropertyExprMixin):

    class PropertyExprMixin:
        pass
    
    class PropertyExpr:
        def __new__(cls, tp, value, *args, **kwargs):
            return type("PropertyExprMixin", (tp,PropertyExprMixin), kwargs)(value)
    
    prop1 = PropertyExpr(int, 5, expr="expr int")
    print(prop1, " -> ", prop1.expr, type(prop1), isinstance(prop1, int), isinstance(prop1, PropertyExprMixin))
    
    prop2 = PropertyExpr(list, "5", expr="expr list")
    print(prop2, " -> ", prop2.expr, type(prop2), isinstance(prop2, list), isinstance(prop2, PropertyExprMixin))
    
    prop3 = PropertyExpr(str, "abc", expr="expr string")
    print(prop3, " -> ", prop3.expr, type(prop3), isinstance(prop3, str), isinstance(prop3, PropertyExprMixin))
    

    Output:

    5  ->  expr int <class '__main__.PropertyExprMixin'> True True     
    ['5']  ->  expr list <class '__main__.PropertyExprMixin'> True True
    abc  ->  expr string <class '__main__.PropertyExprMixin'> True True