Piszę algorytm wyszukiwania w przestrzeni stanów AI i mam klasę ogólną, której można użyć do szybkiego wdrożenia algorytmu wyszukiwania. Podklasa definiuje niezbędne operacje, a algorytm zajmie się resztą.
Tutaj utknąłem: chcę uniknąć ciągłego ponownego generowania stanu nadrzędnego, więc mam następującą funkcję, która zwraca operacje, które można legalnie zastosować do dowolnego stanu:
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
Funkcja invert_op generuje domyślnie.
Czy istnieje szybszy sposób sprawdzenia, czy funkcja nie jest zdefiniowana, niż przechwytywanie wyjątku?
Myślałem o czymś w rodzaju sprawdzania obecności w reż, ale to nie wydaje się właściwe. hasattr jest implementowany przez wywołanie getattr i sprawdzenie, czy podnosi, co nie jest tym, czego chcę.
has_op = lambda obj, op: callable(getattr(obj, op, None))
hasattr(connection, 'invert_opt')
.Odpowiedzi:
Tak, użyj,
getattr()
aby pobrać atrybut icallable()
sprawdzić, czy jest to metoda:invert_op = getattr(self, "invert_op", None) if callable(invert_op): invert_op(self.path.parent_op)
Zauważ, że
getattr()
zwykle zgłasza wyjątek, gdy atrybut nie istnieje. Jeśli jednak określisz wartość domyślną (None
w tym przypadku), zostanie ona zwrócona.źródło
getattr
w tym przypadku po cichu przechwytuje wyjątek i zamiast tego zwraca wartość domyślną, tak jakhasattr
robi to, czemu OP z jakiegoś powodu był przeciwny.Działa zarówno w Pythonie 2, jak i Pythonie 3
hasattr(connection, 'invert_opt')
hasattr
zwraca,True
jeśli obiekt połączenia mainvert_opt
zdefiniowaną funkcję . Oto dokumentacja, którą możesz wypasaćhttps://docs.python.org/2/library/functions.html#hasattr https://docs.python.org/3/library/functions.html#hasattr
źródło
connection.invert_opt = 'foo'
.Dlaczego się temu sprzeciwiasz? W większości przypadków Pythonic lepiej prosić o przebaczenie niż o pozwolenie. ;-)
Dlaczego tak jest? Poniższy tekst jest dość Pythonic:
try: invert_op = self.invert_op except AttributeError: pass else: parent_inverse = invert_op(self.path.parent_op) ops.remove(parent_inverse)
Lub,
# 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)
Należy jednak pamiętać, że
getattr(obj, attr, default)
jest to zasadniczo realizowane poprzez przechwytywanie wyjątku. Nie ma w tym nic złego w krainie Pythona!źródło
Odpowiedzi tutaj sprawdzają, czy ciąg jest nazwą atrybutu obiektu. Aby sprawdzić, czy atrybut jest metodą, potrzebny jest dodatkowy krok (z możliwością wywołania).
Sprowadza się więc do: jaki jest najszybszy sposób sprawdzenia, czy obiekt obj ma atrybut attribute. Odpowiedź to
'attrib' in obj.__dict__
Dzieje się tak, ponieważ dyktowanie haszuje swoje klucze, więc sprawdzenie istnienia klucza jest szybkie.
Zobacz porównania czasu poniżej.
>>> 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
źródło
__slots__
.__slots__
pomaga przyspieszyć dostęp do atrybutów o ~ 10%. stackoverflow.com/a/14119024/1459669Jak wszystko w Pythonie, jeśli wystarczająco się postarasz, możesz dostać się do odwagi i zrobić coś naprawdę paskudnego. A teraz paskudna część:
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'
Zrób nam przysługę, po prostu rób to, co masz w swoim pytaniu i NIE używaj tego, chyba że jesteś w zespole PyPy, który włamuje się do interpretera Pythona. To, co tam masz, to Pythonic, a ja mam tutaj czyste ZŁO .
źródło
co_names
jest równy('NotImplementedError',)
. Nie jestem jednak pewien, czy to czyni to mniej czy bardziej złym.Podoba mi się odpowiedź Nathana Ostgarda i zagłosowałem za nią. Ale innym sposobem rozwiązania problemu byłoby użycie dekoratora zapamiętującego, który buforowałby wynik wywołania funkcji. Możesz więc iść dalej i mieć kosztowną funkcję, która coś wymyśli, ale potem, gdy ją wywołujesz, kolejne połączenia są szybkie; zapamiętana wersja funkcji wyszukuje argumenty w dyktandzie, znajduje wynik w dyktacie z chwili, gdy rzeczywista funkcja obliczyła wynik, i zwraca wynik od razu.
Oto przepis na dekorator zapamiętujący o nazwie „lru_cache” autorstwa Raymonda Hettingera. Wersja tego jest teraz standardem w module functools w Pythonie 3.2.
http://code.activestate.com/recipes/498245-lru-and-lfu-cache-decorators/
http://docs.python.org/release/3.2/library/functools.html
źródło
Możesz również przejść do klasy:
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')
źródło
Podczas gdy sprawdzanie atrybutów we właściwości __dict__ jest naprawdę szybkie, nie możesz tego użyć dla metod, ponieważ nie pojawiają się one w hashu __dict__. Możesz jednak uciec się do rozwiązania hakerskiego w swojej klasie, jeśli wydajność jest tak krytyczna:
class Test(): def __init__(): # redefine your method as attribute self.custom_method = self.custom_method def custom_method(self): pass
Następnie sprawdź metodę jako:
t = Test() 'custom_method' in t.__dict__
Porównanie czasu z
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)
Nie dlatego, że zachęcam do takiego podejścia, ale wydaje się, że działa.
[EDYCJA] Zwiększenie wydajności jest jeszcze większe, gdy nazwa metody nie znajduje się w danej klasie:
>>%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)
źródło
__dict__
może zostać nadpisany. Nie można mu ufać.