2012-03-17 35 views
54

Który jest lepszy sposób sprawdzenia istnienia atrybutu?Jaki jest najlepszy sposób sprawdzenia, czy istnieje atrybut?

Jarret Hardie warunkiem tej odpowiedzi:

if hasattr(a, 'property'): 
    a.property 

widzę, że to może być również wykonane w ten sposób:

if 'property' in a.__dict__: 
    a.property 

Czy jedno podejście zazwyczaj stosuje się więcej niż inni?

+0

Twoja druga opcja jest źle, i tak [drugi odpowiedź] (http://stackoverflow.com/a/610923/1132524) z linku, który podałeś odpowiedź na swoje pytanie. –

Odpowiedz

124

Nie ma "najlepszego" sposobu,, ponieważ nigdy nie sprawdzasz, czy atrybut istnieje; zawsze jest częścią większego programu. Istnieje kilka poprawnych sposobów i jeden zauważalny niepoprawny sposób.

niewłaściwy sposób

if 'property' in a.__dict__: 
    a.property 

Oto demonstracja, która pokazuje tę technikę niepowodzeniem:

class A(object): 
    @property 
    def prop(self): 
     return 3 

a = A() 
print "'prop' in a.__dict__ =", 'prop' in a.__dict__ 
print "hasattr(a, 'prop') =", hasattr(a, 'prop') 
print "a.prop =", a.prop 

wyjściowa:

 
'prop' in a.__dict__ = False 
hasattr(a, 'prop') = True 
a.prop = 3 

Większość czasu, nie ma bałagan z __dict__. Jest to specjalny atrybut do robienia specjalnych rzeczy, a sprawdzenie, czy dany atrybut istnieje, jest dość przyziemne.

EAFP sposób

Wspólne idiom w Pythonie jest „łatwiej prosić o przebaczenie niż pozwolenie”, lub EAFP za krótki. Zobaczysz wiele kodu Pythona, który używa tego idiomu, a nie tylko do sprawdzania istnienia atrybutów.

# Cached attribute 
try: 
    big_object = self.big_object 
    # or getattr(self, 'big_object') 
except AttributeError: 
    # Creating the Big Object takes five days 
    # and three hundred pounds of over-ripe melons. 
    big_object = CreateBigObject() 
    self.big_object = big_object 
big_object.do_something() 

Należy zauważyć, że jest to dokładnie ten sam idiom dla otwierania pliku, który może nie istnieć.

try: 
    f = open('some_file', 'r') 
except IOError as ex: 
    if ex.errno != errno.ENOENT: 
     raise 
    # it doesn't exist 
else: 
    # it does and it's open 

Także do konwersji łańcuchów na liczby całkowite.

try: 
    i = int(s) 
except ValueError: 
    print "Not an integer! Please try again." 
    sys.exit(1) 

Nawet importowanie opcjonalnych modułów ...

try: 
    import readline 
except ImportError: 
    pass 

The LBYL sposób

Sposób hasattr, oczywiście, działa też. Technika ta nosi nazwę "wygląd przed skokiem" lub LBYL w skrócie.

# Cached attribute 
if not hasattr(self, 'big_object'): 
    big_object = CreateBigObject() 
    self.big_object = CreateBigObject() 
big_object.do_something() 

(hasattr wbudowane faktycznie zachowuje się dziwnie w wersjach Pythona przed 3.2 w odniesieniu do wyjątków - to złapać wyjątki, że nie powinien on. - ale to chyba bez znaczenia, gdyż takie wyjątki są mało prawdopodobne Technika hasattr jest również wolniejsza niż try/except, ale nie nazywa się tego wystarczająco często, aby się tym przejmować, a różnica nie jest zbyt duża. Wreszcie, hasattr nie jest atomowa, więc mógłby rzucić AttributeError, jeśli inny wątek usuwa atrybut, ale jest to scenariusz jest bardzo naciągany, a poza tym musisz uważać na wątki. Nie uważam żadnej z tych trzech różnic za warte martwienia się.)

Używanie hasattr jest znacznie prostsze niż try/except, o ile tylko musisz wiedzieć, czy atrybut istnieje. Dla mnie dużym problemem jest to, że technika LBYL wygląda "dziwnie", ponieważ jako programista w Pythonie jestem bardziej przyzwyczajony do czytania techniki EAFP. Jeśli przerobisz powyższe przykłady, aby używać stylu LBYL, otrzymasz kod, który jest albo niezgrabny, wręcz niepoprawny, albo zbyt trudny do napisania.

# Seems rather fragile... 
if re.match('^(:?0|-?[1-9][0-9]*)$', s): 
    i = int(s) 
else: 
    print "Not an integer! Please try again." 
    sys.exit(1) 

I LBYL jest czasami wręcz błędna:

if os.path.isfile('some_file'): 
    # At this point, some other program could 
    # delete some_file... 
    f = open('some_file', 'r') 

Jeśli chcesz napisać funkcję LBYL importowania opcjonalne moduły, Be My Guest ... to brzmi jak funkcja byłaby całkowita potwór .

getattr sposób

Jeśli wystarczy wartość domyślną, getattr jest krótsza wersja try/except.

x = getattr(self, 'x', default_value) 

Jeśli wartość domyślna jest drogie skonstruować Pokochasz więc skończyć z czymś takim:

x = getattr(self, 'attr', None) 
if x is None: 
    x = CreateDefaultValue() 
    self.attr = x 

Lub jeśli None jest możliwa wartość,

sentinel = object() 

x = getattr(self, 'attr', sentinel) 
if x is sentinel: 
    x = CreateDefaultValue() 
    self.attr = x 

Wnioski

Wewnętrznie: wbudowane getattr i hasattr po prostu użyj techniki try/except (z wyjątkiem napisanej w C). Tak więc wszyscy zachowują się w ten sam sposób, w którym to się liczy, a wybranie właściwego jest kwestią okoliczności i stylu.

Kod EAFP try/except będzie zawsze pocierał niektórych programistów w niewłaściwy sposób, a kod LBYL hasattr/getattr będzie irytować innych programistów. Oba są poprawne i często nie ma naprawdę nieodpartego powodu, aby wybrać jedną lub drugą. (Jeszcze inni programiści są zdegustowani, które można uznać za normalne atrybutem jako niezdefiniowany, a niektórzy programiści są przerażeni, że to w ogóle możliwe, aby mieć niezdefiniowany atrybut w Pythonie.)

+4

Prawdopodobnie "try ... except" jest lepsze, gdy prawie zawsze oczekujesz, że 'x' ma atrybut' x.attr'. (Wtedy wyjątek byłby naprawdę wyjątkowy.) –

+3

Prawie zawsze używam 'try ... except 'osobiście, metoda' hasattr' pozostawia w moich ustach zły smak. –

+0

@Dietrich Epp: kod, który może ukryć wyjątek, taki jak 'try/except' powyżej, jest zdecydowanie gorszy niż' hasattr() '. – jfs

10

hasattr() jest droga *.

a.__dict__ jest brzydki i nie działa w wielu przypadkach. hasattr() faktycznie próbuje uzyskać atrybut i zatrzymuje wewnętrznie AttributeError, więc działa nawet jeśli zdefiniujesz niestandardową metodę __getattr__().

Aby uniknąć zainteresowanie atrybut dwukrotnie trzeci argument dla getattr() mogłyby zostać wykorzystane:

not_exist = object() 

# ... 
attr = getattr(obj, 'attr', not_exist) 
if attr is not_exist: 
    do_something_else() 
else: 
    do_something(attr) 

mógłby po prostu użyć wartości domyślnej zamiast not_exist Sentinel jeśli jest to bardziej odpowiednie w danym przypadku.

Nie podoba mi się try: do_something(x.attr) \n except AttributeError: .. może ukryć AttributeError w funkcji do_something().

*Before Python 3.1 hasattr() suppressed all exceptions (nie tylko AttributeError) jeżeli nie jest pożądane getattr() powinny być stosowane.

4

hasattr() to Pythonic sposób to zrobić. Ucz się, kochaj to.

Inny możliwy sposób jest sprawdzenie, czy nazwa zmiennej jest w locals() lub globals():

if varName in locals() or in globals(): 
    do_something() 
else: 
    do_something_else() 

ja osobiście nienawidzę złapać wyjątki w celu sprawdzenia czegoś. Wygląda i czuje się brzydko. To identyczna sprawdzenie czy ciąg zawiera tylko cyfry w ten sposób:

s = "84984x" 
try: 
    int(s) 
    do_something(s) 
except ValueError: 
    do_something_else(s) 

Zamiast delikatnie używając s.isdigit(). Eww.

+0

Wersje 'int' /' isdigit' różnią się ważny sposób; wersja 'int' zezwala na liczby ujemne. Rzucam Ci wyzwanie, by wymyślić zwięzły i czytelny odpowiednik "try: int (s) oprócz ValueError: ...", który działa poprawnie dla liczb ujemnych i odrzuca numery z zewnętrznymi wiodącymi zerami (tak jak robi to Python). –

0

Bardzo stare pytanie, ale naprawdę potrzebuje dobrej odpowiedzi. Nawet na krótki program, powiedziałbym, użyj funkcji niestandardowej!

Oto przykład. To nie jest idealne dla wszystkich aplikacji, ale jest dla mnie, do analizowania odpowiedzi z niezliczonych API i używania Django. Jest łatwa do naprawy dla wszystkich wymagań.

from django.core.exceptions import ObjectDoesNotExist 
from functools import reduce 

class MultipleObjectsReturned(Exception): 
    pass 

def get_attr(obj, attr, default, asString=False, silent=True): 
    """ 
    Gets any attribute of obj. 
    Recursively get attributes by separating attribute names with the .-character.   
    Calls the last attribute if it's a function. 

    Usage: get_attr(obj, 'x.y.z', None) 
    """ 
    try: 
     attr = reduce(getattr, attr.split("."), obj) 
     if hasattr(attr, '__call__'): 
      attr = attr() 
     if attr is None: 
      return default 
     if isinstance(attr, list): 
      if len(attr) > 1: 
       logger.debug("Found multiple attributes: " + str(attr)) 
       raise MultipleObjectsReturned("Expected a single attribute") 
      else: 
       return str(attr[0]) if asString else attr[0] 
     else: 
      return str(attr) if asString else attr 
    except AttributeError: 
     if not silent: 
      raise 
     return default 
    except ObjectDoesNotExist: 
     if not silent: 
      raise 
     return default 
Powiązane problemy