How to improve code clarity in nested try-except-else clauses?

后端 未结 3 751
庸人自扰
庸人自扰 2021-01-20 05:19

Sometimes I have a cascade of different things I can try to accomplish a task, e. g. If I need to get a record I can first try to find the record, and if this fails, I can c

相关标签:
3条回答
  • 2021-01-20 05:29

    You could break that into multiple functions ?

    def handle_missing():
        try:
            record = create_record()
        except CreateFailed:
            record = tape
            logger.info("Using a tape now")
        else:
            logger.info("Created a new record")
        return record
    
    
    def get_record():
        try:
            record = find_record()
        except NoSuchRecord:
            record = handle_missing()
        else:
            logger.info("Record found")
        return record
    

    And then you'd use it like,

    record = get_record()
    
    0 讨论(0)
  • 2021-01-20 05:51

    You can use a for loop to successively try variants:

    for task, error in ((find_record, NoSuchRecord), (create_record, CreateFailed)):
        try:
            result = task()
        except error:
            continue
        else:
            break
    else:
        # for..else is only entered if there was no break
        result = tape
    

    If you need an else clause, you can provide it as a separate function:

    for task, error, success in (
        (find_record, NoSuchRecord, lambda: logger.info("Record found")),
        (create_record, CreateFailed, lambda: logger.info("Created a new record"))
    ):
        try:
            result = task()
        except error:
            continue
        else:
            success()
            break
    else:
        result = tape
        logger.info("Using a tape now")
    

    Take note that the default case tape is not part of the variants - this is because it has no failure condition. If it should execute with the variants, it can be added as (lambda: tape, (), lambda: None).


    You can put this all into a function for reuse:

    def try_all(*cases):
        for task, error, success in cases:
            try:
                result = task()
            except error:
                continue
            else:
                success()
                return result
    
    try_all(
        (find_record, NoSuchRecord, lambda: logger.info("Record found")),
        (create_record, CreateFailed, lambda: logger.info("Created a new record")),
        (lambda: tape, (), lambda: logger.info("Using a tape now")),
    )
    

    In case the tuples seem difficult to read, a NamedTuple can be used to name the elements. This can be mixed with plain tuples:

    from typing import NamedTuple, Callable, Union, Tuple
    from functools import partial
    
    class Case(NamedTuple):
        task: Callable
        error: Union[BaseException, Tuple[BaseException, ...]]
        success: Callable
    
    
    try_all(
        Case(
            task=find_record,
            error=NoSuchRecord,
            success=partial(logger.info, "Record found")),
        (
            create_record, CreateFailed,
            partial(logger.info, "Created a new record")),
        Case(
            task=lambda: tape,
            error=(),
            success=partial(logger.info, "Using a tape now")),
    )
    
    0 讨论(0)
  • 2021-01-20 05:52

    I think following code is more readable and clean. Also I am sure in real problem we need some parameters to be sent to "find_record" and "create_record" functions like id, some, values to create new record. The factory solution need those parameters also be listed in tuple

    def try_create(else_return):
        try:
            record = create_record()
        except CreateFailed:
            record = else_return
            logger.info("Using a tape now")
        else:
            logger.info("Created a new record")
    
    def try_find(else_call= try_create, **kwargs):
        try:
            record = find_record()
        except NoSuchRecord:
            try_create(**kwargs)
        else:
            logger.info("Record found") 
    
    
    
    try_find(else_call=try_create, else_return=tape)
    
    0 讨论(0)
提交回复
热议问题