Suppose we have a python class like below:
class Test1:
    def __init__(self, value):
        self.value = value
    def f(self):
        return Test(self.value+1)
    def g(self):
        return Test(self.value*2)
test1 = Test1()
Both f and g serves as functions on the class instance in some mathematical notation. For example, I desire to run f(g(value)) in the notation. In the above class definition, the way to achieve it is by running test1.g().f(). Although there is nothing wrong with the code, I just don’t like the fact that the order has changed, and it makes the line not that readable from a mathematics perspective.
I wonder if I could run the line like f(g(value)) with some custom magic or dunder method, but could not find one.
I understand that the way to achieve it is simply taking f and g out from the class like:
class Test1:
    def __init__(self, value):
        self.value = value
def f(obj):
    return Test1(obj.value+1)
def g(obj):
     return Test1(obj.value*2)
test1 = Test1()
new_test = f(g(test1)
However, I have some other similar classes that also have the same function/method names (f, g). So, if I run
from Test1 import Test1, f, g from Test2 import Test2, f, g
there will be obvious conflicts. I understand that I could do things like
from Test1 import Test1, f as f1, g as g1 from Test2 import Test2, f as f2, g as g2
but, not that pleasant with this.
Is there any custom package like functools that provide any ways? What I am expecting is something like
class Test1:
    def __init__(self, value):
        self.value = value
    @some_special_stuff
    def f(self):
        return Test(self.value+1)
    @some_special_stuff
    def g(self):
        return Test(self.value*2)
test1 = Test1()
new_test = f(g(test1)
Thank you in advance.
Advertisement
Answer
Your Test1 and Test2 classes have the same interface. We can model that
from typing import Protocol
# using a protocol here, so we don't have to explicitly inherit from TestProtocol
class TestProtocol(Protocol):
    value: int
    def f(self) -> 'TestProtocol': ...
    def g(self) -> 'TestProtocol': ...
Now we know the interface and can define the free function f and g in terms of that interface:
def f(test: TestProtocol) -> TestProtocol:
    return test.f()
def g(test: TestProtocol) -> TestProtocol:
    return test.g()
Now the free functions f and g are compatible with every class that implements the protocol:
class Test1:
    def __init__(self, value: int) -> None:
        self.value = value
    
    def f(self) -> 'Test1':
        return Test1(self.value + 1)
    
    def g(self) -> 'Test1':
        return Test1(self.value * 2)
class Test2:
    def __init__(self, value: int) -> None:
        self.value = value
    
    def f(self) -> 'Test2':
        return Test2(self.value - 1)
    
    def g(self) -> 'Test2':
        return Test2(self.value // 2)
print(f(g(Test1(4))).value)  # 9
print(f(g(Test2(4))).value)  # 1
Actually, you don’t need the protocol at all. If you don’t care about mypy, remove all type hints and the protocol.
So here is the “short” form without type hints:
class Test1:
    def __init__(self, value):
        self.value = value
    
    def f(self):
        return Test1(self.value + 1)
    
    def g(self):
        return Test1(self.value * 2)
class Test2:
    ...
def f(test):
    return test.f()
def g(test):
    return test.g()
print(f(g(Test1(4))).value)
print(f(g(Test2(4))).value)
