2010-11-22 8 views
22

Potrzebuję sposobu na sprawdzenie klasy, aby móc bezpiecznie określić, które atrybuty są atrybutami klasy zdefiniowanymi przez użytkownika. Problem polega na tym, że funkcje takie jak dir(), inspect.getmembers() i przyjaciele zwracają wszystkie atrybuty klas, w tym predefiniowane, takie jak: __class__, __doc__, __dict__, __hash__. Jest to oczywiście zrozumiałe i można by argumentować, że mógłbym po prostu zrobić listę nazwanych członków do zignorowania, ale niestety te predefiniowane atrybuty muszą się zmieniać wraz z różnymi wersjami Pythona, przez co mój projekt może być zmieniony w projekcie Pythona - i nie lubię tego.Sprawdzanie atrybutów klasy Pythona

przykład:

>>> class A: 
... a=10 
... b=20 
... def __init__(self): 
...  self.c=30 
>>> dir(A) 
['__doc__', '__init__', '__module__', 'a', 'b'] 
>>> get_user_attributes(A) 
['a','b'] 

W powyższym przykładzie I chce bezpieczny sposób, aby odzyskać tylko zdefiniowanej przez użytkownika klasy atrybutów [ „a”, „b”] Za „c”, jak to jest atrybutem wystąpienie . Więc moje pytanie brzmi ... Czy ktoś może mi pomóc z powyższą fikcyjną funkcją get_user_attributes(cls)?

P.S. Spędziłem trochę czasu, próbując rozwiązać problem, analizując klasę na poziomie AST, która byłaby bardzo łatwa. Ale nie mogę znaleźć sposobu przekonwertowania już przeanalizowanych obiektów do drzewa węzłów AST. Zgaduję, że wszystkie informacje AST są odrzucane, gdy klasa została skompilowana do kodu bajtowego.

poważaniem Jakob

+1

Wspominasz, że próbujesz to zrobić w AST. Czy to oznacza, że ​​chcesz tylko atrybuty, które są zdefiniowane bezpośrednio na klasie, a nie na jej nadklasach? Rozumiem, że nie chcesz tych "wbudowanych", ale jestem zdezorientowany w tej kwestii. – aaronasterling

Odpowiedz

27

Poniżej skórze. Oto prosty sposób. Nie wiem, dlaczego nie przyszło mi to wcześniej.

import inspect 

def get_user_attributes(cls): 
    boring = dir(type('dummy', (object,), {})) 
    return [item 
      for item in inspect.getmembers(cls) 
      if item[0] not in boring] 

Oto początek

def get_user_attributes(cls): 
    boring = dir(type('dummy', (object,), {})) 
    attrs = {} 
    bases = reversed(inspect.getmro(cls)) 
    for base in bases: 
     if hasattr(base, '__dict__'): 
      attrs.update(base.__dict__) 
     elif hasattr(base, '__slots__'): 
      if hasattr(base, base.__slots__[0]): 
       # We're dealing with a non-string sequence or one char string 
       for item in base.__slots__: 
        attrs[item] = getattr(base, item) 
      else: 
       # We're dealing with a single identifier as a string 
       attrs[base.__slots__] = getattr(base, base.__slots__) 
    for key in boring: 
     del attrs['key'] # we can be sure it will be present so no need to guard this 
    return attrs 

ten powinien być dość solidne. Zasadniczo działa to poprzez ignorowanie atrybutów znajdujących się w domyślnej podklasie object. Następnie dostaje mro klasy, która jest do niego przekazywana i przechodzi przez nią w odwrotnej kolejności, aby klucze podklasy mogły nadpisywać klucze nadklasy. Zwraca słownik par klucz-wartość. Jeśli chcesz listę klucza, krotki wartości jak w inspect.getmembers potem po prostu wrócić albo attrs.items() lub list(attrs.items()) w Pythonie 3.

jeśli w rzeczywistości nie chcą przechodzić przez MRO i po prostu chcą atrybuty zdefiniowane bezpośrednio na podklasy to jest to łatwiej:

def get_user_attributes(cls): 
    boring = dir(type('dummy', (object,), {})) 
    if hasattr(cls, '__dict__'): 
     attrs = cls.__dict__.copy() 
    elif hasattr(cls, '__slots__'): 
     if hasattr(base, base.__slots__[0]): 
      # We're dealing with a non-string sequence or one char string 
      for item in base.__slots__: 
       attrs[item] = getattr(base, item) 
      else: 
       # We're dealing with a single identifier as a string 
       attrs[base.__slots__] = getattr(base, base.__slots__) 
    for key in boring: 
     del attrs['key'] # we can be sure it will be present so no need to guard this 
    return attrs 
+3

'__slots__'? ;-) –

+1

@Chris Morgan. Dobre oko. Coś jeszcze nie tak? – aaronasterling

+0

nie sądzę. Po prostu grałem wtedy adwokatem diabła, ponieważ właśnie myślałem o '__slots__' w tym czasie (próbując dowiedzieć się czegoś z PyPy) –

6

Pokój podkreślenia na obu końcach „wyróżnikiem” zostały częścią pytona przed 2.0. Byłoby bardzo mało prawdopodobne, aby zmienili to w dowolnym momencie w najbliższej przyszłości.

class Foo(object): 
    a = 1 
    b = 2 

def get_attrs(klass): 
    return [k for k in klass.__dict__.keys() 
      if not k.startswith('__') 
      and not k.endswith('__')] 

print get_attrs(Foo) 

[ 'a', 'b']

+2

co o zdefiniowanych przez użytkownika '__add__',' __mul__', '__iter__' itd.? – aaronasterling

+0

Jeśli są zdefiniowane przez użytkownika, to ja też tego chcę. Czy możliwe jest pozyskanie drzewa AST dla klasy, która jest już sparsowana i skompilowana w bajtach? –

+0

@jakob, nie, nie można uzyskać AST za "żywy" kod, ponieważ źródło nie jest już przechowywane w pamięci po przeanalizowaniu, a tam, gdy Pythonowi brakuje kodu bajtowego, nawet bez źródła, więc nie może być AST. – toriningen

2

Jeśli używasz nowych klas stylów, czy możesz po prostu odjąć atrybuty klasy nadrzędnej?

class A(object): 
    a = 10 
    b = 20 
    #... 

def get_attrs(Foo): 
    return [k for k in dir(Foo) if k not in dir(super(Foo))] 

Edit: Niezupełnie. __dict__, __module__ i __weakref__ pojawiają się podczas dziedziczenia z obiektu, ale nie znajdują się w samym obiekcie.Możesz to zrobić w specjalnym przypadku - wątpię, by bardzo często się zmieniały.

3

Dzięki aaronasterling, dałeś mi ekspresję i potrzebne :-) Moja ostatnia funkcja atrybut class inspektor wygląda następująco: atrybut class

def get_user_attributes(cls,exclude_methods=True): 
    base_attrs = dir(type('dummy', (object,), {})) 
    this_cls_attrs = dir(cls) 
    res = [] 
    for attr in this_cls_attrs: 
    if base_attrs.count(attr) or (callable(getattr(cls,attr)) and exclude_methods): 
     continue 
    res += [attr] 
    return res 

Albo powrót variabels tylko (exclude_methods = true) lub też pobrać metody. Moje wstępne testy i powyższa funkcja obsługują zarówno stare, jak i nowe style python.

/Jakob

+0

ładny. Jedną z ulepszeń, które można wprowadzić, jest przełączenie sprawdzenia dla 'exclude_methods' i' callable (getattr (...)) ', aby' callable' działał tylko wtedy, gdy proste sprawdzanie boolowskie nie powiedzie się. – aaronasterling

+0

Masz rację :-) –