Was ist der schnellste Weg, um zu überprüfen, ob für eine Klasse eine Funktion definiert ist?

132

Ich schreibe einen AI-Zustandsraumsuchalgorithmus und habe eine generische Klasse, mit der ein Suchalgorithmus schnell implementiert werden kann. Eine Unterklasse würde die notwendigen Operationen definieren, und der Algorithmus erledigt den Rest.

Hier stecke ich fest: Ich möchte vermeiden, den übergeordneten Status immer wieder neu zu generieren, daher habe ich die folgende Funktion, die die Operationen zurückgibt, die legal auf jeden Status angewendet werden können:

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

Und die Funktion invert_op wird standardmäßig ausgelöst.

Gibt es eine schnellere Möglichkeit, um zu überprüfen, ob die Funktion nicht definiert ist, als eine Ausnahme abzufangen?

Ich habe etwas überlegt, ob ich in dir nach der Gegenwart suchen soll, aber das scheint nicht richtig zu sein. hasattr wird implementiert, indem getattr aufgerufen und überprüft wird, ob es ausgelöst wird, was ich nicht möchte.

Alex
quelle
8
"hasattr wird implementiert, indem getattr aufgerufen und überprüft wird, ob es ausgelöst wird, was ich nicht möchte." Warum nicht? Warum interessiert es Sie, was die Implementierung tut?
Detly
4
has_op = lambda obj, op: callable(getattr(obj, op, None))
samplebias
1
Versuchen Sie : hasattr(connection, 'invert_opt').
Kenorb

Antworten:

204

Ja, verwenden Sie diese Option, getattr()um das Attribut callable()abzurufen und zu überprüfen, ob es sich um eine Methode handelt:

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

Beachten Sie, dass getattr()normalerweise eine Ausnahme ausgelöst wird, wenn das Attribut nicht vorhanden ist. Wenn Sie jedoch einen Standardwert angeben ( Nonein diesem Fall), wird dieser stattdessen zurückgegeben.

Nathan Ostgard
quelle
3
Beachten Sie auch, dass die Implementierung von getattrin diesem Fall eine Ausnahme stillschweigend abfängt und stattdessen den Standardwert zurückgibt, genau wie dies der hasattrFall ist, gegen den das OP aus irgendeinem Grund war.
Santa
3
Was ist, wenn sich die Funktion nicht in dieser Klasse befindet, sondern in der übergeordneten Klasse? In diesem Fall bekomme ich ein True, auch wenn die Kinder diese Funktion nie implementieren (mit hasattr)
darkgaze
46

Es funktioniert sowohl in Python 2 als auch in Python 3

hasattr(connection, 'invert_opt')

hasattrGibt zurück, Truewenn für das Verbindungsobjekt eine Funktion invert_optdefiniert ist. Hier ist die Dokumentation, auf der Sie grasen können

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

Antonius
quelle
5
Obwohl der Code geschätzt wird, sollte er immer eine begleitende Erklärung enthalten. Dies muss nicht lange dauern, wird aber erwartet.
Peterh - Wiedereinstellung Monica
gut, Sie können auf einen Artikel verweisen, obwohl es nicht schaden würde :)
Vitaliy Terziev
5
Dies gibt auch True zurück, wenn die Verbindung ein Attribut hat connection.invert_opt = 'foo'.
Robert Hönig
20

Gibt es eine schnellere Möglichkeit, um zu überprüfen, ob die Funktion nicht definiert ist, als eine Ausnahme abzufangen?

Warum bist du dagegen? In den meisten pythonischen Fällen ist es besser, um Vergebung als um Erlaubnis zu bitten. ;-);

hasattr wird implementiert, indem getattr aufgerufen und überprüft wird, ob es ausgelöst wird, was ich nicht möchte.

Warum ist das so? Das Folgende ist ziemlich pythonisch:

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

Oder,

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

Beachten Sie jedoch, dass dies getattr(obj, attr, default)im Wesentlichen auch durch das Abfangen einer Ausnahme implementiert wird. Daran ist im Python-Land nichts auszusetzen!

Santa
quelle
4

Die Antworten hier prüfen, ob eine Zeichenfolge der Name eines Attributs des Objekts ist. Ein zusätzlicher Schritt (unter Verwendung von callable) ist erforderlich, um zu überprüfen, ob das Attribut eine Methode ist.

Es läuft also darauf hinaus: Was ist der schnellste Weg, um zu überprüfen, ob ein Objekt obj ein Attribut Attribut hat. Die Antwort ist

'attrib' in obj.__dict__

Dies liegt daran, dass ein Diktat seine Schlüssel hasht, sodass die Überprüfung der Existenz des Schlüssels schnell erfolgt.

Siehe Timing-Vergleiche unten.

>>> 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
Thorwhalen
quelle
Dies schlägt bei Klassen fehl, die verwenden __slots__. __slots__Beschleunigen Sie den Zugriff auf Attribute um ~ 10%. stackoverflow.com/a/14119024/1459669
noɥʇʎԀʎzɐɹƆ
3

Ich mag die Antwort von Nathan Ostgard und habe sie hochgestimmt. Eine andere Möglichkeit, Ihr Problem zu lösen, besteht darin, einen Erinnerungsdekorator zu verwenden, der das Ergebnis des Funktionsaufrufs zwischenspeichert. Sie können also eine teure Funktion haben, die etwas herausfindet, aber wenn Sie sie immer wieder aufrufen, sind die nachfolgenden Anrufe schnell. Die gespeicherte Version der Funktion sucht nach den Argumenten in einem Diktat, findet das Ergebnis im Diktat ab dem Zeitpunkt, an dem die eigentliche Funktion das Ergebnis berechnet hat, und gibt das Ergebnis sofort zurück.

Hier ist ein Rezept für einen Erinnerungsdekorateur namens "lru_cache" von Raymond Hettinger. Eine Version davon ist jetzt Standard im functools-Modul in 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
quelle
2

Wie alles in Python können Sie, wenn Sie sich genug anstrengen, den Mut aufbringen und etwas wirklich Böses tun. Hier ist der böse Teil:

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'

Bitte tun Sie uns einen Gefallen, tun Sie einfach weiter, was Sie in Ihrer Frage haben, und verwenden Sie dies NICHT , es sei denn, Sie sind im PyPy-Team und hacken sich in den Python-Interpreter. Was du da oben hast, ist Pythonic, was ich hier habe, ist reines BÖSE .

YH Wong
quelle
Dies gilt, wenn die Methode eine Ausnahme auslöst. Sie sollten auch überprüfen, ob co_namesgleich ist ('NotImplementedError',). Ich bin mir jedoch nicht sicher, ob dies mehr oder weniger böse ist.
Kindall
1

Sie können auch die Klasse durchgehen:

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

Die Suche nach Attributen in der Eigenschaft __dict__ ist zwar sehr schnell, Sie können dies jedoch nicht für Methoden verwenden, da sie nicht im Hash __dict__ angezeigt werden. Sie können jedoch auf eine hackige Problemumgehung in Ihrer Klasse zurückgreifen, wenn die Leistung so kritisch ist:

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

    def custom_method(self):
        pass

Überprüfen Sie dann die Methode wie folgt:

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

Zeitvergleich mit 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)

Nicht, dass ich diesen Ansatz ermutige, aber er scheint zu funktionieren.

[EDIT] Die Leistungssteigerung ist noch höher, wenn der Methodenname nicht in der angegebenen Klasse enthalten ist:

>>%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
quelle
1
__dict__könnte überschrieben werden. Es kann nicht vertraut werden.
Xiao