[PYTHON] Décorateur (@pureinstansemethod) qui définit une méthode d'instance pure

Préface

En Python, les méthodes de classe et les méthodes statiques peuvent être définies à l'aide de décorateurs tels que @ classmethod et @ staticmethod. Par contre, une méthode normale (méthode d'instance) peut être définie normalement, mais en fait, elle peut être appelée de deux manières:

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

x = 1
myclass = MyClass()

myclass.mymethod(x)  #Utilisation normale
MyClass(None,1)  # !?!?

La deuxième utilisation consiste à utiliser la fonction définie telle quelle (le premier argument est «self», mais il n'est pas utilisé dans la fonction, donc «Aucun» convient).

Cependant, même s'il s'agit d'une méthode d'instance, si vous l'appelez par inadvertance comme une méthode de classe, cela provoquera une erreur déroutante.

Par exemple, lorsque vous prenez plusieurs arguments comme indiqué ci-dessous:

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]

Pour éviter cela, je veux être en colère quand je l'appelle quelque chose comme le second.

<détails>

Supplément </ summary>

En Python2, «myclass.mymethod» renvoie une méthode liée et «MyClass.mymethod» renvoie une méthode non liée. La méthode (lié / non lié) est pour le premier argument (fixé à self / renvoie une erreur si vous prenez une instance non-MyClass). En d'autres termes, le but de cette fois est de faire revivre la méthode Unbound de l'ère Python2 selon le cas.

la mise en oeuvre

Référence 1: stackoverflow- "Obtenir la classe de définition de l'objet méthode indépendant en Python 3" [Référence 2: Maîtriser Python-Qui convertit d'une méthode en une fonction? ](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)

Définissez un décorateur.

import functools
import inspect

# a.x -> type(a).__dict__['x'].__get__(a, type(a))
# A.x -> A.__dict__['x'].__get__(None, A)
#
# --> __get__Lorsque le premier argument de est None, lorsqu'il est appelé depuis la classe.
#Donc un.__dict__['x']À__get__Si vous laissez l'objet avec
#Vous pouvez le pirater gentiment. cependant,__get__(obe,objtype)Faites également attention à la valeur de retour de.
# a.Pour x, la valeur de retour doit être une méthode. En d'autres termes, celui avec le premier argument de la fonction fixe.
# A.Si c'est x, ne vous inquiétez pas (faites-en une erreur).

#def pure_instancemethod(func):
#    functools.wraps(func)
#    def wrapped_func(self,*args,**kwargs):
#        if not isinstance(self,)
#            raise TypeError("Pas une méthode de classe")

class PureInstanceMethod:
    def __init__(self,func):
        self.func = func
    
    def __get__(self,obj,objtype):
        if obj is None:  #Appel de la classe:
            raise TypeError("N'appelez pas de la classe Octopus")
            
        else: #Appel depuis l'instance
            def instancemethod(*args,**kwargs):
                return self.func(obj,*args,**kwargs)
            return instancemethod #Je retournerai la méthode
        
    def __call__(self,*args,**kwargs):
        return self.func(*args,**kwargs)
            
class MyClass:
    
    @PureInstanceMethod
    def mymethod(self,*x):
        print(x)
    
    # MyClass.__dict__["mymethod"] = pureinstancemethod(mymethod)Presque le même que.
    #En d'autres termes, voici une instance de la méthode d'instance pure initialisée par mymethod.

Comment utiliser

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:  #Appel de la classe:
# > ---> 25             raise TypeError("N'appelez pas de la classe Octopus")
# >    26 
# >    27         else: #Appel depuis l'instance
# > 
# > TypeError:N'appelez pas de la classe Octopus

J'étais en colère en toute sécurité.

Recommended Posts

Décorateur (@pureinstansemethod) qui définit une méthode d'instance pure
Créer une fausse classe qui triche également est une instance
En Python, créez un décorateur qui accepte dynamiquement les arguments Créer un décorateur