在一个大型项目的示例上打字的历史

大家好! 今天,我将以Ostrovok.ru中的一个项目为例,告诉您打字发展的故事。



这个故事早在关于python3.5的大肆宣传之前就已经开始,而且它始于python2.7编写的项目中。

2013年 :刚刚发布了python3.3 ,由于没有添加任何特定功能,因此没有必要迁移到新版本,并且在过渡期间会遇到很多痛苦。

我参与了Ostrovok.ru的“合作伙伴”项目-该服务负责与合作伙伴集成,预订,统计数据和个人帐户有关的一切。 我们将内部API用于公司的其他微服务,并将外部API用于我们的合作伙伴。

在某个时候,团队形成了以下方法来编写HTTP处理程序或某种业务逻辑:

1)输入和输出数据必须由结构(类)描述,
2)必须根据说明验证结构实例的内容,
3)在输入处采用结构并在输出处给出结构的函数应分别检查输入和输出处的数据类型。

我不会详细介绍每一个点,下面的示例应该足以理解所面临的问题。

例子

import datetime as dt from contracts import new_contract, contract from schematics.models import Model from schematics.types import IntType, DateType # in class OrderInfoData(Model): order_id = IntType(required=True) # out class OrderInfoResult(Model): order_id = IntType(required=True) checkin_at = DateType(required=True) checkout_at = DateType(required=True) cancelled_at = DateType(required=False) @new_contract def pyOrderInfoData(x): return isinstance(x, OrderInfoData) @new_contract def pyOrderInfoResult(x): return isinstance(x, OrderInfoResult) @contract def get_order_info(data_in): """ :type data_in: pyOrderInfoData :rtype: pyOrderInfoResult """ return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) ) if __name__ == '__main__': data_in = OrderInfoData(dict(order_id=777)) data_out = get_order_info(data_in) print(data_out.to_native()) 


该示例使用以下库: 示意图pycontracts

* 示意图 -一种描述和验证数据的方法。
* pycontracts-一种在运行时检查函数输入/输出的方法。

这种方法使您可以:

  • 编写测试更加容易-不会出现验证问题,仅涵盖业务逻辑。
  • 为了保证API中响应的格式和质量-针对我们准备接受和提供的内容,出现了一个严格的框架。
  • 如果响应格式是具有不同嵌套级别的复杂结构,则更容易理解/重构响应格式。

重要的是要了解类型检查(非验证)仅在运行时有效 ,这对于本地开发,在CI中运行测试以及验证候选发行版是否在过渡环境中工作非常方便。 在生产环境中,必须禁用此功能,否则服务器将变慢。

多年过去了,我们的项目不断壮大,出现了更多新的和复杂的业务逻辑,API句柄的数量至少没有减少。

在某个时候,我开始注意到该项目的启动已经花费了明显的几秒钟-这很烦人,因为每次我编辑代码并运行测试时,我都必须坐很长时间。 当等待时间开始8到10秒钟时,我们最终决定弄清实际情况。

实际上,一切都非常简单。 启动项目时,pycontracts库会解析@contract涵盖的所有文档字符串 ,以便在内存中注册所有结构,然后正确检查它们。 当项目中的结构数量达到数千时,整个过程开始变慢。

怎么办呢? 正确的答案是寻找其他解决方案,所幸在院子中已经是2018年python3.5 - python3.6 ),并且我们已经将项目迁移到了python3.6

我开始研究替代解决方案,并思考如何将项目从“ pycontracts + docstring中的类型描述”迁移到“ something +类型化注释中的类型描述”。 原来,如果将pycontracts升级到最新版本,则可以用键入注释样式来描述类型,例如,它看起来可能像这样:

 @contract def get_order_info(data_in: OrderInfoData) -> OrderInfoResult: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) ) 

如果您需要使用来自键入的结构(例如OptionalUnion)则会出现问题,因为pycontracts不知道如何使用它们:

 from typing import Optional @contract def get_order_info(data_in: OrderInfoData) -> Optional[OrderInfoResult]: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) ) 

我开始寻找用于运行时类型检查的替代库:

* 强制执行
* 打字卫士
* pytypes

当时的Enforce不支持python3.7 ,但是我们已经更新了, pytypes不喜欢语法,因此,选择权归于typeguard

 from typeguard import typechecked @typechecked def get_order_info(data_in: OrderInfoData) -> Optional[OrderInfoResult]: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) ) 

以下是真实项目的示例:

 @typechecked def view( request: HttpRequest, data_in: AffDeeplinkSerpIn, profile: Profile, contract: Contract, ) -> AffDeeplinkSerpOut: ... @typechecked def create_contract( user: Union[User, AnonymousUser], user_uid: Optional[str], params: RegistrationCreateSchemaIn, account_manager: Manager, support_manager: Manager, sales_manager: Optional[Manager], legal_entity: LegalEntity, partner: Partner, ) -> tuple: ... @typechecked def get_metaorder_ids_from_ordergroup_orders( orders: Tuple[OrderGroupOrdersIn, ...], contract: Contract ) -> list: ... 

结果,经过漫长的重构过程,我们能够将项目完全转移到typeguard + 类型注释

我们取得了什么结果:

  • 该项目将在2-3秒内开始,这至少并不令人讨厌。
  • 代码的可读性得到了改善。
  • 由于没有更多的通过@new_contract进行的结构注册,因此该项目的行数和文件数均已减小
  • 像PyCharm这样的智能ID在索引项目和执行不同提示方面变得更好,因为现在它不是注释,而是诚实的导入。
  • 您可以使用mypypyre-check之类的静态分析器,因为它们支持键入注释
  • 整个python社区正朝着以一种或另一种形式键入内容迈进,也就是说,当前的行动是对项目未来的投资。
  • 有时,周期性进口存在问题,但数量很少,可以忽略不计。

希望本文对您有所帮助!

参考文献:
* 强制执行
* 打字卫士
* pytypes
* pycontracts
* 原理图

Source: https://habr.com/ru/post/zh-CN443470/


All Articles