if hasattr(obj, 'attribute'):
# do somthing
vs
try:
# access obj.attribute
except AttributeError, e:
# deal with AttributeError
Cái nào nên được ưu tiên và tại sao?
Câu trả lời:
hasattr
thực hiện nội bộ và nhanh chóng cùng một nhiệm vụ như try/except
khố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.
hasattr
sẽ 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ỏ.
try
có 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.
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
try
tốc độ nhanh gấp đôi hasattr()
. Nếu không, try
chậ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, try
hầ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.
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:
getattr
không có hành vi nuốt chửng ngoại lệ xấu được Martin Geiser chỉ ra - ở Trăn già, hasattr
thậm chí sẽ nuốt chửng a KeyboardInterrupt
.
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 đó.
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ó.)
Nó ngắn hơn try/finally
và thường ngắn hơn hasattr
.
Một except AttributeError
khối rộng có thể bắt khác khối AttributeErrors
mà bạn đang mong đợi, điều này có thể dẫn đến hành vi khó hiểu.
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.
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__
: hasattr
sẽ bắt tất cả các ngoại lệ thay vì bắt AttributeError
giống như bạn mong đợi. Nói cách khác, mã bên dưới sẽ được in b: False
mặc dù sẽ thích hợp hơn nếu thấy một ValueError
ngoạ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 hasattr
hiệ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.
hasattr
ít nhất sẽ không bị bắt, KeyboardInterrupt
v.v.
safehasattr
chỉ 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.
hasattr
đã được cải thiện như vậy.
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.
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ả.
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ã).
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
# 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
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').
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 .
Đầ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.
for
câu lệnh và cũng hasattr
sử 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.
Í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: ...
và
try: obj.attr
except: ...
vì hasattr
chỉ sử dụng trường hợp đầu tiên để xác định kết quả. Thức ăn cho suy nghĩ ;-)