Dapatkan kelas dengan metode yang ditentukan

89

Bagaimana saya bisa mendapatkan kelas yang mendefinisikan metode dengan Python?

Saya ingin contoh berikut mencetak " __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
sumber
Versi Python apa yang Anda gunakan? Sebelum versi 2.2 Anda bisa menggunakan im_class, tapi itu diubah untuk menunjukkan tipe objek diri terikat.
Kathy Van Stone
1
Senang mendengarnya. Tapi saya menggunakan 2.6.
Jesse Aldridge

Jawaban:

70
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
sumber
1
Terima kasih, saya butuh waktu untuk memikirkannya sendiri
David
Hati-hati, tidak semua kelas menerapkan __dict__! Terkadang __slots__digunakan. Mungkin lebih baik digunakan getattruntuk menguji apakah metode tersebut ada di kelas.
Codie CodeMonkey
16
Untuk Python 3, silakan lihat jawaban ini .
Yoel
27
Saya mendapatkan:'function' object has no attribute 'im_class'
Zitrax
5
Di Python 2.7 itu tidak berhasil. Kesalahan yang sama tentang 'im_class' yang hilang.
RedX
8

Terima kasih Sr2222 karena telah menunjukkan bahwa saya melewatkan intinya ...

Inilah pendekatan yang dikoreksi yang sama seperti Alex tetapi tidak perlu mengimpor apa pun. Saya tidak berpikir ini merupakan peningkatan, kecuali ada hierarki besar kelas yang diwariskan karena pendekatan ini berhenti segera setelah kelas yang menentukan ditemukan, alih-alih mengembalikan seluruh warisan seperti getmrohalnya. Seperti yang dikatakan, ini adalah skenario yang sangat tidak mungkin.

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

Dan Contoh:

>>> 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

Solusi Alex mengembalikan hasil yang sama. Selama pendekatan Alex dapat digunakan, saya akan menggunakannya sebagai pengganti yang ini.

estani
sumber
Cls().meth.__self__hanya memberi Anda contoh Clsyang terikat ke contoh spesifik itu meth. Ini analog dengan Cls().meth.im_class. Jika sudah class SCls(Cls), Anda SCls().meth.__self__akan mendapatkan sebuah SClscontoh, bukan sebuah Clscontoh. Apa yang diinginkan OP adalah mendapatkan Cls, yang tampaknya hanya tersedia dengan berjalan di MRO seperti yang dilakukan @Alex Martelli.
Silas Ray
@ sr2222 Anda benar. Saya telah mengubah jawabannya karena saya sudah mulai meskipun menurut saya solusi Alex lebih kompak.
estani
1
Ini adalah solusi yang baik jika Anda perlu menghindari impor, tetapi karena Anda pada dasarnya hanya menerapkan ulang MRO, ini tidak dijamin akan berfungsi selamanya. MRO mungkin akan tetap sama, tetapi sudah pernah diubah sekali di masa lalu Python, dan jika diubah lagi, kode ini akan menghasilkan bug yang halus dan menyebar.
Silas Ray
Berkali-kali, "skenario yang sangat tidak mungkin" terjadi dalam pemrograman. Jarang, menyebabkan bencana. Secara umum, pola pikir "XY.Z% hal itu tidak akan pernah terjadi" adalah alat berpikir yang sangat buruk saat melakukan pengkodean. Jangan gunakan itu. Tulis kode yang 100% benar.
ulidtko
@ulidtko Saya pikir Anda salah membaca penjelasannya. Ini bukan tentang kebenaran tetapi kecepatan. Tidak ada solusi "sempurna" yang cocok untuk semua kasus, jika tidak, misalnya hanya akan ada satu algoritma pengurutan. Solusi yang diusulkan di sini harus lebih cepat dalam kasus "jarang". Karena kecepatan datang dalam persentase 99% dari semua kasus setelah keterbacaan, solusi ini mungkin menjadi solusi yang lebih baik dalam "hanya" kasus yang jarang terjadi itu. Kode tersebut, jika Anda tidak membacanya, adalah 100% benar, jika itu yang Anda takuti.
estani
6

Saya tidak tahu mengapa tidak ada yang pernah mengungkit hal ini atau mengapa jawaban teratas memiliki 50 suara positif padahal lambat sekali, tetapi Anda juga dapat melakukan hal berikut:

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

Untuk python 3 saya yakin ini berubah dan Anda harus memeriksanya .__qualname__.

Nick Chapman
sumber
2
Hmm. Saya tidak melihat kelas yang menentukan ketika saya melakukannya di python 2.7 - Saya mendapatkan kelas tempat metode itu dipanggil, bukan yang didefinisikan ...
F1Rumors
5

Di Python 3, jika Anda membutuhkan objek kelas yang sebenarnya, Anda dapat melakukan:

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

Jika fungsi tersebut bisa menjadi milik kelas bertingkat, Anda perlu melakukan iterasi sebagai berikut:

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
sumber
2

Python 3

Memecahkannya dengan cara yang sangat sederhana:

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

Ini memberi

'FooClass.foo_method'

Pisahkan di titik untuk mendapatkan kelas dan nama fungsi secara terpisah

firelynx
sumber
Wow, hemat sekali!
pengguna3712978
1

Saya mulai melakukan sesuatu yang agak mirip, pada dasarnya idenya adalah memeriksa setiap kali metode di kelas dasar telah diterapkan atau tidak di sub kelas. Ternyata cara saya awalnya melakukannya, saya tidak dapat mendeteksi ketika kelas menengah benar-benar menerapkan metode tersebut.

Solusi saya untuk itu sebenarnya cukup sederhana; menyetel atribut metode dan menguji keberadaannya nanti. Inilah penyederhanaan semuanya:

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

UPDATE: Sebenarnya panggil method()dari run_method()(bukankah itu semangatnya?) Dan minta ia meneruskan semua argumen yang tidak dimodifikasi ke metode.

PS: Jawaban ini tidak langsung menjawab pertanyaan itu. IMHO ada dua alasan seseorang ingin tahu kelas mana yang mendefinisikan metode; pertama adalah mengarahkan jari ke kelas dalam kode debug (seperti dalam penanganan pengecualian), dan yang kedua adalah menentukan apakah metode tersebut telah diimplementasikan kembali (di mana metode adalah rintisan yang dimaksudkan untuk diterapkan oleh pemrogram). Jawaban ini menyelesaikan kasus kedua dengan cara yang berbeda.

Thomas Guyot-Sionnest
sumber