2010-11-22 65 views
22

Tôi cần một cách để kiểm tra một lớp để tôi có thể xác định một cách an toàn thuộc tính nào là thuộc tính lớp do người dùng định nghĩa. Vấn đề là các hàm như dir(), inspect.getmembers() và bạn bè trả về tất cả các thuộc tính lớp bao gồm các thuộc tính được xác định trước như: __class__, __doc__, __dict__, __hash__. Điều này tất nhiên là dễ hiểu, và người ta có thể lập luận rằng tôi có thể tạo một danh sách các thành viên có tên để bỏ qua, nhưng tiếc là các thuộc tính được định nghĩa trước này bị ràng buộc thay đổi với các phiên bản Python khác nhau, do đó làm cho dự án của tôi dễ thay đổi trong dự án python - và tôi không thích điều đó.Kiểm tra thuộc tính lớp python

dụ:

>>> 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'] 

Trong ví dụ trên, chúng tôi muốn có một cách an toàn để lấy chỉ lớp người dùng định nghĩa các thuộc tính [ 'a', 'b'] không phải là 'c' vì nó là một thuộc tính dụ . Vì vậy, câu hỏi của tôi là ... Bất cứ ai có thể giúp tôi với các chức năng trên fictive get_user_attributes(cls)?

P.S. Tôi đã dành thời gian cố gắng giải quyết vấn đề bằng cách phân tích cú pháp lớp ở mức AST sẽ rất dễ dàng. Nhưng tôi không thể tìm thấy một cách để chuyển đổi các đối tượng đã được phân tích cú pháp thành cây nút AST. Tôi đoán tất cả các thông tin AST bị hủy bỏ khi một lớp đã được biên dịch thành bytecode.

Trân trọng Jakob

+1

Bạn đề cập đến việc bạn đã cố gắng thực hiện điều đó trong AST. Điều đó có nghĩa là bạn chỉ muốn các thuộc tính được định nghĩa ngay trên lớp chứ không phải trên các lớp siêu lớp của nó? Tôi nhận ra rằng bạn không muốn những người 'xây dựng' nhưng tôi bối rối về vấn đề này. – aaronasterling

Trả lời

27

Dưới đây là cách cứng. Đây là cách dễ dàng. Không biết tại sao nó không xảy ra với tôi sớm hơn.

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] 

Dưới đây là một sự khởi đầu

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 

này nên được khá mạnh mẽ. Về cơ bản, nó hoạt động bằng cách nhận các thuộc tính nằm trên lớp con mặc định của object để bỏ qua. Sau đó nó nhận được mro của lớp được truyền cho nó và đi qua nó theo thứ tự ngược lại để các khóa lớp con có thể ghi đè lên các khóa siêu lớp. Nó trả về một từ điển các cặp khóa-giá trị. Nếu bạn muốn có một danh sách các trọng điểm, các bộ giá trị như thế nào trong inspect.getmembers sau đó chỉ cần trở lại một trong hai attrs.items() hoặc list(attrs.items()) bằng Python 3.

Nếu bạn không thực sự muốn đi qua MRO và chỉ muốn thuộc tính được định nghĩa trực tiếp trên lớp con sau đó nó dễ dàng hơn:

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. Có mắt quan sát. Còn gì nữa không? – aaronasterling

+0

đừng nghĩ vậy. Tôi chỉ đang chơi người ủng hộ ma quỷ sau đó khi tôi chỉ nghĩ về '__slots__' vào thời điểm đó (cố gắng tìm ra một số thứ với PyPy) –

6

Dấu gạch dưới kép trên cả hai đầu của 'thuộc tính đặc biệt' là một phần của python trước 2.0. Nó sẽ rất không chắc rằng họ sẽ thay đổi bất cứ lúc nào trong tương lai gần.

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

điều gì về người dùng được xác định '__add__',' __mul__', '__iter__', v.v ...? – aaronasterling

+0

Nếu chúng được người dùng xác định, thì tôi cũng muốn những người đó. Có thể thu được cây AST cho một lớp đã được phân tích cú pháp và biên dịch byte không? –

+0

@ jakob, không, không thể lấy AST cho mã "còn sống", vì nguồn không còn giữ lại trong bộ nhớ sau khi được phân tích cú pháp, và có trường hợp khi Python hết mã byte ngay cả khi không có nguồn, vì vậy không thể có AST. – toriningen

2

Nếu bạn sử dụng các lớp kiểu mới, bạn có thể chỉ cần trừ các thuộc tính của lớp cha?

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

Chỉnh sửa: Không hoàn toàn. __dict__, __module____weakref__ xuất hiện khi kế thừa từ đối tượng, nhưng không có trong chính đối tượng.Bạn có thể đặc biệt trường hợp này - Tôi nghi ngờ họ sẽ thay đổi rất thường xuyên.

3

Cảm ơn aaronasterling, bạn đã cho tôi sự biểu hiện tôi cần :-) thức chức năng thanh tra thuộc tính lớp của tôi trông như thế này:

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 

Hoặc trở lại lớp thuộc tính chỉ variabels (exclude_methods = True) hoặc cũng lấy phương pháp. Các thử nghiệm ban đầu của tôi và chức năng trên hỗ trợ cả các lớp python cũ và kiểu mới.

/Jakob

+0

đẹp. Một cải tiến có thể được thực hiện là chuyển đổi kiểm tra 'exclude_methods' và' callable (getattr (...)) 'để' callable' chỉ chạy nếu kiểm tra boolean đơn giản thất bại. – aaronasterling

+0

Bạn có quyền :-) –

Các vấn đề liên quan