ฉันจะยกระดับข้อยกเว้นเดียวกันด้วยข้อความที่กำหนดเองใน Python ได้อย่างไร


145

ฉันมีtryบล็อกนี้ในรหัสของฉัน:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise ValueError(errmsg)

พูดอย่างเคร่งครัดจริงๆแล้วฉันกำลังเลี้ยงดูคนอื่น ValueErrorไม่ใช่ที่ValueErrorถูกโยนทิ้งdo_something...()ซึ่งถูกอ้างถึงerrในกรณีนี้ ฉันจะแนบข้อความที่กำหนดเองได้errอย่างไร ฉันพยายามรหัสต่อไป แต่ล้มเหลวเนื่องจากerrเป็นValueError ตัวอย่างไม่เป็น callable:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise err(errmsg)

13
@Hamish การแนบข้อมูลเพิ่มเติมและการยกเว้นใหม่อาจมีประโยชน์มากเมื่อทำการดีบัก
Johan Lundberg

@Johan Absolutely - และนั่นคือสิ่งที่ stacktrace มีไว้สำหรับ ไม่เข้าใจว่าทำไมคุณจึงแก้ไขข้อความแสดงข้อผิดพลาดที่มีอยู่แทนที่จะเพิ่มข้อผิดพลาดใหม่
Hamish

@Hamish แน่นอน แต่คุณสามารถเพิ่มสิ่งอื่น ๆ สำหรับคำถามของคุณดูที่คำตอบของฉันและตัวอย่างของ UnicodeDecodeError หากคุณมีความคิดเห็นเกี่ยวกับสิ่งนั้นอาจแสดงความคิดเห็นคำตอบของฉันแทน
Johan Lundberg


1
@Kit มันคือ 2020 และ python 3 มีอยู่ทั่วไป ทำไมไม่เปลี่ยนคำตอบที่ได้รับการยอมรับที่จะตอบเบน :-)
mit

คำตอบ:


88

Update: สำหรับ Python 3 ตรวจสอบคำตอบของ Ben


ในการแนบข้อความไปยังข้อยกเว้นปัจจุบันและเพิ่มใหม่อีกครั้ง: (ลองภายนอก / ยกเว้นเพียงเพื่อแสดงผล)

สำหรับ python 2.x โดยที่ x> = 6:

try:
    try:
      raise ValueError  # something bad...
    except ValueError as err:
      err.message=err.message+" hello"
      raise              # re-raise current exception
except ValueError as e:
    print(" got error of type "+ str(type(e))+" with message " +e.message)

นอกจากนี้ยังจะทำสิ่งที่ถูกต้องถ้าerrจะมาValueErrorจาก UnicodeDecodeErrorเช่น

errโปรดทราบว่าคุณสามารถเพิ่มสิ่งที่คุณต้องการ err.problematic_array=[1,2,3]เช่น


แก้ไข:จุด @Ducan ในความคิดเห็นดังกล่าวข้างต้นไม่ได้ทำงานกับงูหลาม 3 ตั้งแต่ไม่ได้เป็นสมาชิกของ.message ValueErrorแต่คุณสามารถใช้สิ่งนี้ได้ (python ที่ถูกต้อง 2.6 หรือใหม่กว่าหรือ 3.x):

try:
    try:
      raise ValueError
    except ValueError as err:
       if not err.args: 
           err.args=('',)
       err.args = err.args + ("hello",)
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e.args))

Edit2:

คุณยังสามารถเลือกที่จะเพิ่มข้อมูลพิเศษภายใต้ชื่อตัวแปรของคุณเองได้ สำหรับทั้ง python2 และ python3:

try:
    try:
      raise ValueError
    except ValueError as err:
       err.extra_info = "hello"
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e))
    if 'extra_info' in dir(e):
       print e.extra_info

9
เนื่องจากคุณได้ใช้ความพยายามในการจัดการข้อยกเว้นสไตล์ Python 3 และprintคุณควรทราบว่ารหัสของคุณใช้งานไม่ได้ใน Python 3.x เนื่องจากไม่มีmessageแอตทริบิวต์ในข้อยกเว้น err.args = (err.args[0] + " hello",) + err.args[1:]อาจทำงานได้อย่างน่าเชื่อถือมากขึ้น (และจากนั้นเพียงแปลงเป็นสตริงเพื่อรับข้อความ)
ดันแคน

1
น่าเสียดายที่ไม่มีการรับประกันว่า args [0] เป็นประเภทสตริงที่แสดงถึงข้อความแสดงข้อผิดพลาด - "tuple ของการขัดแย้งที่กำหนดให้กับตัวสร้างข้อยกเว้นบางข้อยกเว้นในตัว (เช่น IOError) คาดว่าจะมีอาร์กิวเมนต์จำนวนหนึ่งและกำหนดความหมายพิเศษ องค์ประกอบของ tuple นี้ในขณะที่คนอื่น ๆ มักจะถูกเรียกด้วยสตริงเดียวที่ให้ข้อความแสดงข้อผิดพลาด ". ดังนั้นรหัสจะไม่ทำงาน arg [0] ไม่ใช่ข้อความแสดงข้อผิดพลาด (อาจเป็น int หรืออาจเป็นสตริงที่แสดงชื่อไฟล์)
เทรนต์

1
@ Taras น่าสนใจ คุณมีการอ้างอิงเกี่ยวกับเรื่องนี้หรือไม่? จากนั้นฉันจะเพิ่มให้กับสมาชิกใหม่อย่างสมบูรณ์: err.my_own_extra_info หรือแค็ปซูลมันทั้งหมดยกเว้นของฉันเองในการเก็บข้อมูลใหม่และต้นฉบับ
Johan Lundberg

2
ตัวอย่างจริงเมื่อ args [0] ไม่ใช่ข้อความแสดงข้อผิดพลาด - docs.python.org/2/library/exceptions.html - "exception EnvironmentError คลาสพื้นฐานสำหรับข้อยกเว้นที่อาจเกิดขึ้นนอกระบบ Python: IOError, OSError เมื่อข้อยกเว้นประเภทนี้ถูกสร้างขึ้นด้วย 2-tuple รายการแรกจะพร้อมใช้งานในแอตทริบิวต์ errno ของอินสแตนซ์ (ซึ่งสันนิษฐานว่าเป็นหมายเลขข้อผิดพลาด) และรายการที่สองพร้อมใช้งานในแอตทริบิวต์ strerror (โดยปกติจะเกี่ยวข้องกับ ข้อความแสดงข้อผิดพลาด) tuple เองยังมีอยู่ในแอตทริบิวต์ args ด้วย "
เทรนต์

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

171

หากคุณโชคดีพอที่จะรองรับ python 3.x ได้เท่านั้นนี่จะกลายเป็นความงาม :)

เพิ่มจาก

เราสามารถโซ่ข้อยกเว้นโดยใช้เพิ่มจาก

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks') from e

ในกรณีนี้ข้อยกเว้นที่ผู้โทรจะจับได้จะมีหมายเลขบรรทัดของสถานที่ที่เรายกข้อยกเว้นของเรา

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks') from e
Exception: Smelly socks

สังเกตว่าข้อยกเว้นด้านล่างจะมีสแต็คติดตามจากที่ที่เรายกข้อยกเว้นของเราขึ้นมา ผู้โทรของคุณยังคงสามารถรับข้อยกเว้นดั้งเดิมได้โดยเข้าถึง__cause__แอตทริบิวต์ของข้อยกเว้นที่จับได้

with_traceback

หรือคุณสามารถใช้with_traceback

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks').with_traceback(e.__traceback__)

การใช้แบบฟอร์มนี้ข้อยกเว้นที่ผู้โทรจะจับได้มีการย้อนกลับจากที่เกิดข้อผิดพลาดดั้งเดิม

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks').with_traceback(e.__traceback__)
  File "test.py", line 2, in <module>
    1 / 0
Exception: Smelly socks

สังเกตว่าข้อยกเว้นด้านล่างมีบรรทัดที่เราทำการหารที่ไม่ถูกต้องเช่นเดียวกับบรรทัดที่เราทำการยกเว้น


1
เป็นไปได้หรือไม่ที่จะเพิ่มข้อความที่กำหนดเองลงในข้อยกเว้นโดยไม่มีการติดตามย้อนกลับ ตัวอย่างเช่นสามารถraise Exception('Smelly socks') from eแก้ไขได้เพียงเพิ่ม "Smelly socks" เป็นความคิดเห็นในการติดตามกลับดั้งเดิมแทนที่จะแนะนำการย้อนกลับใหม่ของตนเอง
joelostblom

นั่นเป็นพฤติกรรมที่คุณจะได้รับจากคำตอบของ Johan Lundberg
Ben

3
น่ารักจริง ๆ ขอบคุณ.
allanberry

3
การเพิ่มข้อยกเว้นใหม่หรือการยกระดับข้อยกเว้นใหม่ด้วยข้อความใหม่จะสร้างความสับสนมากกว่าที่จำเป็นในหลายกรณี โดยตัวของมันเองนั้นมีความซับซ้อน กลยุทธ์ที่ดีกว่าคือเพิ่มข้อความของคุณต่อท้ายอาร์กิวเมนต์ของข้อยกเว้นเดิมหากเป็นไปได้ใน err.args + = ("message",) และเพิ่มข้อความข้อยกเว้นขึ้นอีกครั้ง การติดตามย้อนกลับอาจไม่นำคุณไปยังหมายเลขบรรทัดที่มีการตรวจพบข้อยกเว้น แต่จะนำคุณไปยังจุดที่เกิดข้อยกเว้นขึ้นอย่างแน่นอน
user-asterix

2
นอกจากนี้คุณยังสามารถระงับการแสดงห่วงโซ่ข้อยกเว้นโดยชัดเจนโดยระบุไม่มีในจากข้อ:raise RuntimeError("Something bad happened") from None
pfabri

10
try:
    try:
        int('a')
    except ValueError as e:
        raise ValueError('There is a problem: {0}'.format(e))
except ValueError as err:
    print err

พิมพ์:

There is a problem: invalid literal for int() with base 10: 'a'

1
ผมสงสัยว่าถ้ามีสำนวนหลามสำหรับสิ่งที่ฉันพยายามที่จะทำนอกเหนือจากการเพิ่มอีกเช่น
ชุด

@Kit - ฉันจะเรียกมันว่าเป็นการเพิ่มข้อยกเว้นอีกครั้ง: docs.python.org/reference/simple_stmts.html#raise
eumiro

1
@ eumiro ไม่คุณกำลังสร้างข้อยกเว้นใหม่ ดูคำตอบของฉัน จากลิงก์ของคุณ: "... แต่ควรเพิ่มโดยไม่ต้องมีนิพจน์หากข้อยกเว้นที่จะถูกยกขึ้นใหม่นั้นเป็นข้อยกเว้นที่ใช้งานล่าสุดในขอบเขตปัจจุบัน"
Johan Lundberg

3
@JohanLundberg - raiseโดยไม่มีพารามิเตอร์เป็นการเพิ่มอีกครั้ง ถ้า OP ต้องการเพิ่มข้อความเขาต้องเพิ่มข้อยกเว้นใหม่และสามารถใช้ข้อความ / ประเภทของข้อยกเว้นเดิมได้อีกครั้ง
eumiro

2
หากคุณต้องการเพิ่มข้อความคุณไม่สามารถสร้างข้อความใหม่ตั้งแต่ต้นโดยการขว้าง "ValueError" ด้วยการทำเช่นนี้คุณจะทำลายข้อมูลพื้นฐานของ ValueError ประเภทใด (คล้ายกับการแบ่งส่วนใน C ++) โดยเรื่องการขว้างปาเดียวกันยกเว้นมีการเพิ่มโดยไม่โต้แย้งคุณผ่านวัตถุเดิมกับที่ถูกต้องประเภทที่เฉพาะเจาะจง (มาจาก ValueError)
Johan Lundberg

9

ดูเหมือนว่าคำตอบทั้งหมดกำลังเพิ่มข้อมูลไปยัง e.args [0] ดังนั้นการแก้ไขข้อความแสดงข้อผิดพลาดที่มีอยู่ มีข้อเสียคือการขยาย tuple args แทน? ฉันคิดว่าสิ่งที่เป็นไปได้คือคุณสามารถทิ้งข้อความแสดงข้อผิดพลาดเดิมไว้สำหรับกรณีที่ต้องการแยกสตริงนั้น และคุณสามารถเพิ่มองค์ประกอบหลายรายการลงใน tuple หากการจัดการข้อผิดพลาดที่กำหนดเองของคุณสร้างข้อความหรือรหัสข้อผิดพลาดหลายกรณีสำหรับกรณีที่การสืบค้นกลับจะแยกวิเคราะห์ทางโปรแกรม (เช่นผ่านเครื่องมือตรวจสอบระบบ)

## Approach #1, if the exception may not be derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args = (e.args if e.args else tuple()) + ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

หรือ

## Approach #2, if the exception is always derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args += ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

คุณเห็นข้อเสียของวิธีนี้หรือไม่?


คำตอบเก่าของฉันไม่ได้เปลี่ยน e.args [0]
Johan Lundberg

4

เทมเพลตรหัสนี้จะช่วยให้คุณสามารถยกข้อยกเว้นด้วยข้อความที่กำหนดเอง

try:
     raise ValueError
except ValueError as err:
    raise type(err)("my message")

3
สิ่งนี้ไม่เก็บสแต็กการติดตาม
plok

ผู้ถามไม่ได้ระบุว่าการติดตามสแต็กจะถูกเก็บรักษาไว้
shrewmouse

4

เพิ่มข้อยกเว้นใหม่ด้วยข้อความแสดงข้อผิดพลาดของคุณโดยใช้

raise Exception('your error message')

หรือ

raise ValueError('your error message')

ภายในสถานที่ที่คุณต้องการยกระดับหรือแนบข้อความแสดงข้อผิดพลาด (แทนที่) เป็นข้อยกเว้นปัจจุบันโดยใช้ 'จาก' (รองรับ Python 3.x เท่านั้น):

except ValueError as e:
  raise ValueError('your message') from e

Thanx, @gberger, วิธีการ 'จาก e' ไม่รองรับโดย python 2.x
Alexey Antonenko

3

นี่คือฟังก์ชั่นที่ฉันใช้เพื่อปรับเปลี่ยนข้อความแสดงข้อยกเว้นใน Python 2.7 และ 3.x ในขณะที่ยังคงร่องรอยการสืบค้นกลับดั้งเดิม มันต้องการsix

def reraise_modify(caught_exc, append_msg, prepend=False):
    """Append message to exception while preserving attributes.

    Preserves exception class, and exception traceback.

    Note:
        This function needs to be called inside an except because
        `sys.exc_info()` requires the exception context.

    Args:
        caught_exc(Exception): The caught exception object
        append_msg(str): The message to append to the caught exception
        prepend(bool): If True prepend the message to args instead of appending

    Returns:
        None

    Side Effects:
        Re-raises the exception with the preserved data / trace but
        modified message
    """
    ExceptClass = type(caught_exc)
    # Keep old traceback
    traceback = sys.exc_info()[2]
    if not caught_exc.args:
        # If no args, create our own tuple
        arg_list = [append_msg]
    else:
        # Take the last arg
        # If it is a string
        # append your message.
        # Otherwise append it to the
        # arg list(Not as pretty)
        arg_list = list(caught_exc.args[:-1])
        last_arg = caught_exc.args[-1]
        if isinstance(last_arg, str):
            if prepend:
                arg_list.append(append_msg + last_arg)
            else:
                arg_list.append(last_arg + append_msg)
        else:
            arg_list += [last_arg, append_msg]
    caught_exc.args = tuple(arg_list)
    six.reraise(ExceptClass,
                caught_exc,
                traceback)

3

Python 3 มีข้อยกเว้นในตัวstrerror:

except ValueError as err:
  err.strerror = "New error message"
  raise err

ดูเหมือนจะใช้งานไม่ได้ คุณหายไปไหม
MasayoMusic

2

คำตอบปัจจุบันใช้งานไม่ได้ผลสำหรับฉันหากข้อยกเว้นไม่ถูกตรวจจับซ้ำข้อความที่ต่อท้ายจะไม่ปรากฏ

แต่การทำเช่นนี้ทั้งคู่จะติดตามและแสดงข้อความต่อท้ายโดยไม่คำนึงว่าข้อยกเว้นนั้นถูกดักจับใหม่หรือไม่

try:
  raise ValueError("Original message")
except ValueError as err:
  t, v, tb = sys.exc_info()
  raise t, ValueError(err.message + " Appended Info"), tb

(ฉันใช้ Python 2.7 ไม่ได้ลองใน Python 3)


1

ไม่มีวิธีแก้ไขปัญหาข้างต้นทำสิ่งที่ฉันต้องการอย่างแน่นอนซึ่งก็คือการเพิ่มข้อมูลลงในส่วนแรกของข้อความแสดงข้อผิดพลาดนั่นคือฉันต้องการให้ผู้ใช้ของฉันเห็นข้อความที่กำหนดเองของฉันก่อน

สิ่งนี้ใช้ได้กับฉัน:

exception_raised = False
try:
    do_something_that_might_raise_an_exception()
except ValueError as e:
    message = str(e)
    exception_raised = True

if exception_raised:
    message_to_prepend = "Custom text"
    raise ValueError(message_to_prepend + message)

0

ใช้งานได้กับ Python 3เท่านั้น คุณสามารถแก้ไขข้อโต้แย้งเดิมของข้อยกเว้นและเพิ่มข้อโต้แย้งของคุณเอง

ข้อยกเว้นจะจดจำ args ที่สร้างขึ้นด้วย ฉันคิดว่านี่คือเพื่อให้คุณสามารถแก้ไขข้อยกเว้น

ในฟังก์ชั่นreraiseเราจะเพิ่มอาร์กิวเมนต์ดั้งเดิมของข้อยกเว้นด้วยอาร์กิวเมนต์ใหม่ใด ๆ ที่เราต้องการ (เช่นข้อความ) ในที่สุดเราก็เพิ่มข้อยกเว้นอีกครั้งขณะที่รักษาประวัติย้อนกลับ

def reraise(e, *args):
  '''re-raise an exception with extra arguments
  :param e: The exception to reraise
  :param args: Extra args to add to the exception
  '''

  # e.args is a tuple of arguments that the exception with instantiated with.
  #
  e.args = args + e.args

  # Recreate the expection and preserve the traceback info so thta we can see 
  # where this exception originated.
  #
  raise e.with_traceback(e.__traceback__)   


def bad():
  raise ValueError('bad')

def very():
  try:
    bad()
  except Exception as e:
    reraise(e, 'very')

def very_very():
  try:
    very()
  except Exception as e:
    reraise(e, 'very')

very_very()

เอาท์พุต

Traceback (most recent call last):
  File "main.py", line 35, in <module>
    very_very()
  File "main.py", line 30, in very_very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 28, in very_very
    very()
  File "main.py", line 24, in very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 22, in very
    bad()
  File "main.py", line 18, in bad
    raise ValueError('bad')
ValueError: ('very', 'very', 'bad')

-3

ถ้าคุณต้องการกำหนดชนิดของข้อผิดพลาดเองสิ่งที่คุณทำได้คือการกำหนดคลาสของข้อผิดพลาดตาม ValueError


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