python 方法注释模板_python中的模板方法模式

python 方法注释模板

Are you a Python developer who finds themselves copy-pasting the same code over and over again and then changing it slightly to get a different function?

您是一名Python开发人员,发现自己一次又一次地复制粘贴相同的代码,然后对其进行轻微更改以获取不同的功能吗?

In his seminal book, “Clean Code,” Bob Martin argues that code duplication is very unhealthy for any application, and a developer should strive to avoid it all costs. The pattern I’m about to introduce to you is one of the ways this can be done when faced with multiple method implementations differing only in the details.

鲍勃·马丁( Bob Martin )在他的开创性著作《干净的代码》中指出,代码重复对于任何应用程序都是非常不健康的,因此开发人员应努力避免所有成本。 我要介绍给您的模式是面对多种仅在细节上有所不同的方法实现时可以完成的方法之一。

Although this technique is well-known in the object-oriented community, the OO nature of languages like Java requires a nontrivial boilerplate-code overhead in the form of functional interfaces and lambda expressions to make the pattern work. This often discourages the developer from putting the pattern in use, even when underlying conditions call for it.

尽管此技术在面向对象社区中是众所周知的,但是像Java这样的语言的OO性质要求以功能接口和lambda表达式的形式进行大量的样板代码开销,以使模式起作用。 即使潜在的条件要求,这也常常使开发人员不愿使用该模式。

On the other hand, Python’s native support for the functional programming paradigm makes implementation of the template method pattern very smooth and, consequently, crushes all possible excuses for not employing it when one has the chance.

另一方面,Python对函数式编程范例的本机支持使 模板方法的模式非常平滑,因此粉碎了所有可能的借口,使他们有机会不使用它。

您将从本文中受益吗? (How Will You Benefit From This Article?)

After finishing this article, you’ll know when and how to implement the template method pattern in Python and how to write cleaner and more maintainable code for your Python-based applications.

在完成本文之后,您将知道何时以及如何在Python中实现模板方法模式,以及如何为基于Python的应用程序编写更简洁,更可维护的代码。

何时使用模板方法模式 (When to Use the Template Method Pattern)

The technique is well-suited for situations when one has several functions, each of which has a small custom part (i.e., code specific to that function) and a fairly sizeable common part (i.e., code shared by all the functions).

该技术非常适合以下情况:一个功能具有多个功能,每个功能都有一个小的自定义部分(即,该功能特定的代码)和一个相当大的公共部分(即,所有功能共享的代码)。

For example, imagine that you’re building a machine learning application that pulls data from DB and then calculates predictions from different models that had already been trained. In this case, each function will represent a different model, something like predict_logistic_regression(), predict_knn(), and predict_svm().

例如,假设您正在构建一个机器学习应用程序,该应用程序从DB中提取数据,然后根据已经训练的不同模型计算预测。 在这种情况下,每个函数将代表一个不同的模型,例如predict_logistic_regression()predict_knn()predict_svm()

The common part could be pulling data from that DB, cleaning it, constructing a feature set from it, and, once predictions are made, storing those back to DB. The custom part would be the actual result-predicting code — something like this:

常见的部分可能是从该数据库中提取数据,清理数据,从中构造功能集,一旦做出预测,将其存储回数据库中。 定制部分将是实际的结果预测代码,如下所示:

y = model.predict(X)

y = model.predict(X)

model here would vary from one method to another. This pattern is especially powerful when the number of functions is high (3, 4, 5 …), the common part significant, and the custom part tiny.

model 这里 从一种方法到另一种方法都会有所不同。 当功能数量很多(3、4、5…),公共部分很重要且自定义部分很小时,此模式特别强大。

一个简单的例子 (A Simple Example)

Following the best practices of the programming community, let’s start with a “Hello, World!” use case.

遵循编程社区的最佳实践,让我们从“ Hello,World!”开始。 用例。

之前 (Before)

Keeping things simple, we have two methods, each of which has a two-line common part and a one-line custom part.

为简单起见,我们有两种方法,每种方法都有两行公共部分和一行自定义部分。

def action_a():
    # For example pulling data from DB and preparing model features...
    print('Setting up...')


    # For example predicting via KNN
    print('Running action A')


    # For example closing connection to DB  calculating model predictions
    print('Tearing down...')


    return 'A'




def action_b():
    # For example pulling data from DB and preparing model features...
    print('Setting up...')


    # For example predicting via Logistics Regression
    print('Running action B')


    # For example closing connection to DB  calculating model predictions
    print('Tearing down...')


    return 'B'




# Example usage
print()
print(f'Result of action_a = {action_a()}')


print()
print(f'Result of action_b = {action_b()}')

Running the code will yield:

运行代码将产生:

Image for post
Before-script output
脚本前输出

After successfully identifying the nontrivial common part of both functions, let’s look at how we can extract this shared logic into a template method to reduce the code duplication throughout our program while preserving the original functionality.

成功识别两个函数的共同部分后,让我们看看如何将这种共享逻辑提取到模板方法中,以减少整个程序中的代码重复,同时又保留原始功能。

(After)

This is where Python’s support for functional programming comes into play. The cool thing is that in Python, functions can be passed around as parameters just like any other ole object, without any extra boilerplate overhead.

这就是Python对函数式编程的支持发挥作用的地方。 很棒的事情是,在Python中,函数可以像其他任何ole对象一样作为参数传递,而没有任何额外的模板开销。

Hence, our template method will just be the common logic — in the middle of which, we run the custom part that has been passed down as a parameter.

因此,我们的模板方法将只是常见的逻辑-在其中,我们运行已作为参数传递的自定义部件。

def template_method(action):
    # For example pulling data from DB and preparing model features...
    print('Setting up...')


    # Running custom part
    result = action()


    # For example closing connection to DB  calculating model predictions
    print('Tearing down...')


    # Returning custom result after all boilerplate has been executed
    return result




def action_a():
    # For example predicting via KNN
    print('Running action A')
    return 'A'




def action_b():
    # For example predicting via Logistics Regression
    print('Running action B')
    return 'B'




# Example usage
print()
print(f'Result of action_a = {template_method(action_a)}')


print()
print(f'Result of action_b = {template_method(action_b)}')

Running the code verifies that the result is the same as before:

运行代码可验证结果是否与以前相同:

Image for post
Before-script output
脚本前输出

Look at how much simpler the implementations of action_a and action_b are. If you intend to add more actions in the future, you’ll be able to focus on their custom part alone without worrying about, for example, closing a DB connection.

看的更简单的实现方式如何action_aaction_b是。 如果您打算将来添加更多操作,则可以仅关注它们的自定义部分,而不必担心例如关闭数据库连接。

This, it turns out, is a huge benefit, especially when building large projects. Actually, there are many other benefits, so let’s take a closer look at them.

事实证明,这是一个巨大的好处,尤其是在构建大型项目时。 实际上,还有许多其他好处,因此让我们仔细看一下。

为什么要使用模板方法模式 (Why to Use the Template Method Pattern)

For the purpose of our analysis, let’s assume that your program requires N different functions, each of which has a custom part of X lines and a common part of Y lines of code.

为了进行分析,我们假设您的程序需要N个不同的函数,每个函数都有X行的自定义部分和Y行代码的公用部分。

By implementing the pattern, in addition to you feeling like a pro, your application will also gain from these three tangible benefits:

通过实施该模式,除了让您感觉像专家一样,您的应用程序还将受益于以下三个明显的好处:

减少编写代码 (Less code to write)

Let’s say you have five functions, all of which have a common part of ten lines and a custom part of ten lines. How many lines will your program have?

假设您有五个函数,所有这些函数都有一个共同的部分,即十行,而一个自定义部分是十行。 您的程序有几行?

N * (X + Y) = 5 * (10 + 10) = 100

N * (X + Y) = 5 * (10 + 10) = 100

On the other hand, if you extract the common part into a template method, then your program will have:

另一方面,如果将公共部分提取到模板方法中,则程序将具有:

N * X + Y = 5*10 + 10 = 60

N * X + Y = 5*10 + 10 = 60

Now that’s a significant improvement. The amount of saved lines becomes even more evident when the number of method implementations (N) grows. This, from my experience, happens often.

现在,这是一个重大改进。 随着方法实现次数(N)的增加,节省的行数变得更加明显。 根据我的经验,这种情况经常发生。

维护代码更少 (Less code to maintain)

In software engineering, requirements keep changing. Imagine a new business-feature request forcing you to change five lines of the common implementation part. Assuming the same setup as in the paragraph above, if this happens before you migrated to the template method implementation, you’d have to change 25 lines of code — five lines at five different places.

在软件工程中,需求不断变化。 想象一个新的业务功能请求,迫使您更改公共实施部分的五行。 假设与上面段落中的设置相同,如果在迁移到模板方法实现之前发生这种情况,则必须更改25行代码-在五个不同的地方更改五行。

With a template method, you change the code once — i.e., you touch only five lines of code in one place. This is very important because every time you’re touching something that works, you’re risking that it’ll stop working. Generally, when deploying quick fixes and new features, you want to minimize the points of intervention.

使用模板方法,您只需更改一次代码,即,您只需要触摸五行代码即可。 这非常重要,因为每当您触摸有效的东西时,就有冒着停止作用的危险。 通常,在部署快速修复程序和新功能时,您希望最大程度地减少干预点。

Also, keep in mind that when you touch N different methods, in the ideal world, you’d have also added, for each of the N methods, proper unit-test coverage. Not only is this annoying, but you create even more duplicated code (Uncle Bob wouldn’t be happy!). With a template method, you only write unit tests for the common part once.

另外,请记住,当您触摸N种不同的方法时,在理想情况下,您还应该为N种方法中的每一种添加适当的单元测试范围。 这不仅令人讨厌,而且您还会创建更多重复的代码( Bob叔叔会很高兴!)。 使用模板方法,您只需为公共部分编写一次单元测试。

需要记住的代码更少 (Less code to remember)

For example, let’s assume each of the N methods needs to close a connection the the database once it finishes its computation. This means, you’ll need to include the connection.close() line at the end of each implementation.

例如,假设N个方法中的每一个都需要在数据库完成计算后关闭数据库的连接。 这意味着,您需要在每个实现的末尾包括connection.close()行。

If you’ve ever written a sizeable software project, you know how easy it is to forget to include such a line. And you also know that innocent omissions like this can cause ghostlike bugs that’ll creep out on you when you least expect them and force you to spend long hours finding and fixing the problems later in the future.

如果您曾经编写过一个大型软件项目,您就会知道忘记包含这样的行是多么容易。 而且您还知道,这样的无辜遗漏会导致类似幽灵般的错误,当您最不希望它们时,这些错误会逐渐蔓延到您身上,并迫使您花费大量时间来寻找并解决以后的问题。

In the template method pattern, the template method is used to encapsulate the boilerplate from the custom logic, and, consequently, it frees you up from thinking about the boring common part. You write connection.close() just once at the end of your template method, and you can forget about the line altogether and focus on the custom implementations, which is where most of the value resides anyway.

在模板方法模式中,模板方法用于从自定义逻辑中封装样板,因此,它使您无需考虑无聊的公共部分。 您只需在模板方法的末尾编写一次connection.close() ,您就可以完全忽略这一行,而将注意力集中在自定义实现上,这是大多数值所驻留的地方。

一个真实的例子 (A Real-World Example)

To showcase the benefits of the pattern in practice, let’s look at a class I’ve recently added to my trading-engine project. The purpose of the class is to encapsulate calls to an external trading API for actions like placing and cancelling orders.

为了展示这种模式在实践中的好处,让我们看一下我最近在我的交易引擎项目中添加的一类。 该类的目的是封装对外部交易API的调用,以执行诸如下达和取消订单之类的操作。

A nontrivial common part shared by each method has been identified. Then, this complex logic consisting of logging in a console, storing logs into a DB, and handling retries in case of specific server responses has been extracted to a __run_with_log(api_name, api_call) template method.

已经确定了每种方法共有的重要部分。 然后,这种复杂的逻辑包括提取到__run_with_log(api_name, api_call)模板方法中,该逻辑包括登录控制台,将日志存储到DB中以及在特定服务器响应的情况下处理重试。

Then, in each of the API wrapper functions, one just focuses on preparing the specific requests. The actual calls are delegated to the template method, which makes sure to have the boring, yet crucial, boilerplate code executed.

然后,在每个API包装函数中,仅着重于准备特定的请求。 实际的调用被委托给template方法,该方法确保执行无聊但至关重要的样板代码。

# Imports etc...


class BitmexAPI:


# Contructor etc...


    def place_order(self, order: Order):
        def place_order_api_call():
            size_sign = 1 if order.order_side == OrderSide.BUY else -1


            result = self.__bitmex_client.Order.Order_new(symbol='XBTUSD',
                                                          orderQty=size_sign * order.size,
                                                          price=order.price,
                                                          clOrdID=order.reference_key,
                                                          ordType='Limit').result()


            status_code = str(result[1])
            response_body = to_json(result[0])
            result_object = result[0]


            return status_code, response_body, result_object


        return self.__run_with_log(api_name='PLACE_ORDER_{}'.format(order.reference_key), api_call=place_order_api_call)


    def add_stop_loss(self, order: Order):
        def add_stop_loss_api_call():
            result = self.__bitmex_client.Order.Order_new(symbol='XBTUSD',
                                                          side=order.order_side.value,
                                                          clOrdID=order.reference_key,
                                                          ordType='Stop',
                                                          stopPx=order.price,
                                                          execInst='LastPrice,Close').result()


            status_code = str(result[1])
            response_body = to_json(result[0])
            result_object = result[0]


            return status_code, response_body, result_object


        return self.__run_with_log(api_name='ADD_STOP_LOSS_{}'.format(order.reference_key),
                                   api_call=add_stop_loss_api_call)


    def cancel_order_by_ref_key(self, ref_key: str):
        def cancel_order_api_call():
            result = self.__bitmex_client.Order.Order_cancel(clOrdID=ref_key).result()


            status_code = str(result[1])
            response_body = to_json(result[0][0])
            result_object = result[0][0]


            return status_code, response_body, result_object


        return self.__run_with_log(api_name='CANCEL_ORDER_{}'.format(ref_key), api_call=cancel_order_api_call)




    def __run_with_log(self, api_name, api_call, retry_count=0):
        time_of_call = datetime.now()
        status_code = ''
        error_message = ''
        concatenated_response_body = ''
        full_response_body = ''


        try:
            (status_code, response_message, result_object) = api_call()


            status_code = status_code
            full_response_body = response_message
            concatenated_response_body = response_message[:LOG_MESSAGE_LENGTH]


            return result_object


        except (HTTPTooManyRequests, HTTPServiceUnavailable) as e:
            status_code = str(e.response)
            error_message = str(e)[:LOG_MESSAGE_LENGTH]
            print('STATUS CODE: ', status_code)
            print('ERROR: ', error_message)
            if retry_count <= MAX_RETRY_COUNT:
                print('retry_count ({}) <= max_retry_count({})'.format(retry_count, MAX_RETRY_COUNT))
                print('-> Sleeping for {} seconds and retrying'.format(DURATION_BETWEEN_RETRIES))
                time.sleep(DURATION_BETWEEN_RETRIES)
                return self.__run_with_log(api_name=api_name, api_call=api_call, retry_count=retry_count + 1)
            else:
                print('retry_count ({}) > max_retry_count({})'.format(retry_count, MAX_RETRY_COUNT))
                print('-> Throwing an error')
                status_code = str(e.response)
                error_message = str(e)[:LOG_MESSAGE_LENGTH]


                raise e




        except HTTPError as e:
            status_code = str(e.response)
            error_message = str(e)[:LOG_MESSAGE_LENGTH]


            raise e
        finally:
            print('-------------------------------------API CALL BEGINNING----------------------------')
            print('API Name = ', api_name)
            print('Response Code = ', status_code)
            print('Api Executed at = ', time_of_call)


            if self.__with_verbose_logging:
                print()
                print('Full Response Body = ', full_response_body)
                print()
                print('Error Message = ', error_message)
            print('-------------------------------------API CALL END---------------------------------')


            self.__log_service.log_api_call(vendor=self.__api_vendor_name,
                                            api_name=api_name,
                                            status_code=status_code,
                                            error_message=error_message,
                                            response_body=concatenated_response_body,
                                            time_of_call=time_of_call,
                                            caller=self.__caller)

Take note of how fairly verbose the template method __run_with_log(api_name, api_call) is. How annoying would it be to have to copy and paste it every time I add a new API call? I hope this makes a persuasive case for the usefulness of the pattern.

注意模板方法__run_with_log(api_name, api_call)程度。 每次添加新的API调用时都必须复制并粘贴它,这有多烦人? 我希望这能为该模式的有用性提供说服力。

结论 (Conclusion)

Congratulations! By now you’re ready to make your Python code cleaner with the template method pattern. If you’re excited to put your new knowledge to practice, here are some example use cases for you to explore:

恭喜你! 到目前为止,您已经准备好使用模板方法模式来使Python代码更简洁。 如果您很高兴将新知识应用到实践中,请参考以下示例用例:

  • Unit testing: Test environment initialization and mock instantiation go into the template method

    单元测试:测试环境初始化和模拟实例化进入模板方法

  • Database access: Initialization and the closing of the DB connection go into the template

    数据库访问:初始化和关闭数据库连接进入模板

  • Verbose logging: Before and after log statements go into the template

    详细日志记录:日志语句之前和之后进入模板

Thank you for reading my article. Happy hacking!

感谢您阅读我的文章。 骇客骇客!

资源资源 (Resources)

翻译自: https://medium.com/better-programming/the-template-method-pattern-in-python-72b7d6e95c96

python 方法注释模板

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值