2012-06-20 16 views
13

Tôi đang đấu tranh với __getattr__. Tôi có một codebase đệ quy phức tạp, trong đó điều quan trọng là để cho các ngoại lệ lan truyền.Python: Tại sao __getattr__ bắt AttributeErrors?

class A(object): 
    @property 
    def a(self): 
     raise AttributeError('lala') 

    def __getattr__(self, name):  
     print('attr: ', name) 
     return 1  

print(A().a) 

Kết quả trong:

('attr: ', 'a') 
1 

Tại sao hành vi này? Tại sao không có ngoại lệ ném? Hành vi này không được ghi chép (__getattr__ documentation). getattr() chỉ có thể sử dụng A.__dict__. Có suy nghĩ gì không?

+2

Tôi sẽ nói rằng sự thất bại của tài liệu là nó nói rằng 'AttributeError' có thể được nâng lên trong' __get__' (đó là những gì bạn đang thực hiện ở đây) nhưng không giải thích hiệu ứng có. Nó có lẽ nên nói rằng 'AttributeError' tín hiệu để python rằng nó sẽ hành xử như thể thuộc tính đã không được tìm thấy (để' __getattr__' được cố gắng như một bản sao lưu, như thường lệ) - nếu đó không phải là hành vi mà bạn muốn, sau đó bạn nên nâng thứ gì đó khác. – James

Trả lời

9

Tôi chỉ thay đổi mã để

class A(object): 
    @property 
    def a(self): 
     print "trying property..." 
     raise AttributeError('lala') 
    def __getattr__(self, name):  
     print('attr: ', name) 
     return 1  

print(A().a) 

và, như chúng ta thấy, thực sự tài sản được thử đầu tiên. Nhưng vì nó tuyên bố không ở đó (bằng cách tăng AttributeError), __getattr__() được gọi là "phương sách cuối cùng".

Tài liệu không được ghi rõ ràng, nhưng có thể được tính trong "Được gọi khi tra cứu thuộc tính không tìm thấy thuộc tính ở các vị trí thông thường".

+0

và cách được khuyến nghị để thực hiện điều này thay thế là gì? Không sử dụng các thuộc tính hoặc sử dụng '__getattribute__' thay vì' __getattr__'? –

+2

Tôi sẽ nói không nâng 'AttributeError', hoặc xử lý trường hợp đó trong' __getattr__', hoặc sử dụng '__getattribute__' và gọi' super (...) .__ getattribute__', bắt 'AttributeError'. – glglgl

+1

Tôi không thể hiểu tại sao bạn muốn nâng cao 'AttributeError' một cách rõ ràng trong một thuộc tính. Hoặc bất kỳ nơi nào khác ngoài phương thức '__ (get | set) attr [ibute] __', thực sự. –

4

__getattr__ được gọi khi quyền truy cập thuộc tính không thành công với một AttributeError. Có lẽ đây là lý do tại sao bạn nghĩ rằng nó 'bắt' các lỗi. Tuy nhiên, nó không, đó là chức năng truy cập thuộc tính của Python mà bắt chúng, và sau đó gọi __getattr__.

Nhưng bản thân số __getattr__ không gặp bất kỳ lỗi nào. Nếu bạn nâng AttributeError trong __getattr__ bạn nhận được đệ quy vô hạn.

6

__getattribute__ documentation nói:

Nếu lớp cũng định nghĩa __getattr__(), sau này sẽ không được gọi là trừ khi __getattribute__() hoặc gọi nó một cách rõ ràng hoặc đặt ra một AttributeError.

tôi đọc này (bằng cách inclusio unius est exclusio alterius) nói rằng truy cập thuộc tính sẽ gọi __getattr__ nếu object.__getattribute__ (được "gọi vô điều kiện để thực hiện thuộc tính truy cập") xảy ra để nâng cao AttributeError - cho dù trực tiếp hoặc bên trong một mô tả __get__ (ví dụ như một tài sản fget); lưu ý rằng __get__ nên "trả về giá trị thuộc tính (được tính) hoặc tăng AttributeError ngoại lệ".

Như một phương pháp tương tự, toán tử đặc biệt có thể tăng NotImplementedError trong đó các phương thức toán tử khác (ví dụ: __radd__ cho __add__) sẽ được thử.

6

Sử dụng __getattr__ và thuộc tính trong cùng một lớp là nguy hiểm, vì nó có thể dẫn đến lỗi rất khó gỡ lỗi.

Nếu bộ thu của tài sản ném AttributeError, thì AttributeError bị âm thầm bắt và __getattr__ được gọi. Thông thường, điều này gây ra __getattr__ để thất bại với một ngoại lệ, nhưng nếu bạn là vô cùng không may mắn, nó không, và bạn thậm chí sẽ không thể dễ dàng theo dõi vấn đề trở lại __getattr__.

Trừ khi tài sản của bạn getter là tầm thường, bạn không bao giờ có thể chắc chắn 100% nó sẽ không ném AttributeError. Ngoại lệ có thể bị ném nhiều cấp độ sâu.

Đây là những gì bạn có thể làm:

  1. Tránh sử dụng tài sản và __getattr__ trong cùng một lớp.
  2. Thêm một khối try ... except cho tất cả các thu khí tài sản mà không phải là tầm thường
  3. Giữ getters tài sản đơn giản, vì vậy bạn biết rằng họ sẽ không ném AttributeError
  4. Viết phiên bản của riêng bạn của @property trang trí, mà bắt AttributeError và tái ném nó như RuntimeError.

Xem thêm http://blog.devork.be/2011/06/using-getattr-and-property_17.html

EDIT: Trong trường hợp bất cứ ai đang cân nhắc giải pháp 4 (mà tôi không đề nghị), nó có thể được thực hiện như thế này:

def property_(f): 
    def getter(*args, **kwargs): 
     try: 
      return f(*args, **kwargs) 
     except AttributeError as e: 
      raise RuntimeError, "Wrapped AttributeError: " + str(e), sys.exc_info()[2] 

    return property(getter) 

Sau đó sử dụng @property_ thay vì @property trong các lớp ghi đè __getattr__.

0

Bạn đang cam chịu anyways khi bạn kết hợp với @property__getattr__:

class Paradise: 
    pass 

class Earth: 
    @property 
    def life(self): 
     print('Checking for paradise (just for fun)') 
     return Paradise.breasts 
    def __getattr__(self, item): 
     print("sorry! {} does not exist in Earth".format(item)) 

earth = Earth() 
try: 
    print('Life in earth: ' + str(earth.life)) 
except AttributeError as e: 
    print('Exception found!: ' + str(e)) 

Cung cấp đầu ra sau đây:

Checking for paradise (just for fun) 
sorry! life does not exist in Earth 
Life in earth: None 

Khi vấn đề thực sự của bạn là với gọi Paradise.breasts.

__getattr__ luôn được gọi khi số AtributeError được tăng lên. Nội dung của ngoại lệ bị bỏ qua.

Điều đáng buồn là không có giải pháp cho vấn đề này trao hasattr(earth, 'life') sẽ trở lại True (chỉ vì __getattr__ được định nghĩa), nhưng sẽ vẫn đạt được bởi thuộc tính 'cuộc sống' như nó không tồn tại, trong khi tiềm ẩn thực vấn đề là với Paradise.breasts.

Giải pháp một phần của tôi liên quan đến việc sử dụng thử, ngoại trừ trong các khối @property được biết là sẽ xảy ra khi AttributeError ngoại lệ.

0

thường xuyên gặp sự cố này vì tôi triển khai __getattr__ rất nhiều và có rất nhiều phương pháp @property. Dưới đây là một trang trí tôi đã đưa ra để có được một thông báo lỗi hữu ích hơn:

def replace_attribute_error_with_runtime_error(f): 
    @functools.wraps(f) 
    def wrapped(*args, **kwargs): 
     try: 
      return f(*args, **kwargs) 
     except AttributeError as e: 
      # logging.exception(e) 
      raise RuntimeError(
       '{} failed with an AttributeError: {}'.format(f.__name__, e) 
      ) 
    return wrapped 

Và sử dụng nó như thế này:

class C(object): 

    def __getattr__(self, name): 
     ... 

    @property 
    @replace_attribute_error_with_runtime_error 
    def complicated_property(self): 
     ... 

    ... 

Thông báo lỗi của các ngoại lệ cơ bản sẽ bao gồm tên của lớp mà dụ đã nêu ra số AttributeError. Bạn cũng có thể đăng nhập nếu muốn.

+0

Tôi đã thực sự làm một cái gì đó tương tự như làm việc xung quanh nó và gọi nó là 'safe_property'. Nó làm điều tương tự, chỉ với một trang trí. –

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