hasattr () so với khối try-exception để xử lý các thuộc tính không tồn tại


Câu trả lời:


82

hasattrthực hiện nội bộ và nhanh chóng cùng một nhiệm vụ như try/exceptkhối: đó là một công cụ một tác vụ rất cụ thể, được tối ưu hóa, và do đó nên được ưu tiên, khi có thể, thay thế cho mục đích chung.


8
Ngoại trừ việc bạn vẫn cần khối try / catch để xử lý các điều kiện cuộc đua (nếu bạn đang sử dụng luồng).
Douglas Leeder

1
Hoặc, trường hợp đặc biệt mà tôi vừa gặp phải: django OneToOneField không có giá trị: hasattr (obj, field_name) trả về False, nhưng có một thuộc tính với field_name: nó chỉ gây ra lỗi DoesNotExist.
Matthew Schinckel

3
Lưu ý rằng hasattrsẽ bắt tất cả các ngoại lệ trong Python 2.x. Xem câu trả lời của tôi để biết ví dụ và giải pháp nhỏ.
Martin Geisler

5
Một nhận xét thú vị : trycó thể truyền đạt rằng hoạt động sẽ hoạt động. Mặc dù tryý định của không phải lúc nào cũng như vậy, nhưng nó là phổ biến, vì vậy nó có thể được coi là dễ đọc hơn.
Ioannis Filippidis

88

Bất kỳ băng ghế nào minh họa sự khác biệt về hiệu suất?

đến lúc đó là bạn của bạn

$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'hasattr(c, "nonexistent")'
1000000 loops, best of 3: 1.87 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'hasattr(c, "a")'
1000000 loops, best of 3: 0.446 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'try:
 c.a
except:
 pass'
1000000 loops, best of 3: 0.247 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'try:
 c.nonexistent
except:
 pass'
100000 loops, best of 3: 3.13 usec per loop
$

       |positive|negative
hasattr|  0.446 |  1.87 
try    |  0.247 |  3.13

16
+1 để cung cấp những con số hữu hình, thú vị. Trên thực tế, "try" hiệu quả khi nó chứa trường hợp chung (tức là khi một ngoại lệ Python thực sự đặc biệt).
Eric O Lebigot

Tôi không chắc cách giải thích những kết quả này. Ở đây cái nào nhanh hơn và bằng bao nhiêu?
Stevoisiak

2
@ StevenM.Vascellaro: Nếu thuộc tính tồn tại, trytốc độ nhanh gấp đôi hasattr(). Nếu không, trychậm hơn khoảng 1,5 lần hasattr()(và cả hai đều chậm hơn đáng kể so với nếu thuộc tính tồn tại). Điều này có lẽ là do, trên con đường hạnh phúc, tryhầu như không làm được gì (Python đã trả phí cho các ngoại lệ bất kể bạn có sử dụng chúng hay không), nhưng hasattr()yêu cầu tra cứu tên và gọi hàm. Trên con đường không vui, cả hai đều phải thực hiện một số xử lý ngoại lệ và a goto, nhưng hasattr()thực hiện bằng C chứ không phải là mã byte Python.
Kevin

24

Có một thứ ba, và thường tốt hơn, thay thế:

attr = getattr(obj, 'attribute', None)
if attr is not None:
     print attr

Ưu điểm:

  1. getattrkhông có hành vi nuốt chửng ngoại lệ xấu được Martin Geiser chỉ ra - ở Trăn già, hasattrthậm chí sẽ nuốt chửng a KeyboardInterrupt.

  2. Lý do bình thường mà bạn đang kiểm tra xem đối tượng có thuộc tính hay không là để bạn có thể sử dụng thuộc tính và điều này tự nhiên dẫn đến thuộc tính đó.

  3. Thuộc tính được đọc nguyên tử và an toàn trước các luồng khác thay đổi đối tượng. (Mặc dù vậy, nếu đây là một mối quan tâm lớn, bạn có thể muốn xem xét việc khóa đối tượng trước khi truy cập nó.)

  4. Nó ngắn hơn try/finallyvà thường ngắn hơn hasattr.

  5. Một except AttributeErrorkhối rộng có thể bắt khác khối AttributeErrorsmà bạn đang mong đợi, điều này có thể dẫn đến hành vi khó hiểu.

  6. Việc truy cập một thuộc tính chậm hơn so với truy cập một biến cục bộ (đặc biệt nếu nó không phải là một thuộc tính phiên bản thuần túy). (Tuy nhiên, thành thật mà nói, tối ưu hóa vi mô trong Python thường là một việc vặt vãnh.)

Một điều cần cẩn thận là nếu bạn quan tâm đến trường hợp obj.attributeđược đặt thành Không, bạn sẽ cần sử dụng một giá trị sentinel khác.


1
+1 - Điều này liên minh với dict.get ('my_key', 'default_value') và sẽ được biết đến rộng rãi hơn

1
Tuyệt vời cho trường hợp sử dụng phổ biến nơi bạn muốn kiểm tra sự tồn tại và sử dụng thuộc tính với giá trị mặc định.
dsalaj

18

Tôi hầu như luôn sử dụng hasattr: đó là lựa chọn chính xác cho hầu hết các trường hợp.

Trường hợp có vấn đề là khi một lớp ghi đè __getattr__: hasattrsẽ bắt tất cả các ngoại lệ thay vì bắt AttributeErrorgiống như bạn mong đợi. Nói cách khác, mã bên dưới sẽ được in b: Falsemặc dù sẽ thích hợp hơn nếu thấy một ValueErrorngoại lệ:

class X(object):
    def __getattr__(self, attr):
        if attr == 'a':
            return 123
        if attr == 'b':
            raise ValueError('important error from your database')
        raise AttributeError

x = X()
print 'a:', hasattr(x, 'a')
print 'b:', hasattr(x, 'b')
print 'c:', hasattr(x, 'c')

Lỗi quan trọng do đó đã biến mất. Điều này đã được sửa trong Python 3.2 ( issue9666 ), nơi hasattrhiện chỉ bắt được AttributeError.

Một cách giải quyết dễ dàng là viết một hàm tiện ích như sau:

_notset = object()

def safehasattr(thing, attr):
    return getattr(thing, attr, _notset) is not _notset

Điều này hãy getattrđối phó với tình huống và sau đó nó có thể đưa ra ngoại lệ thích hợp.


2
Điều này cũng đã được cải thiện một chút trong Python2.6 để hasattrít nhất sẽ không bị bắt, KeyboardInterruptv.v.
poolie

Hoặc, thay vì safehasattrchỉ sử dụng getattrđể sao chép giá trị vào một biến cục bộ nếu bạn định sử dụng nó, điều mà bạn hầu như luôn làm.
poolie

@poolie Thật tuyệt, tôi không biết nó hasattrđã được cải thiện như vậy.
Martin Geisler

Vâng, nó là tốt. Tôi cũng không biết điều đó cho đến hôm nay khi tôi định nói với ai đó để tránh hasattr, và đi kiểm tra. Chúng tôi đã có một số lỗi bzr hài hước nơi hasattr vừa nuốt ^ C.
poolie

Đã gặp sự cố khi nâng cấp 2.7 lên 3.6. Câu trả lời này giúp tôi hiểu và giải quyết vấn đề.
Kamesh Jungi

13

Tôi sẽ nói rằng nó phụ thuộc vào việc hàm của bạn có thể chấp nhận các đối tượng không có thuộc tính theo thiết kế hay không , ví dụ: nếu bạn có hai người gọi hàm, một người cung cấp một đối tượng có thuộc tính và người kia cung cấp một đối tượng không có nó.

Nếu trường hợp duy nhất mà bạn nhận được một đối tượng không có thuộc tính là do lỗi nào đó, thì tôi khuyên bạn nên sử dụng cơ chế ngoại lệ mặc dù nó có thể chậm hơn, vì tôi tin rằng đó là một thiết kế gọn gàng hơn.

Điểm mấu chốt: Tôi nghĩ đó là vấn đề thiết kế và khả năng đọc hơn là vấn đề hiệu quả.


1
+1 vì nhấn mạnh lý do tại sao "thử" có ý nghĩa đối với những người đọc mã. :)
Eric O Lebigot

5

Nếu không có thuộc tính không phải là điều kiện lỗi, thì biến thể xử lý ngoại lệ có vấn đề: nó sẽ bắt các lỗi AttributeErrors có thể xuất hiện bên trong khi truy cập obj.attribute (ví dụ: vì thuộc tính là thuộc tính nên việc truy cập nó gọi một số mã).


Theo tôi đây là một vấn đề lớn đã bị bỏ qua.
Rick ủng hộ Monica

5

Chủ đề này đã được đề cập trong bài nói chuyện EuroPython 2016 Viết Python nhanh hơn của Sebastian Witowski. Đây là bản sao của trang trình bày của anh ấy với tóm tắt hiệu suất. Anh ấy cũng sử dụng giao diện thuật ngữ trước khi bạn bước vào cuộc thảo luận này, điều đáng nói ở đây là gắn thẻ từ khóa đó.

Nếu thuộc tính thực sự bị thiếu thì việc cầu xin sự tha thứ sẽ chậm hơn so với việc yêu cầu quyền. Vì vậy, theo nguyên tắc chung, bạn có thể sử dụng cách xin phép nếu biết rằng rất có thể thuộc tính sẽ bị thiếu hoặc các vấn đề khác mà bạn có thể dự đoán. Nếu không, nếu bạn mong đợi mã sẽ dẫn đến hầu hết các lần mã có thể đọc được

3 SỰ CHO PHÉP HAY QUÊN?

# CASE 1 -- Attribute Exists
class Foo(object):
    hello = 'world'
foo = Foo()

if hasatter(foo, 'hello'):
    foo.hello
## 149ns ##

try:
    foo.hello
except AttributeError:
    pass
## 43.1 ns ##
## 3.5 times faster


# CASE 2 -- Attribute Absent
class Bar(object):
    pass
bar = Bar()

if hasattr(bar, 'hello'):
    bar.hello
## 428 ns ##

try:
    bar.hello
except AttributeError :
    pass
## 536 ns ##
## 25% slower

4

Nếu đó chỉ là một thuộc tính bạn đang thử nghiệm, tôi muốn nói là sử dụng hasattr. Tuy nhiên, nếu bạn đang thực hiện một số quyền truy cập vào các thuộc tính có thể tồn tại hoặc có thể không tồn tại thì việc sử dụng một trykhối có thể giúp bạn tiết kiệm một số thao tác nhập.


3

Tôi đề xuất tùy chọn 2. Tùy chọn 1 có điều kiện cuộc đua nếu một số luồng khác đang thêm hoặc xóa thuộc tính.

Ngoài ra python còn có một Idiom , EAFP ('dễ xin sự tha thứ hơn là xin phép') tốt hơn LBYL ('hãy nhìn trước khi bạn nhảy').


2

Từ quan điểm thực tế, trong hầu hết các ngôn ngữ sử dụng điều kiện sẽ luôn nhanh hơn đáng kể so với xử lý một ngoại lệ.

Nếu bạn muốn xử lý trường hợp thuộc tính không tồn tại ở đâu đó bên ngoài hàm hiện tại, thì ngoại lệ là cách tốt hơn để thực hiện. Một chỉ báo mà bạn có thể muốn sử dụng ngoại lệ thay vì điều kiện là điều kiện chỉ đơn thuần đặt một cờ và hủy bỏ hoạt động hiện tại, và một cái gì đó ở nơi khác sẽ kiểm tra cờ này và thực hiện hành động dựa trên đó.

Điều đó nói rằng, như Rax Olgud đã chỉ ra, giao tiếp với người khác là một thuộc tính quan trọng của mã và điều bạn muốn nói bằng cách nói "đây là một tình huống ngoại lệ" thay vì "đây là điều tôi mong đợi sẽ xảy ra" có thể quan trọng hơn .


+1 vì nhấn mạnh vào thực tế rằng "try" có thể được hiểu là "đây là một tình huống ngoại lệ", so với kiểm tra điều kiện. :)
Eric O Lebigot

0

Đầu tiên.

Ngắn hơn là tốt hơn. Các trường hợp ngoại lệ nên đặc biệt.


5
Các ngoại lệ rất phổ biến trong Python - có một ngoại lệ ở cuối mỗi forcâu lệnh và cũng hasattrsử dụng một ngoại lệ. Tuy nhiên, "ngắn hơn là tốt hơn" (và "đơn giản hơn là tốt hơn"!) NÊN áp dụng, vì vậy hasattr đơn giản hơn, ngắn hơn, cụ thể hơn thực sự thích hợp hơn.
Alex Martelli

@Alex chỉ vì trình phân tích cú pháp Python biến đổi các câu lệnh đó thành 1 không có nghĩa là nó rất phổ biến. Có một lý do tại sao họ tạo ra đường cú pháp đó: vì vậy bạn không bị mắc kẹt với sự phức tạp của việc gõ khối thử ngoại trừ.
Không xác định

Nếu ngoại lệ là đặc biệt, thì "rõ ràng là tốt hơn", và lựa chọn thứ 2 của người đăng ban đầu tốt hơn, tôi muốn nói ...
Eric O Lebigot

0

Ít nhất là khi tùy thuộc vào những gì đang diễn ra trong chương trình, bỏ qua phần con người có thể đọc được, v.v. như Roee Adler và những người khác đã chỉ ra).

Tuy nhiên, nhìn nó từ góc độ đó, nó trở thành vấn đề lựa chọn giữa

try: getattr(obj, attr)
except: ...

try: obj.attr
except: ...

hasattrchỉ sử dụng trường hợp đầu tiên để xác định kết quả. Thức ăn cho suy nghĩ ;-)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.