久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

使用 Mypy 檢查 30 萬行 Python 代碼,總結(jié)出 3 大痛點與 6 個技巧,!

 龍書意 2022-10-30 發(fā)布于廣東

在 Spring ,,我們維護了一個大型的 Python 單體代碼庫(英:monorepo),用上了 Mypy 最嚴(yán)格的配置項,,實現(xiàn)了 Mypy 全覆蓋,。簡而言之,這意味著每個函數(shù)簽名都是帶注解的,,并且不允許有隱式的 Any 轉(zhuǎn)換,。

(譯注:此處的 Spring 并不是 Java 中那個著名的 Spring 框架,而是一家生物科技公司,,專注于找到與年齡相關(guān)的疾病的療法,,2022 年 3 月曾獲得比爾&梅琳達·蓋茨基金會 120 萬美元的資助。)

誠然,,代碼行數(shù)是一個糟糕的衡量標(biāo)準(zhǔn),,但可作一個粗略的估計:我們的代碼倉有超過 30 萬行 Python 代碼,其中大約一半構(gòu)成了核心的數(shù)據(jù)平臺,,另一半是由數(shù)據(jù)科學(xué)家和機器學(xué)習(xí)研究員編寫的終端用戶代碼,。

我有個大膽的猜測,就這個規(guī)模而言,,這是最全面的加了類型的 Python 代碼倉之一,。

我們在 2019 年 7 月首次引入了 Mypy,大約一年后實現(xiàn)了全面的類型覆蓋,,從此成為了快樂的 Mypy 用戶,。

幾周前,我跟 Leo Boytsov 和 Erik Bernhardsson 在 Twitter 上對 Python 類型有一次簡短的討論——然后我看到 Will McGugan 也對類型大加贊賞,。由于 Mypy 是我們在 Spring 公司發(fā)布和迭代 Python 代碼的關(guān)鍵部分,,我想寫一下我們在過去幾年中大規(guī)模使用它的經(jīng)驗。

一句話總結(jié):雖然采用 Mypy 是有代價的(前期和持續(xù)的投入,、學(xué)習(xí)曲線等),,但我發(fā)現(xiàn)它對于維護大型 Python 代碼庫有著不可估量的價值,。Mymy 可能不適合于所有人,但它十分適合我,。

# Mypy 是什么,?

(如果你很熟悉 Mypy,可跳過本節(jié),。)

Mypy 是 Python 的一個靜態(tài)類型檢查工具,。如果你寫過 Python 3,你可能會注意到 Python 支持類型注解,,像這樣:

def greeting(name: str) -> str:
    return 'Hello ' + name

Python 在 2014 年通過 PEP-484 定義了這種類型注解語法。雖然這些注解是語言的一部分,,但 Python(以及相關(guān)的第一方工具)實際上并不拿它們來強制做到類型安全,。

相反,類型檢查通過第三方工具來實現(xiàn),。Mypy 就是這樣的工具,。Facebook 的 Pyre 也是這樣的工具——但就我所知,Mypy 更受歡迎(Mypy 在 GitHub 上有兩倍多的星星,,它是 Pants 默認(rèn)使用的工具),。IntelliJ 也有自己的類型檢查工具,支持在 PyCharm 中實現(xiàn)類型推斷,。這些工具都聲稱自己“兼容 PEP-484”,,因為它們使用 Python 本身定義的類型注解。

(譯注:最著名的類型檢查工具還有谷歌的pytype 和微軟的pyright ,,關(guān)于基本情況介紹與對比,,可查閱這篇文章

換句話說:Python 認(rèn)為自己的責(zé)任是定義類型注解的語法和語義(盡管 PEP-484 本身很大程度上受到了 Mypy 現(xiàn)有版本的啟發(fā)),但有意讓第三方工具來檢查這些語義,。

請注意,,當(dāng)你使用像 Mypy 這樣的工具時,你是在 Python 本身之外運行它的——比如,,當(dāng)你運行mypy path/to/file.py 后,,Mypy 會把推斷出的違規(guī)代碼都吐出來。Python 在運行時顯露但不利用那些類型注解,。

(順便一提:在寫本文時,,我了解到相比于 Pypy 這樣的項目,Mypy 最初有著非常不同的目標(biāo),。那時還沒有 PEP-484(它的靈感來自 Mypy?。?Mypy 定義了自己的語法,,與 Python 不同,,并實現(xiàn)了自己的運行時(也就是說,,Mypy 代碼是通過 Mypy 執(zhí)行的)。當(dāng)時,,Mypy 的目標(biāo)之一是利用靜態(tài)類型,、不可變性等來提高性能——而且明確地避開了與 CPython 兼容。Mypy 在 2013 年切換到兼容 Python 的語法,,而 PEP-484 在 2015 年才推出,。(“使用靜態(tài)類型加速 Python”的概念催生了 Mypyc,它仍然是一個活躍的項目,,可用于編譯 Mypy 本身,。))

# 在 Spring 集成 Mypy

我們在 2019 年 7 月將 Mypy 引入代碼庫(#1724)。當(dāng)首次發(fā)起提議時,,我們有兩個主要的考慮:

  1. 雖然 Mypy 在 2012 年的 PyCon 芬蘭大會上首次亮相,,并在 2015 年初發(fā)布了兼容 PEP-484 的版本,但它仍然是一個相當(dāng)新的工具——至少對我們來說是這樣,。盡管我們在一些相當(dāng)大的 Python 代碼庫上工作過(在可汗學(xué)院和其它地方),,但團隊中沒有人使用過它。

  2. 像其它增量類型檢查工具一樣(例如 Flow),,隨著代碼庫的注解越來越多,,Mypy 的價值會與時俱增。由于 Mypy 可以并且將會用最少的注解捕獲 bug,,所以你在代碼庫上投入注解的時間越多,,它就會變得越有價值。

盡管有所猶豫,,我們還是決定給 Mypy 一個機會,。在公司內(nèi)部,我們有強烈偏好于靜態(tài)類型的工程師文化(除了 Python,,我們寫了很多 Rust 和 TypeScript),。所以,我們準(zhǔn)備使用 Mypy,。

我們首先類型化了一些文件,。一年后,我們完成了全部代碼的類型化(#2622),,并升級到最嚴(yán)格的 Mypy 設(shè)置(最關(guān)鍵的是 disallow_untyped_defs ,,它要求對所有函數(shù)簽名進行注解),從那時起,,我們一直維護著這些設(shè)置,。(Wolt 團隊有一篇很好的文章,他們稱之為“專業(yè)級的 Mypy 配置”,,巧合的是,,我們使用的正是這種配置,。)

Mypy 配置:https://blog./engineering/2021/09/30/professional-grade-mypy-configuration/

# 反饋

總體而言:我對 Mypy 持積極的看法。 作為核心基礎(chǔ)設(shè)施的開發(fā)人員(跨服務(wù)和跨團隊使用的公共庫),,我認(rèn)為它極其有用,。

我將在以后的任何 Python 項目中繼續(xù)使用它。

# 好處

Zulip 早在 2016 年寫了一篇漂亮的文章,,內(nèi)容關(guān)于使用 Mypy 的好處(這篇文章也被收入了 Mypy 官方文檔 中),。

Zulip 博文:https://blog./2016/10/13/static-types-in-python-oh-mypy/#benefitsofusingmypy

我不想重述靜態(tài)類型的所有好處(它很好),但我想簡要地強調(diào)他們在帖子中提到的幾個好處:

  1. 改善可讀性:有了類型注解,,代碼趨向于自描述(與文檔字符串不同,,這種描述的準(zhǔn)確性可以靜態(tài)地強制執(zhí)行)。(英:self-documenting)

  2. 捕獲錯誤:是真的,!Mypy 確實能找出 bug,。從始至終。

  3. 自信地重構(gòu):這是 Mypy 最有影響力的一個好處,。有了 Mypy 的廣泛覆蓋,,我可以自信地發(fā)布涉及數(shù)百甚至數(shù)千個文件的更改,。當(dāng)然,,這與上一條好處有關(guān)——我們用 Mypy 找出的大多數(shù) bug 都是在重構(gòu)時發(fā)現(xiàn)的。

第三點的價值怎么強調(diào)都不為過,。毫不夸張地說,,在 Mypy 的幫助下,我發(fā)布更改的速度快了十倍,,甚至快了一百倍,。

雖然這是完全主觀的,但在寫這篇文章時,,我意識到:我信任 Mypy,。雖然程度還不及,比如說 OCaml 編譯器,,但它完全改變了我維護 Python 代碼的關(guān)系,,我無法想象回到?jīng)]有注解的世界。

# 痛點

Zulip 的帖子同樣強調(diào)了他們在遷移 Mypy 時所經(jīng)歷的痛點(與靜態(tài)代碼分析工具的交互,,循環(huán)導(dǎo)入),。

坦率地說,我在 Mypy 上經(jīng)歷的痛點與 Zulip 文章中提到的不一樣,。我把它們分成三類:

  1. 外部庫缺乏類型注解

  2. Mypy 學(xué)習(xí)曲線

  3. 對抗類型系統(tǒng)

讓我們來逐一回顧一下:

 1. 外部庫缺乏類型注解

最重要的痛點是,,我們引入的大多數(shù)第三方 Python 庫要么是無類型的,要么不兼容 PEP-561,。在實踐中,,這意味著對這些外部庫的引用會被解析為不兼容,,這會大大削弱類型的覆蓋率。

每當(dāng)在環(huán)境里添加一個第三方庫時,,我們都會在mypy.ini 里添加一個許可條目,,它告訴 Mypy 要忽略那些模塊的類型注解(有類型或提供類型存根的庫,比較罕見):

[mypy-altair.*]
ignore_missing_imports = True

[mypy-apache_beam.*]
ignore_missing_imports = True

[mypy-bokeh.*]
ignore_missing_imports = True

...

由于有了這樣的安全出口,,即使是隨便寫的注解也不會生效,。例如,Mypy 允許這樣做:

import pandas as pd

def return_data_frame() -> pd.DataFrame:
    '''Mypy interprets pd.DataFrame as Any, so returning a str is fine!'''
    return 'Hello, world!'

除了第三方庫,,我們在 Python 標(biāo)準(zhǔn)庫上也遇到了一些不順,。例如,functools.lru_cache 盡管在 typeshed 里有類型注解,,但由于復(fù)雜的原因,,它不保留底層函數(shù)的簽名,所以任何用 @functools.lru_cache 裝飾的函數(shù)都會被移除所有類型注解,。

例如,,Mypy 允許這樣做:

import functools

@functools.lru_cache
def add_one(x: float) -> float:
    return x + 1

add_one('Hello, world!')

第三方庫的情況正在改善。例如,,NumPy 在 1.20 版本中開始提供類型,。Pandas 也有一系列公開的類型存根 ,但它們被標(biāo)記為不完整的,。(添加存根到這些庫是非常重要的,,這是一個巨大的成就!)另外值得一提的是,,我最近在 Twitter 上看到了 Wolt 的 Python 項目模板 ,,它也默認(rèn)包括類型。

所以,,類型正在變得不再罕見,。過去當(dāng)我們添加一個有類型注解的依賴時,我會感到驚訝,。有類型注解的庫還是少數(shù),,并未成為主流。

 2. Mypy 學(xué)習(xí)曲線

大多數(shù)加入 Spring 的人沒有使用過 Mypy(寫過 Python),,盡管他們基本知道并熟悉 Python 的類型注解語法,。

同樣地,在面試中,,候選人往往不熟悉typing 模塊,。我通常在跟候選人作廣泛的技術(shù)討論時,會展示一個使用了typing.Protocol 的代碼片段,,我不記得有任何候選人看到過這個特定的構(gòu)造——當(dāng)然,,這完全沒問題,!但這體現(xiàn)了 typing 在 Python 生態(tài)的流行程度。

所以,,當(dāng)我們招募團隊成員時,,Mypy 往往是他們必須學(xué)習(xí)的新東西。雖然類型注解語法的基礎(chǔ)很簡單,,但我們經(jīng)常聽到這樣的問題:“為什么 Mypy 會這樣,?”、“為什么 Mypy 在這里報錯,?”等等,。

例如,這是一個通常需要解釋的例子:

if condition:
    value: str = 'Hello, world'
else:
  # Not ok -- we declared `value` as `str`, and this is `None`!
  value = None

...

if condition:
    value: str = 'Hello, world'
else:
  # Not ok -- we already declared the type of `value`.
  value: Optional[str] = None

...

# This is ok!
if condition:
    value: Optional[str] = 'Hello, world'
else:
  value = None

另外,,還有一個容易混淆的例子:

from typing import Literal

def my_func(value: Literal['a''b']) -> None:
  ...

for value in ('a''b'):
    # Not ok -- `value` is `str`, not `Literal['a', 'b']`.
  my_func(value)

當(dāng)解釋之后,,這些例子的“原因”是有道理的,但我不可否認(rèn)的是,,團隊成員需要耗費時間去熟悉 Mypy,。有趣的是,我們團隊中有人說 PyCharm 的類型輔助感覺還不如在同一個 IDE 中使用 TypeScript 得到的有用和完整(即使有足夠的靜態(tài)類型),。不幸的是,,這只是使用 Mypy 的代價。

除了學(xué)習(xí)曲線之外,,還有持續(xù)地注解函數(shù)和變量的開銷,。我曾建議對某些“種類”的代碼(如探索性數(shù)據(jù)分析)放寬我們的 Mypy 規(guī)則——然而,,團隊的感覺是注解是值得的,,這件事很酷。

 3. 對抗類型系統(tǒng)

在編寫代碼時,,我會盡量避免幾件事,,以免導(dǎo)致自己與類型系統(tǒng)作斗爭:寫出我知道可行的代碼,并強迫 Mypy 接受,。

首先是@overload ,,來自typing 模塊:非常強大,但很難正確使用,。當(dāng)然,,如果需要重載一個方法,我就會使用它——但是,,就像我說的,,如果可以的話,我寧可避免它,。

基本原理很簡單:

@overload
def clean(s: str) -> str:
    ...

@overload
def clean(s: None) -> None:
    ...

def clean(s: Optional[str]) -> Optional[str]:
    if s:
        return s.strip().replace('\u00a0'' ')
    else:
        return None

但通常,,我們想要做一些事情,,比如“基于布爾值返回不同的類型,帶有默認(rèn)值”,,這需要這樣的技巧:

@overload
def lookup(
    paths: Iterable[str], *, strict: Literal[False]
)
 -> Mapping[str, Optional[str]]:

    ...


@overload
def lookup(
    paths: Iterable[str], *, strict: Literal[True]
)
 -> Mapping[str, str]:

    ...


@overload
def lookup(
    paths: Iterable[str]
)
 -> Mapping[str, Optional[str]]:

    ...


def lookup(
    paths: Iterable[str], *, strict: Literal[True, False] = False
)
 -> Any:

    pass

即使這是一個 hack——你不能傳一個boolfind_many_latest,,你必須傳一個字面量 TrueFalse

同樣地,,我也遇到過其它問題,,使用 @typing.overload 或者@overload 、在類方法中使用@overload ,,等等,。

其次是TypedDict ,同樣來自typing 模塊:可能很有用,,但往往會產(chǎn)生笨拙的代碼,。

例如,你不能解構(gòu)一個TypedDict ——它必須用字面量 key 構(gòu)造——所以下方第二種寫法是行不通的:

from typing import TypedDict

class Point(TypedDict):
    x: float
    y: float

a: Point = {'x'1'y'2}

# error: Expected TypedDict key to be string literal
b: Point = {**a, 'y'3}

在實踐中,,很難用TypedDict對象做一些 Pythonic 的事情,。我最終傾向于使用 dataclasstyping.NamedTuple 對象。

第三是裝飾器,。Mypy 的 文檔 對保留簽名的裝飾器和裝飾器工廠有一個規(guī)范的建議,。它很先進,但確實有效:

F = TypeVar('F', bound=Callable[..., Any])

def decorator(func: F) -> F:
    def wrapper(*args: Any, **kwargs: Any):
        return func(*args, **kwargs)

    return cast(F, wrapper)

@decorator
def f(a: int) -> str:
    return str(a)

但是,,我發(fā)現(xiàn)使用裝飾器做任何花哨的事情(特別是不保留簽名的情況),,都會導(dǎo)致代碼難以類型化或者充斥著強制類型轉(zhuǎn)換。

這可能是一件好事,!Mypy 確實改變了我編寫 Python 的方式:耍小聰明的代碼更難被正確地類型化,,因此我盡量避免編寫討巧的代碼。

(裝飾器的另一個問題是我前面提過的@functools.lru_cache :由于裝飾器最終定義了一個全新的函數(shù),,所以如果你不正確地注解代碼,,就可能會出現(xiàn)嚴(yán)重而令人驚訝的錯誤。)

我對循環(huán)導(dǎo)入也有類似的感覺——由于要導(dǎo)入類型作為注解使用,,這就可能導(dǎo)致出現(xiàn)本可避免的循環(huán)導(dǎo)入(這也是 Zulip 團隊強調(diào)的一個痛點),。雖然循環(huán)導(dǎo)入是 Mypy 的一個痛點但這通常意味著系統(tǒng)或代碼本身存在著設(shè)計缺陷,,這是 Mypy 強迫我們?nèi)タ紤]的問題,。

不過,根據(jù)我的經(jīng)驗,,即使是經(jīng)驗豐富的 Mypy 用戶,,在類型檢查通過之前,他們也需對本來可以正常工作的代碼進行一兩處更正。

(順便說一下:Python 3.10 使用ParamSpec 對裝飾器的情況作了重大的改進,。)

# 提示與技巧

最后,,我要介紹幾個在使用 Mypy 時很有用的技巧。

 1. reveal_type

在代碼中添加reveal_type ,,可以讓 Mypy 在對文件進行類型檢查時,,顯示出變量的推斷類型。這是非常非常非常有用的,。

最簡單的例子是:

# No need to import anything. Just call `reveal_type`.
# Your editor will flag it as an undefined reference -- just ignore that.
x = 1
reveal_type(x)  # Revealed type is 'builtins.int'

當(dāng)你處理泛型時,,reveal_type 特別地有用,因為它可以幫助你理解泛型是如何被“填充”的,、類型是否被縮小了,,等等。

 2. Mypy 作為一個庫

Mypy 可以用作一個運行時庫,!

我們內(nèi)部有一個工作流編排庫,,看起來有點像 Flyte 或 Prefect。細節(jié)并不重要,,但值得注意的是,,它是完全類型化的——因此我們可以靜態(tài)地提升待運行任務(wù)的類型安全性,因為它們被鏈接在一起,。

把類型弄準(zhǔn)確是非常具有挑戰(zhàn)性的,。為了確保它完好,不被意外的Any毒害,,我們在一組文件上寫了調(diào)用 Mypy 的單元測試,,并斷言 Mypy 拋出的錯誤能匹配一系列預(yù)期內(nèi)的異常:

def test_check_function(self) -> None:
      result = api.run(
          [
              os.path.join(
                  os.path.dirname(__file__),
                  'type_check_examples/function.py',
              ),
              '--no-incremental',
          ],
      )

      actual = result[0].splitlines()
      expected = [
          # fmt: off
          'type_check_examples/function.py:14: error: Incompatible return value type (got 'str', expected 'int')',  # noqa: E501
          'type_check_examples/function.py:19: error: Missing positional argument 'x' in call to '__call__' of 'FunctionPipeline'',  # noqa: E501
          'type_check_examples/function.py:22: error: Argument 'x' to '__call__' of 'FunctionPipeline' has incompatible type 'str'; expected 'int'',  # noqa: E501
          'type_check_examples/function.py:25: note: Revealed type is 'builtins.int'',  # noqa: E501
          'type_check_examples/function.py:28: note: Revealed type is 'builtins.int'',  # noqa: E501
          'type_check_examples/function.py:34: error: Unexpected keyword argument 'notify_on' for 'options' of 'Expression'',  # noqa: E501
          'pipeline.py:307: note: 'options' of 'Expression' defined here',  # noqa: E501
          'Found 4 errors in 1 file (checked 1 source file)',
          # fmt: on
      ]

      self.assertEqual(actual, expected)

 3. GitHub 上的問題

當(dāng)搜索如何解決某個類型問題時,我經(jīng)常會找到 Mypy 的 GitHub Issues (比 Stack Overflow 還多),。它可能是 Mypy 類型相關(guān)問題的解決方案和 How-To 的最佳知識源頭,。你會發(fā)現(xiàn)其核心團隊(包括 Guido)對重要問題的提示和建議。

主要的缺點是,,GitHub Issue 中的每個評論僅僅是某個特定時刻的評論——2018 年的一個問題可能已經(jīng)解決了,,去年的一個變通方案可能有了新的最佳實踐,。所以在查閱 issue 時,,一定要把這一點牢記于心。

 4. typing-extensions

typing 模塊在每個 Python 版本中都有很多改進,,同時,,還有一些特性會通過typing-extensions 模塊向后移植。

例如,,雖然只使用 Python 3.8,,但我們借助typing-extensions ,在前面提到的工作流編排庫中使用了3.10 版本的ParamSpec。(遺憾的是,,PyCharm 似乎不支持通過typing-extensions 引入的ParamSpec 語法,,并將其標(biāo)記為一個錯誤,但是,,還算好吧,。)當(dāng)然,Python 本身語法變化而出現(xiàn)的特性,,不能通過typing-extensions 獲得,。

 5. NewType

typing 模塊中有很多有用的輔助對象,NewType 是我的最愛之一,。

NewType 可讓你創(chuàng)建出不同于現(xiàn)有類型的類型,。例如,你可以使用NewType 來定義合規(guī)的谷歌云存儲 URL,,而不僅是str 類型,,比如:

from typing import NewType

GCSUrl = NewType('GCSUrl', str)

def download_blob(url: GCSUrl) -> None:
    ...

# Incompatible type 'str'; expected 'GCSUrl'
download_blob('gs://my_bucket/foo/bar/baz.jpg')

# Ok!
download_blob(GCSUrl('gs://my_bucket/foo/bar/baz.jpg'))

通過向download_blob 的調(diào)用者指出它的意圖,我們使這個函數(shù)具備了自描述能力,。

我發(fā)現(xiàn) NewType對于將原始類型(如 strint )轉(zhuǎn)換為語義上有意義的類型特別有用,。

 6. 性能

Mypy 的性能并不是我們的主要問題。Mypy 將類型檢查結(jié)果保存到緩存中,,能加快重復(fù)調(diào)用的速度(據(jù)其文檔稱:“Mypy 增量地執(zhí)行類型檢查,,復(fù)用前一次運行的結(jié)果,以加快后續(xù)運行的速度”),。

在我們最大的服務(wù)中運行 mypy,,冷緩存大約需要 50-60 秒,熱緩存大約需要 1-2 秒,。

至少有兩種方法可以加速 Mypy,,這兩種方法都利用了以下的技術(shù)(我們內(nèi)部沒有使用):

  1. Mypy 守護進程在后臺持續(xù)運行 Mypy,讓它在內(nèi)存中保持緩存狀態(tài),。雖然 Mypy 在運行后將結(jié)果緩存到磁盤,,但是守護進程確實是更快。(我們使用了一段時間的默認(rèn) Mypy 守護進程,,但因共享狀態(tài)導(dǎo)致一些問題后,,我禁用了它——我不記得具體細節(jié)了。)

  2. 共享遠程緩存,。如前所述,,Mypy 在每次運行后都會將類型檢查結(jié)果緩存到磁盤——但是如果在新機器或新容器上運行 Mypy(就像在 CI 上一樣),則不會有緩存的好處,。解決方案是在磁盤上預(yù)置一個最近的緩存結(jié)果(即,,預(yù)熱緩存),。Mypy 文檔概述了這個過程,但它相當(dāng)復(fù)雜,,具體內(nèi)容取決于你自己的設(shè)置,。我們最終可能會在自己的 CI 系統(tǒng)中啟用它——暫時還沒有去做。

# 結(jié)論

Mypy 對我們產(chǎn)生了很大的影響,,提升了我們發(fā)布代碼時的信心,。雖然采納它需要付出一定的成本,但我們并不后悔,。

除了工具本身的價值之外,,Mypy 還是一個讓人印象非常深刻的項目,我非常感謝維護者們多年來為它付出的工作,。在每一個 Mypy 和 Python 版本中,,我們都看到了對 typing模塊、注解語法和 Mypy 本身的顯著改進,。(例如:新的聯(lián)合類型語法( X|Y),、 ParamSpecTypeAlias,這些都包含在 Python 3.10 中,。)

原文發(fā)布于 2022 年 8 月 21 日,。

作者:Charlie Marsh
譯者:豌豆花下貓@Python貓
英文:Using Mypy in production at Spring (https://notes./using-mypy-in-production-at-spring)

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多