[PYTHON] Decorator (@pureinstansemethod) that defines a pure instance method

Preface

In Python, class methods and static methods can be defined using decorators such as @ classmethod and @staticmethod. On the other hand, ordinary methods (instance methods) can be defined normally, but in fact they can be called in two ways:

class MyClass:
    def mymethod(self,x):
        print(x)

x = 1
myclass = MyClass()

myclass.mymethod(x)  #Normal usage
MyClass(None,1)  # !?!?

The second usage is to use the defined function as it is (the first argument is self, but it is not used in the function, so None is fine).

However, even though it is an instance method, if you inadvertently call it like a class method, it will cause an confusing error.

For example, if you take multiple arguments as below:

class MyClass:
    def mymethod(self,*x):
        print(x)

x = list(range(4))
myclass = MyClass()

myclass.mymethod(*x)  # [0,1,2,3]
MyClass.mymethod(*x)  # [1,2,3]

In order to avoid this, I want to be angry when I call it something like the second one.

Supplement
In Python2, `myclass.mymethod` returns a bound method and` MyClass.mymethod` returns an unbound method. The (bound / unbound) method is for the first argument (fixed to self / returns an error if you take an instance other than MyClass). In other words, the purpose of this time is to revive the Unbound method of the Python2 era as the case may be.

Implementation

Reference 1: stackoverflow-"Get defining class of unbound method object in Python 3" [Reference 2: Mastering Python-Who is converting from a method to a function? ](Https://python.ms/descriptor/#%E7%96%91%E5%95%8F-get-%E3%83%A1%E3%82%BD%E3%83%83%E3%83 % 89% E3% 81% A3% E3% 81% A6% E3% 81% AA% E3% 81% AB% EF% BC% 9F)

Define a decorator.

import functools
import inspect

# a.x -> type(a).__dict__['x'].__get__(a, type(a))
# A.x -> A.__dict__['x'].__get__(None, A)
#
# --> __get__When the first argument of is None, when it is called from the class.
#So A.__dict__['x']To__get__If you leave the object with
#You can hack it nicely. However,__get__(obe,objtype)Also pay attention to the return value of.
# a.For x, the return value must be a method. In other words, the one with the first argument of the function fixed.
# A.If it's x, don't worry (make an error).

#def pure_instancemethod(func):
#    functools.wraps(func)
#    def wrapped_func(self,*args,**kwargs):
#        if not isinstance(self,)
#            raise TypeError("Not a class method")

class PureInstanceMethod:
    def __init__(self,func):
        self.func = func
    
    def __get__(self,obj,objtype):
        if obj is None:  #Call from class:
            raise TypeError("Don't call from class Octopus")
            
        else: #Call from instance
            def instancemethod(*args,**kwargs):
                return self.func(obj,*args,**kwargs)
            return instancemethod #I'll return the method
        
    def __call__(self,*args,**kwargs):
        return self.func(*args,**kwargs)
            
class MyClass:
    
    @PureInstanceMethod
    def mymethod(self,*x):
        print(x)
    
    # MyClass.__dict__["mymethod"] = pureinstancemethod(mymethod)Almost the same as.
    #In other words, here is an instance of the pure instance method initialized by mymethod.

How to use

myclass.mymethod(*[1,2,3])
# > (1, 2, 3)
MyClass.mymethod(*[1,2,3]) 
# > ---------------------------------------------------------------------------
# > TypeError                                 Traceback (most recent call last)
# > <ipython-input-152-99447b4e3435> in <module>
# > ----> 1 MyClass.mymethod(*[1,2,3])  # [1,2,3]
# >
# > <ipython-input-147-39b9424a9215> in __get__(self, obj, objtype)
# >    23     def __get__(self,obj,objtype):
# >    24         if obj is None:  #Call from class:
# > ---> 25             raise TypeError("Don't call from class Octopus")
# >    26 
# >    27         else: #Call from instance
# > 
# > TypeError:Don't call from class Octopus

I was angry safely.

Recommended Posts

Decorator (@pureinstansemethod) that defines a pure instance method
Create a fake class that also cheats is instance
In Python, create a decorator that dynamically accepts arguments Create a decorator