hasattr () vs try-except block เพื่อจัดการกับแอตทริบิวต์ที่ไม่มีอยู่จริง


85
if hasattr(obj, 'attribute'):
    # do somthing

เทียบกับ

try:
    # access obj.attribute
except AttributeError, e:
    # deal with AttributeError

ควรเลือกแบบไหนและเพราะเหตุใด


คำตอบ:


82

hasattrภายในและรวดเร็วทำงานเช่นเดียวกับtry/exceptบล็อก: เป็นเครื่องมือที่เฉพาะเจาะจงมากที่สุดได้รับการปรับให้เหมาะสมและเป็นงานเดียวดังนั้นจึงควรเป็นที่ต้องการสำหรับทางเลือกที่ใช้งานทั่วไป


8
ยกเว้นคุณยังต้องการบล็อก try / catch เพื่อจัดการกับเงื่อนไขการแข่งขัน (หากคุณใช้เธรด)
Douglas Leeder

1
หรือกรณีพิเศษที่ฉันเพิ่งเจอ: django OneToOneField ที่ไม่มีค่า: hasattr (obj, field_name) ส่งคืน False แต่มีแอตทริบิวต์ที่มี field_name: มันทำให้เกิดข้อผิดพลาด DoesNotExist
Matthew Schinckel

3
โปรดทราบว่าhasattrจะจับข้อยกเว้นทั้งหมดใน Python 2.x. ดูคำตอบของฉันสำหรับตัวอย่างและวิธีแก้ปัญหาเล็กน้อย
Martin Geisler

5
ความคิดเห็นที่น่าสนใจ: tryสามารถสื่อได้ว่าการดำเนินการควรได้ผล แม้ว่าtryเจตนาจะไม่เป็นเช่นนั้นเสมอไป แต่ก็เป็นเรื่องธรรมดาดังนั้นจึงอาจถือว่าอ่านได้ง่าย
Ioannis Filippidis

88

ม้านั่งใดที่แสดงให้เห็นถึงความแตกต่างในประสิทธิภาพ?

ถึงเวลาก็คือเพื่อนของคุณ

$ 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 สำหรับการให้ตัวเลขที่น่าสนใจและจับต้องได้ ในความเป็นจริง "ลอง" จะมีประสิทธิภาพเมื่อมีกรณีทั่วไป (เช่นเมื่อข้อยกเว้นของ Python นั้นยอดเยี่ยมจริงๆ)
Eric O Lebigot

ฉันไม่แน่ใจว่าจะตีความผลลัพธ์เหล่านี้อย่างไร ที่นี่เร็วกว่าและเท่าไหร่
Stevoisiak

2
@ StevenM.Vascellaro: ถ้าแอตทริบิวต์ที่มีอยู่เป็นเรื่องเกี่ยวกับรวดเร็วเป็นสองเท่าtry hasattr()หากไม่tryเป็นเช่นนั้นจะช้ากว่าประมาณ 1.5 เท่าhasattr()(และทั้งสองอย่างช้ากว่าแอตทริบิวต์อย่างมาก) อาจเป็นเพราะบนเส้นทางแห่งความสุขtryแทบจะไม่ทำอะไรเลย (Python จ่ายค่าใช้จ่ายสำหรับข้อยกเว้นอยู่แล้วไม่ว่าคุณจะใช้หรือไม่ก็ตาม) แต่hasattr()ต้องการการค้นหาชื่อและการเรียกใช้ฟังก์ชัน บนเส้นทางที่ไม่มีความสุขทั้งคู่ต้องจัดการข้อยกเว้นและ a gotoแต่hasattr()ทำใน C แทนที่จะเป็น Python bytecode
Kevin

24

มีทางเลือกที่สามและมักจะดีกว่า:

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

ข้อดี:

  1. getattrไม่มีพฤติกรรมการกลืนข้อยกเว้นที่ไม่ดีที่Martin Geiser ชี้ให้เห็น - ใน Pythons เก่า ๆhasattrจะกลืนกKeyboardInterrupt.

  2. เหตุผลปกติที่คุณกำลังตรวจสอบว่าวัตถุนั้นมีแอตทริบิวต์หรือไม่ก็เพื่อที่คุณจะสามารถใช้แอตทริบิวต์ได้และสิ่งนี้จะนำไปสู่มันโดยธรรมชาติ

  3. แอตทริบิวต์ถูกอ่านออกโดยอะตอมและปลอดภัยจากเธรดอื่นที่เปลี่ยนวัตถุ (แม้ว่านี่จะเป็นข้อกังวลหลักคุณอาจต้องพิจารณาล็อกวัตถุก่อนเข้าถึง)

  4. มันสั้นกว่าและมักจะสั้นกว่าtry/finallyhasattr

  5. except AttributeErrorบล็อกกว้าง ๆสามารถจับสิ่งอื่นAttributeErrorsนอกเหนือจากที่คุณคาดหวังซึ่งอาจนำไปสู่พฤติกรรมที่สับสน

  6. การเข้าถึงแอตทริบิวต์ช้ากว่าการเข้าถึงตัวแปรภายใน (โดยเฉพาะอย่างยิ่งถ้าไม่ใช่แอตทริบิวต์อินสแตนซ์ธรรมดา) (พูดตามตรงการเพิ่มประสิทธิภาพไมโครใน Python มักเป็นธุระของคนโง่)

สิ่งหนึ่งที่ต้องระวังคือหากคุณสนใจเกี่ยวกับกรณีที่obj.attributeตั้งค่าเป็นไม่มีคุณจะต้องใช้ค่ายามอื่น


1
+1 - อยู่ในลีกที่มี dict.get ('my_key', 'default_value') และควรเป็นที่รู้จักในวงกว้างมากขึ้น

1
เหมาะสำหรับกรณีการใช้งานทั่วไปที่คุณต้องการตรวจสอบการมีอยู่และใช้แอตทริบิวต์ที่มีค่าเริ่มต้น
dsalaj

18

ฉันมักจะใช้ hasattrมันเป็นตัวเลือกที่ถูกต้องสำหรับกรณีส่วนใหญ่

กรณีที่เป็นปัญหาคือเมื่อคลาสแทนที่__getattr__: hasattrจะจับข้อยกเว้นทั้งหมดแทนที่จะจับAttributeErrorเหมือนที่คุณคาดไว้ กล่าวอีกนัยหนึ่งโค้ดด้านล่างจะพิมพ์b: Falseแม้ว่าจะเหมาะสมกว่าหากเห็นValueErrorข้อยกเว้น:

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

ข้อผิดพลาดที่สำคัญจึงหายไป สิ่งนี้ได้รับการแก้ไขแล้วใน Python 3.2 ( issue9666 ) ซึ่งhasattrตอนนี้จับได้เท่านั้นAttributeErrorตอนนี้จับเท่านั้น

วิธีแก้ปัญหาง่ายๆคือการเขียนฟังก์ชันยูทิลิตี้ดังนี้:

_notset = object()

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

เรามาgetattrจัดการกับสถานการณ์และจะสามารถเพิ่มข้อยกเว้นที่เหมาะสมได้


2
สิ่งนี้ได้รับการปรับปรุงเล็กน้อยใน Python2.6 ดังนั้นhasattrอย่างน้อยก็จะไม่จับKeyboardInterruptฯลฯ
poolie

หรือแทนที่จะsafehasattrใช้getattrเพื่อคัดลอกค่าไปยังตัวแปรท้องถิ่นหากคุณจะใช้ซึ่งคุณมักจะเป็น
poolie

@poolie ดีจังไม่รู้ว่าhasattrได้รับการปรับปรุงแบบนั้น
Martin Geisler

ใช่มันเป็นสิ่งที่ดี ฉันไม่รู้ว่าจนถึงวันนี้เมื่อฉันกำลังจะบอกให้ใครบางคนหลีกเลี่ยงhasattrและไปตรวจสอบ เรามีบั๊ก bzr ตลก ๆ ที่ hasattr เพิ่งกลืน ^ C
poolie

พบปัญหาขณะอัปเกรด 2.7 เป็น 3.6 คำตอบนี้ช่วยให้ฉันเข้าใจและแก้ไขปัญหาได้
Kamesh Jungi

13

ฉันจะบอกว่ามันขึ้นอยู่กับว่าฟังก์ชันของคุณอาจรับวัตถุที่ไม่มีแอตทริบิวต์ตามการออกแบบหรือไม่หรือไม่เช่นถ้าคุณมีผู้เรียกใช้ฟังก์ชันสองคนคนหนึ่งให้วัตถุที่มีแอตทริบิวต์และอีกคนหนึ่งให้วัตถุที่ไม่มี

หากในกรณีเดียวที่คุณจะได้รับวัตถุที่ไม่มีแอตทริบิวต์นั้นเกิดจากข้อผิดพลาดบางอย่างฉันขอแนะนำให้ใช้กลไกข้อยกเว้นแม้ว่ามันอาจจะช้ากว่าก็ตามเพราะฉันเชื่อว่ามันเป็นการออกแบบที่สะอาดกว่า

บรรทัดล่าง: ฉันคิดว่ามันเป็นปัญหาการออกแบบและความสามารถในการอ่านมากกว่าปัญหาด้านประสิทธิภาพ


1
+1 สำหรับการยืนยันว่าเหตุใด "ลอง" จึงมีความหมายสำหรับผู้ที่อ่านโค้ด :)
Eric O Lebigot

5

หากไม่มีแอตทริบิวต์ไม่ใช่เงื่อนไขข้อผิดพลาดตัวแปรการจัดการข้อยกเว้นจะมีปัญหา: มันจะจับ AttributeErrors ที่อาจเกิดขึ้นภายในเมื่อเข้าถึง obj.attribute (เช่นเนื่องจากแอตทริบิวต์เป็นคุณสมบัติเพื่อให้การเข้าถึงเรียกใช้รหัส)


นี่เป็นปัญหาสำคัญที่ถูกละเลยอย่างมากในความคิดของฉัน
Rick สนับสนุน Monica

5

หัวข้อนี้ครอบคลุมในการพูดคุย EuroPython 2016 การเขียน Python ที่เร็วขึ้นโดย Sebastian Witowski นี่คือการจำลองสไลด์ของเขาพร้อมสรุปประสิทธิภาพ นอกจากนี้เขายังใช้คำศัพท์ก่อนที่คุณจะก้าวกระโดดในการสนทนานี้ซึ่งควรค่าแก่การกล่าวถึงที่นี่เพื่อแท็กคำหลักนั้น

หากแอตทริบิวต์หายไปจริงการขอการให้อภัยจะช้ากว่าการขอสิทธิ์ ตามหลักทั่วไปคุณสามารถใช้วิธีขออนุญาตได้หากทราบว่ามีโอกาสมากที่แอตทริบิวต์จะหายไปหรือมีปัญหาอื่น ๆ ที่คุณสามารถคาดเดาได้ มิฉะนั้นหากคุณคาดหวังว่าโค้ดจะส่งผลให้โค้ดส่วนใหญ่สามารถอ่านได้

3 สิทธิ์หรือการให้อภัย?

# 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

hasattrถ้ามันเป็นเพียงหนึ่งในคุณลักษณะที่คุณกำลังทดสอบผมว่าการใช้งาน อย่างไรก็ตามหากคุณกำลังเข้าถึงแอตทริบิวต์หลายรายการซึ่งอาจมีหรือไม่มีอยู่การใช้tryบล็อกอาจช่วยให้คุณประหยัดเวลาในการพิมพ์


3

ฉันขอแนะนำตัวเลือก 2 ตัวเลือกที่ 1 มีเงื่อนไขการแย่งชิงหากเธรดอื่นกำลังเพิ่มหรือลบแอตทริบิวต์

นอกจากนี้ python ยังมีสำนวน EAFP ('ง่ายกว่าที่จะขอการให้อภัยมากกว่าการอนุญาต') ดีกว่า LBYL ('ดูก่อนกระโดด')


2

จากมุมมองในทางปฏิบัติในภาษาส่วนใหญ่ที่ใช้เงื่อนไขจะเร็วกว่าการจัดการข้อยกเว้น

หากคุณต้องการจัดการกับกรณีของแอตทริบิวต์ที่ไม่มีอยู่นอกฟังก์ชันปัจจุบันข้อยกเว้นเป็นวิธีที่ดีกว่า ตัวบ่งชี้ที่คุณอาจต้องการใช้ข้อยกเว้นแทนเงื่อนไขคือเงื่อนไขเป็นเพียงการตั้งค่าสถานะและยกเลิกการดำเนินการปัจจุบันและบางสิ่งบางอย่างตรวจสอบแฟล็กนี้และดำเนินการตามนั้น

ดังที่ Rax Olgud ชี้ให้เห็นว่าการสื่อสารกับผู้อื่นเป็นคุณลักษณะที่สำคัญอย่างหนึ่งของรหัสและสิ่งที่คุณต้องการจะพูดโดยพูดว่า "นี่เป็นสถานการณ์พิเศษ" แทนที่จะเป็น "นี่คือสิ่งที่ฉันคาดว่าจะเกิดขึ้น" อาจสำคัญกว่า .


+1 สำหรับการยืนยันความจริงที่ว่า "ลอง" สามารถตีความได้ว่า "นี่เป็นสถานการณ์พิเศษ" เมื่อเทียบกับการทดสอบตามเงื่อนไข :)
Eric O Lebigot

0

ครั้งแรก.

สั้นจะดีกว่า ข้อยกเว้นควรมีความพิเศษ


5
ข้อยกเว้นเป็นเรื่องปกติมากใน Python - มีหนึ่งข้อในตอนท้ายของทุกforคำสั่งและhasattrใช้อย่างใดอย่างหนึ่งด้วย อย่างไรก็ตาม "สั้นกว่าดีกว่า" (และ "ง่ายกว่าดีกว่า"!) ใช้ดังนั้น hasattr ที่ง่ายกว่าสั้นกว่าและเฉพาะเจาะจงมากขึ้นจึงเป็นที่นิยมมากกว่า
Alex Martelli

@Alex เพียงเพราะตัวแยกวิเคราะห์ Python แปลงข้อความเหล่านั้นให้มี 1 ไม่ได้หมายความว่าเป็นเรื่องธรรมดา มีเหตุผลว่าทำไมพวกเขาถึงสร้างน้ำตาลวากยสัมพันธ์: ดังนั้นคุณจึงไม่ติดอยู่กับความโหดร้ายของการพิมพ์ try ยกเว้นบล็อก
Unknown

หากข้อยกเว้นนั้นยอดเยี่ยมแสดงว่า "ชัดเจนดีกว่า" และตัวเลือกที่ 2 ของผู้โพสต์ต้นฉบับดีกว่าฉันจะบอกว่า ...
Eric O Lebigot

0

อย่างน้อยเมื่อมันขึ้นอยู่กับสิ่งที่เกิดขึ้นในโปรแกรมการละเว้นส่วนที่เป็นมนุษย์ของความสามารถในการอ่าน ฯลฯ (ซึ่งจริงๆแล้วเวลาส่วนใหญ่ไม่สำคัญมากกว่าประสิทธิภาพ (อย่างน้อยก็ในกรณีนี้ - ด้วยช่วงประสิทธิภาพนั้น) ตามที่ Roee Adler และคนอื่น ๆ ชี้ให้เห็น)

อย่างไรก็ตามเมื่อมองจากมุมมองนั้นมันก็กลายเป็นเรื่องของการเลือกระหว่าง

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

และ

try: obj.attr
except: ...

เนื่องจากhasattrใช้เพียงกรณีแรกในการพิจารณาผลลัพธ์ อาหารสมอง ;-)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.