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

来源:https://www.cnblogs.com/pythonista/archive/2022/09/05/16659656.html
-Advertisement-
Play Games

Spring(三)——AOP 概念 什麼是AOP (1)面向切麵編程(方面),利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。 (2)通俗描述:不通過修改源代碼方式,在主幹功能裡面添加新功能 AOP底層原理 JDK動態 ...


作者:Charlie Marsh

譯者:豌豆花下貓@Python貓

英文:Using Mypy in production at Spring (https://notes.crmarsh.com/using-mypy-in-production-at-spring)

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

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

誠然,代碼行數是一個糟糕的衡量標準,但可作一個粗略的估計:我們的代碼倉有超過 30 萬行 Python 代碼,其中大約一半構成了核心的數據平臺,另一半是由數據科學家和機器學習研究員編寫的終端用戶代碼。

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

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

幾周前,我跟 Leo BoytsovErik Bernhardsson 在 Twitter 上對 Python 類型有一次簡短的討論——然後我看到 Will McGugan 也對類型大加贊賞。由於 Mypy 是我們在 Spring 公司發佈和迭代 Python 代碼的關鍵部分,我想寫一下我們在過去幾年中大規模使用它的經驗。

一句話總結:雖然採用 Mypy 是有代價的(前期和持續的投入、學習曲線等),但我發現它對於維護大型 Python 代碼庫有著不可估量的價值。Mymy 可能不適合於所有人,但它十分適合我。

Mypy 是什麼?

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

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

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

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

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

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

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

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

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

在 Spring 集成 Mypy

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

  1. 雖然 Mypy 在 2012 年的 PyCon 芬蘭大會上首次亮相,併在 2015 年初發佈了相容 PEP-484 的版本,但它仍然是一個相當新的工具——至少對我們來說是這樣。儘管我們在一些相當大的 Python 代碼庫上工作過(在可汗學院和其它地方),但團隊中沒有人使用過它。
  2. 像其它增量類型檢查工具一樣(例如 Flow),隨著代碼庫的註解越來越多,Mypy 的價值會與時俱增。由於 Mypy 可以並且將會用最少的註解捕獲 bug,所以你在代碼庫上投入註解的時間越多,它就會變得越有價值。

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

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

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

反饋

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

我將在以後的任何 Python 項目中繼續使用它。

好處

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

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

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

  1. 改善可讀性:有了類型註解,代碼趨向於自描述(與文檔字元串不同,這種描述的準確性可以靜態地強制執行)。(英:self-documenting)
  2. 捕獲錯誤:是真的!Mypy 確實能找出 bug。從始至終。
  3. 自信地重構:這是 Mypy 最有影響力的一個好處。有了 Mypy 的廣泛覆蓋,我可以自信地發佈涉及數百甚至數千個文件的更改。當然,這與上一條好處有關——我們用 Mypy 找出的大多數 bug 都是在重構時發現的。

第三點的價值怎麼強調都不為過。毫不誇張地說,在 Mypy 的幫助下,我發佈更改的速度快了十倍,甚至快了一百倍。

雖然這是完全主觀的,但在寫這篇文章時,我意識到:我信任 Mypy。雖然程度還不及,比如說 OCaml 編譯器,但它完全改變了我維護 Python 代碼的關係,我無法想象回到沒有註解的世界。

痛點

Zulip 的帖子同樣強調了他們在遷移 Mypy 時所經歷的痛點(與靜態代碼分析工具的交互,迴圈導入)。

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

  1. 外部庫缺乏類型註解
  2. Mypy 學習曲線
  3. 對抗類型系統

讓我們來逐一回顧一下:

1. 外部庫缺乏類型註解

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

每當在環境里添加一個第三方庫時,我們都會在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 標準庫上也遇到了一些不順。例如,functools.lru_cache 儘管在 typeshed 里有類型註解,但由於複雜的原因,它不保留底層函數的簽名,所以任何用 @functools.lru_cache 裝飾的函數都會被移除所有類型註解。

例如,Mypy 允許這樣做:

import functools

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

add_one("Hello, world!")

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

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

2. Mypy 學習曲線

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

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

所以,當我們招募團隊成員時,Mypy 往往是他們必須學習的新東西。雖然類型註解語法的基礎很簡單,但我們經常聽到這樣的問題:“為什麼 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)

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

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

3. 對抗類型系統

在編寫代碼時,我會儘量避免幾件事,以免導致自己與類型系統作鬥爭:寫出我知道可行的代碼,並強迫 Mypy 接受。

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

基本原理很簡單:

@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

但通常,我們想要做一些事情,比如“基於布爾值返回不同的類型,帶有預設值”,這需要這樣的技巧:

@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 模塊:可能很有用,但往往會產生笨拙的代碼。

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

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 的 文檔 對保留簽名的裝飾器和裝飾器工廠有一個規範的建議。它很先進,但確實有效:

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)

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

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

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

我對迴圈導入也有類似的感覺——由於要導入類型作為註解使用,這就可能導致出現本可避免的迴圈導入(這也是 Zulip 團隊強調的一個痛點)。雖然迴圈導入是 Mypy 的一個痛點但這通常意味著系統或代碼本身存在著設計缺陷,這是 Mypy 強迫我們去考慮的問題。

不過,根據我的經驗,即使是經驗豐富的 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"

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

2. Mypy 作為一個庫

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

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

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

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 上的問題

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

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

4. typing-extensions

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

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

5. NewType

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

NewType 可讓你創建出不同於現有類型的類型。例如,你可以使用NewType 來定義合規的谷歌雲存儲 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 的調用者指出它的意圖,我們使這個函數具備了自描述能力。

我發現 NewType對於將原始類型(如 strint )轉換為語義上有意義的類型特別有用。

6. 性能

Mypy 的性能並不是我們的主要問題。Mypy 將類型檢查結果保存到緩存中,能加快重覆調用的速度(據其文檔稱:“Mypy 增量地執行類型檢查,復用前一次運行的結果,以加快後續運行的速度”)。

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

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

  1. Mypy 守護進程在後臺持續運行 Mypy,讓它在記憶體中保持緩存狀態。雖然 Mypy 在運行後將結果緩存到磁碟,但是守護進程確實是更快。(我們使用了一段時間的預設 Mypy 守護進程,但因共用狀態導致一些問題後,我禁用了它——我不記得具體細節了。)
  2. 共用遠程緩存。如前所述,Mypy 在每次運行後都會將類型檢查結果緩存到磁碟——但是如果在新機器或新容器上運行 Mypy(就像在 CI 上一樣),則不會有緩存的好處。解決方案是在磁碟上預置一個最近的緩存結果(即,預熱緩存)。Mypy 文檔概述了這個過程,但它相當複雜,具體內容取決於你自己的設置。我們最終可能會在自己的 CI 系統中啟用它——暫時還沒有去做。

結論

Mypy 對我們產生了很大的影響,提升了我們發佈代碼時的信心。雖然採納它需要付出一定的成本,但我們並不後悔。

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

原文發佈於 2022 年 8 月 21 日。

作者:Charlie Marsh

譯者:豌豆花下貓@Python貓

英文:Using Mypy in production at Spring (https://notes.crmarsh.com/using-mypy-in-production-at-spring)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 同時使用過渡和動畫 點擊打開視頻講解更加詳細 Vue 為了知道過渡的完成,必須設置相應的事件監聽器。它可以是 transitionend 或 animationend,這取決於給元素應用的 CSS 規則。如果你使用其中任何一種,Vue 能自動識別類型並設置監聽。 但是,在一些場景中,你需要給同一個元 ...
  • 定義:適配器模式是將一個類的介面轉換成客戶希望的另一個介面,適配器模式使得原本由於介面不相容而不能一起工作的類可以一起工作,在軟體設計中我們需要將一些“現存的對象”放到新的環境中,而新環境要求的介面是現對象所不能滿足的,我們可以使用這種模式進行介面適配轉換,使得“老對象”符合新環境的要求。 使用場景 ...
  • 構建者是一種可以將複雜對象的構建和表示分離開來,從而使得一個構建過程可以生成多個不同的表示對象。建造者模式通過一步一步構建對象。 ...
  • 觀察者模式又叫發佈-訂閱(Publish-Subscribe)模式,是對象的行為模式,訂閱是表示這些觀察者對象需要向目標對象進行註冊,這樣目標對象才知道有哪些對象在觀察它。發佈指的是當目標對象的狀態改變時,它就向它所有的觀察者對象發佈狀態更改的消息,以讓這些觀察者對象知曉。定義對象間的一種一對多的依... ...
  • 工作中總是遇到數據存儲相關的 Bug 工單,新需求開發設計中也多多少少會有數據模型設計和存儲相關的問題。經過幾次存儲方案設計選型和討論後發現需要有更全面的思考框架。 日常開發中常用的存儲方案選型很多都是 “拿來主義” 的,憑藉著經驗、習慣選用,但對它們的細節特性或約束少有研究。 除了手邊會用的存儲方... ...
  • 坦克大戰【3】 筆記目錄:(https://www.cnblogs.com/wenjie2000/p/16378441.html) 坦克大戰0.6版 √增加功能 防止敵人坦克重疊運動 記錄玩家的成績(累積擊毀敵方坦克數),存檔退出【io流】 記錄當時的敵人坦克坐標與方向,存檔退出【io流】 玩游戲時 ...
  • 發現問題 前幾天在看別人的項目的時候,發現一個問題,簡單復現一下這個問題 // 註意這是一個Integer對象的數組哦 Integer[] arr = new Integer[]{9999,88,77}; List<Integer> list = Arrays.asList(arr); // 執行以 ...
  • 前置知識 什麼是進程,什麼又是線程?咱不是講系統,簡單說下,知道個大概就好了。 進程:一個可執行文件執行的過程。 線程:操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務 什 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...