Apa cara tercepat untuk memeriksa apakah kelas memiliki fungsi yang ditentukan?

132

Saya sedang menulis algoritme pencarian ruang keadaan AI, dan saya memiliki kelas generik yang dapat digunakan untuk mengimplementasikan algoritma pencarian dengan cepat. Subclass akan mendefinisikan operasi yang diperlukan, dan algoritma melakukan sisanya.

Di sinilah saya macet: Saya ingin menghindari regenerasi negara induk berulang-ulang, jadi saya memiliki fungsi berikut, yang mengembalikan operasi yang dapat diterapkan secara hukum ke keadaan apa pun:

def get_operations(self, include_parent=True):
    ops = self._get_operations()
    if not include_parent and self.path.parent_op:
        try:
            parent_inverse = self.invert_op(self.path.parent_op)
            ops.remove(parent_inverse)
        except NotImplementedError:
            pass
    return ops

Dan fungsi invert_op melempar secara default.

Apakah ada cara yang lebih cepat untuk memeriksa untuk melihat apakah fungsi tidak didefinisikan daripada menangkap pengecualian?

Saya sedang memikirkan sesuatu untuk memeriksa apakah ada dir, tapi itu sepertinya tidak benar. hasattr diimplementasikan dengan memanggil getattr dan memeriksa apakah itu memunculkan, yang bukan yang saya inginkan.

Alex
sumber
8
"hasattr diimplementasikan dengan memanggil getattr dan memeriksa apakah itu memunculkan, yang bukan yang saya inginkan." Kenapa tidak? Mengapa Anda peduli dengan implementasi yang dilakukan?
Detly
4
has_op = lambda obj, op: callable(getattr(obj, op, None))
samplebias
1
Cobalah: hasattr(connection, 'invert_opt').
kenorb

Jawaban:

204

Ya, gunakan getattr()untuk mendapatkan atribut, dan callable()untuk memverifikasi itu adalah metode:

invert_op = getattr(self, "invert_op", None)
if callable(invert_op):
    invert_op(self.path.parent_op)

Perhatikan bahwa getattr()biasanya melempar pengecualian ketika atribut tidak ada. Namun, jika Anda menentukan nilai default ( None, dalam hal ini), itu akan mengembalikannya.

Nathan Ostgard
sumber
3
Perhatikan juga bahwa implementasi getattrdalam hal ini menangkap pengecualian secara diam-diam dan mengembalikan nilai default sebagai gantinya, seperti hasattrhalnya yang dilakukan OP karena alasan tertentu.
Santa
3
Bagaimana jika fungsinya bukan di kelas itu, tetapi di kelas induk ?. Dalam hal ini saya mendapatkan True, bahkan ketika anak-anak tidak pernah mengimplementasikan fungsi itu (menggunakan hasattr)
darkgaze
46

Ia bekerja di kedua Python 2 dan Python 3

hasattr(connection, 'invert_opt')

hasattrkembali Truejika objek koneksi memiliki fungsi yang invert_optditentukan. Ini adalah dokumentasi untuk Anda makan

https://docs.python.org/2/library/functions.html#hasattr https://docs.python.org/3/library/functions.html#hasattr

Antony
sumber
5
Meskipun kode dihargai, itu harus selalu memiliki penjelasan yang menyertainya. Ini tidak harus lama tetapi diharapkan.
peterh
bagus, Anda dapat menunjuk ke sebuah artikel meskipun tidak ada salahnya :)
Vitaliy Terziev
5
Ini juga mengembalikan True jika koneksi memiliki atribut connection.invert_opt = 'foo'.
Robert Hönig
20

Apakah ada cara yang lebih cepat untuk memeriksa untuk melihat apakah fungsi tidak didefinisikan daripada menangkap pengecualian?

Mengapa kamu menentang itu? Dalam kebanyakan kasus Pythonic, lebih baik meminta maaf daripada izin. ;-)

hasattr diimplementasikan dengan memanggil getattr dan memeriksa apakah itu memunculkan, yang bukan yang saya inginkan.

Lagi-lagi mengapa itu? Berikut ini adalah Pythonic:

    try:
        invert_op = self.invert_op
    except AttributeError:
        pass
    else:
        parent_inverse = invert_op(self.path.parent_op)
        ops.remove(parent_inverse)

Atau,

    # if you supply the optional `default` parameter, no exception is thrown
    invert_op = getattr(self, 'invert_op', None)  
    if invert_op is not None:
        parent_inverse = invert_op(self.path.parent_op)
        ops.remove(parent_inverse)

Namun, perlu diketahui bahwa hal getattr(obj, attr, default)itu pada dasarnya dilaksanakan dengan menangkap pengecualian juga. Tidak ada yang salah dengan itu di tanah Python!

Santa
sumber
4

Respons di sini memeriksa apakah string adalah nama atribut objek. Langkah ekstra (menggunakan callable) diperlukan untuk memeriksa apakah atribut adalah metode.

Jadi intinya adalah: apa cara tercepat untuk memeriksa apakah objek obj memiliki atribut atribut. Jawabannya adalah

'attrib' in obj.__dict__

Ini karena dict memiliki kunci-kuncinya sehingga memeriksa keberadaan kunci itu cepat.

Lihat perbandingan waktu di bawah ini.

>>> class SomeClass():
...         pass
...
>>> obj = SomeClass()
>>>
>>> getattr(obj, "invert_op", None)
>>>
>>> %timeit getattr(obj, "invert_op", None)
1000000 loops, best of 3: 723 ns per loop
>>> %timeit hasattr(obj, "invert_op")
The slowest run took 4.60 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 674 ns per loop
>>> %timeit "invert_op" in obj.__dict__
The slowest run took 12.19 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 3: 176 ns per loop
thalwhalen
sumber
Ini gagal pada kelas yang menggunakan __slots__. __slots__membantu mempercepat akses atribut hingga ~ 10%. stackoverflow.com/a/14119024/1459669
noɥʇʎԀʎzɐɹƆ
3

Saya suka jawaban Nathan Ostgard dan saya memilihnya. Tapi cara lain Anda bisa menyelesaikan masalah Anda adalah dengan menggunakan dekorator memoizing, yang akan men-cache hasil panggilan fungsi. Jadi, Anda dapat melanjutkan dan memiliki fungsi mahal yang menemukan sesuatu, tetapi kemudian ketika Anda menyebutnya berulang-ulang panggilan cepat; versi memoized dari fungsi mencari argumen dalam dict, menemukan hasil dict dari ketika fungsi aktual menghitung hasilnya, dan mengembalikan hasilnya segera.

Ini adalah resep untuk dekorator memoizing bernama "lru_cache" oleh Raymond Hettinger. Versi ini sekarang menjadi standar dalam modul functools dengan Python 3.2.

http://code.activestate.com/recipes/498245-lru-and-lfu-cache-decorators/

http://docs.python.org/release/3.2/library/functools.html

steveha
sumber
2

Seperti apa pun di Python, jika Anda berusaha cukup keras, Anda bisa mendapatkan nyali dan melakukan sesuatu yang sangat jahat. Sekarang, inilah bagian jahatnya:

def invert_op(self, op):
    raise NotImplementedError

def is_invert_op_implemented(self):
    # Only works in CPython 2.x of course
    return self.invert_op.__code__.co_code == 't\x00\x00\x82\x01\x00d\x00\x00S'

Tolong bantu kami, terus lakukan apa yang Anda miliki di pertanyaan Anda dan JANGAN pernah menggunakan ini kecuali Anda berada di tim PyPy meretas ke dalam juru bahasa Python. Apa yang Anda miliki di sana adalah Pythonic, apa yang saya miliki di sini adalah JAHAT murni .

YH Wong
sumber
Ini akan benar jika metode ini memunculkan pengecualian. Anda juga harus memeriksa untuk melihat apakah co_namessama dengan ('NotImplementedError',). Saya tidak yakin apakah ini membuatnya lebih atau kurang jahat.
kindall
1

Anda juga dapat membahas kelas:

import inspect


def get_methods(cls_):
    methods = inspect.getmembers(cls_, inspect.isfunction)
    return dict(methods)

# Example
class A(object):
    pass

class B(object):
    def foo():
        print('B')


# If you only have an object, you can use `cls_ = obj.__class__`
if 'foo' in get_methods(A):
    print('A has foo')

if 'foo' in get_methods(B):
    print('B has foo')
Martin Thoma
sumber
0

Meskipun memeriksa atribut dalam properti __dict__ sangat cepat, Anda tidak dapat menggunakan ini untuk metode, karena mereka tidak muncul dalam hash __dict__. Namun Anda dapat menggunakan solusi peretasan di kelas Anda, jika kinerjanya sangat penting:

class Test():
    def __init__():
        # redefine your method as attribute
        self.custom_method = self.custom_method

    def custom_method(self):
        pass

Kemudian periksa metode sebagai:

t = Test()
'custom_method' in t.__dict__

Perbandingan waktu dengan getattr:

>>%timeit 'custom_method' in t.__dict__
55.9 ns ± 0.626 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

>>%timeit getattr(t, 'custom_method', None)
116 ns ± 0.765 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Bukannya saya mendorong pendekatan ini, tetapi tampaknya berhasil.

[EDIT] Peningkatan kinerja bahkan lebih tinggi ketika nama metode tidak ada di kelas yang diberikan:

>>%timeit 'rubbish' in t.__dict__
65.5 ns ± 11 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

>>%timeit getattr(t, 'rubbish', None)
385 ns ± 12.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Jaroslav Loebl
sumber
1
__dict__bisa ditimpa. Itu tidak bisa dipercaya.
Xiao