Obtenez la classe qui définit la méthode

89

Comment puis-je obtenir la classe qui a défini une méthode en Python?

Je voudrais que l'exemple suivant imprime " __main__.FooClass":

class FooClass:
    def foo_method(self):
        print "foo"

class BarClass(FooClass):
    pass

bar = BarClass()
print get_class_that_defined_method(bar.foo_method)
Jesse Aldridge
la source
Quelle version de Python utilisez-vous? Avant la version 2.2, vous pouviez utiliser im_class, mais cela a été modifié pour afficher le type de l'objet self lié.
Kathy Van Stone
1
Bon à savoir. Mais j'utilise 2.6.
Jesse Aldridge

Réponses:

71
import inspect

def get_class_that_defined_method(meth):
    for cls in inspect.getmro(meth.im_class):
        if meth.__name__ in cls.__dict__: 
            return cls
    return None
Alex Martelli
la source
1
Merci, il m'aurait fallu un certain temps pour comprendre cela par moi-même
David
Attention, toutes les classes ne sont pas implémentées __dict__! Parfois __slots__est utilisé. Il est probablement préférable de l'utiliser getattrpour tester si la méthode est dans la classe.
Codie CodeMonkey
16
Pour Python 3, veuillez vous référer à cette réponse .
Yoel le
27
Je reçois:'function' object has no attribute 'im_class'
Zitrax
5
En Python 2.7, cela ne fonctionne pas. Même erreur à propos de «im_class» manquant.
RedX
8

Merci Sr2222 d'avoir signalé que je manquais le point ...

Voici l'approche corrigée qui ressemble à celle d'Alex mais qui ne nécessite rien d'importer. Je ne pense pas que ce soit une amélioration cependant, à moins qu'il y ait une énorme hiérarchie de classes héritées car cette approche s'arrête dès que la classe de définition est trouvée, au lieu de renvoyer tout l'héritage comme le getmrofait. Comme dit, c'est un scénario très improbable.

def get_class_that_defined_method(method):
    method_name = method.__name__
    if method.__self__:    
        classes = [method.__self__.__class__]
    else:
        #unbound method
        classes = [method.im_class]
    while classes:
        c = classes.pop()
        if method_name in c.__dict__:
            return c
        else:
            classes = list(c.__bases__) + classes
    return None

Et l'exemple:

>>> class A(object):
...     def test(self): pass
>>> class B(A): pass
>>> class C(B): pass
>>> class D(A):
...     def test(self): print 1
>>> class E(D,C): pass

>>> get_class_that_defined_method(A().test)
<class '__main__.A'>
>>> get_class_that_defined_method(A.test)
<class '__main__.A'>
>>> get_class_that_defined_method(B.test)
<class '__main__.A'>
>>> get_class_that_defined_method(C.test)
<class '__main__.A'>
>>> get_class_that_defined_method(D.test)
<class '__main__.D'>
>>> get_class_that_defined_method(E().test)
<class '__main__.D'>
>>> get_class_that_defined_method(E.test)
<class '__main__.D'>
>>> E().test()
1

La solution Alex renvoie les mêmes résultats. Tant que l'approche Alex peut être utilisée, je l'utiliserais à la place de celle-ci.

estani
la source
Cls().meth.__self__vous donne simplement l'instance de Clsqui est liée à cette instance spécifique de meth. C'est analogue à Cls().meth.im_class. Si vous avez class SCls(Cls), SCls().meth.__self__vous obtiendrez une SClsinstance, pas une Clsinstance. Ce que l'OP veut, c'est obtenir Cls, ce qui semble être uniquement disponible en marchant sur le MRO comme le fait @Alex Martelli.
Silas Ray
@ sr2222 Vous avez raison. J'ai modifié la réponse car j'ai déjà commencé bien que je pense que la solution Alex est plus compacte.
estani
1
C'est une bonne solution si vous devez éviter les importations, mais comme vous ne faites que réimplémenter le MRO, il n'est pas garanti de fonctionner éternellement. Le MRO restera probablement le même, mais il a déjà été modifié une fois dans le passé de Python, et s'il est à nouveau modifié, ce code entraînera des bogues subtils et omniprésents.
Silas Ray
À maintes reprises, des «scénarios très improbables» se produisent dans la programmation. Rarement, provoquant des catastrophes. En général, le modèle de pensée "XY.Z% ça n'arrivera jamais" est un outil de réflexion extrêmement moche lors du codage. Ne l'utilisez pas. Écrivez un code 100% correct.
ulidtko
@ulidtko Je pense que vous avez mal lu l'explication. Ce n'est pas une question d'exactitude mais de vitesse. Il n'y a pas de solution "parfaite" qui convienne à tous les cas, sinon par exemple il n'y aura qu'un seul algorithme de tri. La solution proposée ici devrait être plus rapide dans le cas "rare". Étant donné que la vitesse intervient dans 99% des cas après la lisibilité, cette solution pourrait être une meilleure solution dans "seulement" ce cas rare. Le code, au cas où vous ne l'auriez pas lu, est correct à 100%, si c'était ce que vous craigniez.
estani
6

Je ne sais pas pourquoi personne n'a jamais soulevé cela ou pourquoi la première réponse a 50 votes positifs alors que c'est lent comme l'enfer, mais vous pouvez également faire ce qui suit:

def get_class_that_defined_method(meth):
    return meth.im_class.__name__

Pour python 3, je pense que cela a changé et vous devrez vous pencher sur .__qualname__.

Nick Chapman
la source
2
Hmm. Je ne vois pas la classe de définition quand je fais cela dans python 2.7 - J'obtiens la classe sur laquelle la méthode a été appelée, pas celle où elle est définie ...
F1Rumors
5

Dans Python 3, si vous avez besoin de l'objet de classe réel, vous pouvez faire:

import sys
f = Foo.my_function
vars(sys.modules[f.__module__])[f.__qualname__.split('.')[0]]  # Gets Foo object

Si la fonction peut appartenir à une classe imbriquée, vous devez itérer comme suit:

f = Foo.Bar.my_function
vals = vars(sys.modules[f.__module__])
for attr in f.__qualname__.split('.')[:-1]:
    vals = vals[attr]
# vals is now the class Foo.Bar
Matthew D. Scholefield
la source
1

J'ai commencé à faire quelque chose de similaire, essentiellement l'idée était de vérifier chaque fois qu'une méthode d'une classe de base avait été implémentée ou non dans une sous-classe. Il s'est avéré comme je l'ai fait à l'origine que je ne pouvais pas détecter quand une classe intermédiaire implémentait réellement la méthode.

Ma solution de contournement était en fait assez simple; définir un attribut de méthode et tester sa présence ultérieurement. Voici une simplification de l'ensemble:

class A():
    def method(self):
        pass
    method._orig = None # This attribute will be gone once the method is implemented

    def run_method(self, *args, **kwargs):
        if hasattr(self.method, '_orig'):
            raise Exception('method not implemented')
        self.method(*args, **kwargs)

class B(A):
    pass

class C(B):
    def method(self):
        pass

class D(C):
    pass

B().run_method() # ==> Raises Exception: method not implemented
C().run_method() # OK
D().run_method() # OK

MISE À JOUR: En fait, appelez method()de run_method()(n'est-ce pas l'esprit?) Et faites-lui passer tous les arguments non modifiés à la méthode.

PS: Cette réponse ne répond pas directement à la question. À mon humble avis, il y a deux raisons pour lesquelles on voudrait savoir quelle classe a défini une méthode; le premier est de pointer du doigt une classe dans le code de débogage (comme dans la gestion des exceptions), et le second est de déterminer si la méthode a été réimplémentée (où method est un stub destiné à être implémenté par le programmeur). Cette réponse résout ce deuxième cas d'une manière différente.

Thomas Guyot-Sionnest
la source
1

Python 3

Résolu le problème d'une manière très simple:

str(bar.foo_method).split(" ", 3)[-2]

Cela donne

'FooClass.foo_method'

Fractionner sur le point pour obtenir la classe et le nom de la fonction séparément

firelynx
la source
Wow quelle sauvegarde!
user3712978