日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
創(chuàng)新互聯(lián)Python教程:ArgumentClinic的用法

Argument Clinic 的用法

作者

Larry Hastings

摘要

Argument Clinic 是 Cpython 的一個 C 文件預處理器。旨在自動處理所有與“內(nèi)置”參數(shù)解析有關(guān)的代碼。本文展示了將 C 函數(shù)轉(zhuǎn)換為配合 Argument Clinic 工作的做法,還介紹了一些關(guān)于 Argument Clinic 用法的進階內(nèi)容。

目前 Argument Clinic 視作僅供 CPython 內(nèi)部使用。不支持在 CPython 之外的文件中使用,也不保證未來版本會向下兼容。換句話說:如果維護的是 CPython 的外部 C 語言擴展,歡迎在自己的代碼中試用 Argument Clinic。但 Argument Clinic 與新版 CPython 中的版本 可能 完全不兼容,且會打亂全部代碼。

Argument Clinic 的設計目標

Argument Clinic 的主要目標,是接管 CPython 中的所有參數(shù)解析代碼。這意味著,如果要把某個函數(shù)轉(zhuǎn)換為配合 Argument Clinic一起工作,則該函數(shù)不應再作任何參數(shù)解析工作——Argument Clinic 生成的代碼應該是個“黑盒”,CPython 會在頂部發(fā)起調(diào)用,底部則調(diào)用自己的代碼, PyObject *args (也許還有 PyObject *kwargs )會神奇地轉(zhuǎn)換成所需的 C 變量和類型。

Argument Clinic 為了能完成主要目標,用起來必須方便。目前,使用 CPython 的參數(shù)解析庫是一件苦差事,需要在很多地方維護冗余信息。如果使用 Argument Clinic,則不必再重復代碼了。

顯然,除非 Argument Clinic 解決了自身的問題,且沒有產(chǎn)生新的問題,否則沒有人會愿意用它。所以,Argument Clinic 最重要的事情就是生成正確的代碼。如果能加速代碼的運行當然更好,但至少不應引入明顯的減速。(最終 Argument Clinic 應該 可以實現(xiàn)較大的速度提升——代碼生成器可以重寫一下,以產(chǎn)生量身定做的參數(shù)解析代碼,而不是調(diào)用通用的 CPython 參數(shù)解析庫。 這會讓參數(shù)解析達到最佳速度?。?/p>

此外,Argument Clinic 必須足夠靈活,能夠與任何參數(shù)解析的方法一起工作。Python 有一些函數(shù)具備一些非常奇怪的解析行為;Argument Clinic 的目標是支持所有這些函數(shù)。

最后,Argument Clinic 的初衷是為 CPython 內(nèi)置程序提供內(nèi)省“簽名”。以前如果傳入一個內(nèi)置函數(shù),內(nèi)省查詢函數(shù)會拋出異常。有了 Argument Clinic,再不會發(fā)生這種問題了!

在與 Argument Clinic 合作時,應該牢記一個理念:給它的信息越多,它做得就會越好。誠然,Argument Clinic 現(xiàn)在還比較簡單。但會演變得越來越復雜,應該能夠利用給出的全部信息干很多聰明而有趣的事情。

基本概念和用法

Argument Clinic 與 CPython 一起提供,位于 Tools/clinic/clinic.py 。若要運行它,請指定一個 C 文件作為參數(shù)。

 
 
 
 
  1. $ Python3 Tools/clinic/clinic.py foo.c

Argument Clinic 會掃描 C 文件,精確查找以下代碼:

 
 
 
 
  1. /*[clinic input]

一旦找到一條后,就會讀取所有內(nèi)容,直至遇到以下代碼:

 
 
 
 
  1. [clinic start generated code]*/

這兩行之間的所有內(nèi)容都是 Argument Clinic 的輸入。所有行,包括開始和結(jié)束的注釋行,統(tǒng)稱為 Argument Clinic “塊”。

Argument Clinic 在解析某一塊時,會生成輸出信息。輸出信息會緊跟著該塊寫入 C 文件中,后面還會跟著包含校驗和的注釋。現(xiàn)在 Argument Clinic 塊看起來應如下所示:

 
 
 
 
  1. /*[clinic input]
  2. ... clinic input goes here ...
  3. [clinic start generated code]*/
  4. ... clinic output goes here ...
  5. /*[clinic end generated code: checksum=...]*/

如果對同一文件第二次運行 Argument Clinic,則它會丟棄之前的輸出信息,并寫入帶有新校驗行的輸出信息。不過如果輸入沒有變化,則輸出也不會有變化。

不應去改動 Argument Clinic 塊的輸出部分。而應去修改輸入,直到生成所需的輸出信息。(這就是校驗和的用途——檢測是否有人改動了輸出信息,因為在 Argument Clinic 下次寫入新的輸出時,這些改動都會丟失)。

為了清晰起見,下面列出了 Argument Clinic 用到的術(shù)語:

  • 注釋的第一行 /*[clinic input]起始行 。

  • 注釋([clinic start generated code]*/)的最后一行是 結(jié)束行。

  • 最后一行(/*[clinic end generated code: checksum=...]*/)是 校驗和行 。

  • 在起始行和結(jié)束行之間是 輸入數(shù)據(jù)。

  • 在結(jié)束行和校驗和行之間是 輸出數(shù)據(jù) 。

  • 從開始行到校驗和行的所有文本,都是 。(Argument Clinic 尚未處理成功的塊,沒有輸出或校驗和行,但仍視作一個塊)。

函數(shù)的轉(zhuǎn)換

要想了解 Argument Clinic 是如何工作的,最好的方式就是轉(zhuǎn)換一個函數(shù)與之合作。下面介紹需遵循的最基本步驟。請注意,若真的準備在 CPython 中進行檢查,則應進行更深入的轉(zhuǎn)換,使用一些本文后續(xù)會介紹到的高級概念(比如 “返回轉(zhuǎn)換” 和 “自轉(zhuǎn)換”)。但以下例子將維持簡單,以供學習。

就此開始

  1. 請確保 CPython 是最新的已簽出版本。

  2. 找到一個調(diào)用 PyArg_ParseTuple() 或 PyArg_ParseTupleAndKeywords() ,且未被轉(zhuǎn)換為采用 Argument Clinic 的 Python 內(nèi)置程序。這里用了 _pickle.Pickler.dump()

  3. 如果對 PyArg_Parse 函數(shù)的調(diào)用采用了以下格式化單元:

       
       
       
       
    1. O&
    2. O!
    3. es
    4. es#
    5. et
    6. et#

    或者多次調(diào)用 PyArg_ParseTuple(),則應再選一個函數(shù)。Argument Clinic 確實 支持上述這些狀況。 但這些都是高階內(nèi)容——第一次就簡單一些吧。

    此外,如果多次調(diào)用 PyArg_ParseTuple() 或 PyArg_ParseTupleAndKeywords() 且同一參數(shù)需支持不同的類型,或者用到 PyArg_Parse 以外的函數(shù)來解析參數(shù),則可能不適合轉(zhuǎn)換為 Argument Clinic 形式。 Argument Clinic 不支持通用函數(shù)或多態(tài)參數(shù)。

  4. 在函數(shù)上方添加以下模板,創(chuàng)建塊:

       
       
       
       
    1. /*[clinic input]
    2. [clinic start generated code]*/
  5. 剪下文檔字符串并粘貼到 [clinic] 行之間,去除所有的無用字符,使其成為一個正確引用的 C 字符串。最有應該只留下帶有左側(cè)縮進的文本,且行寬不大于 80 個字符。(參數(shù) Clinic 將保留文檔字符串中的縮進。)

    如果文檔字符串的第一行看起來像是函數(shù)的簽名,就把這一行去掉吧。((文檔串不再需要用到它——將來對內(nèi)置函數(shù)調(diào)用 help() 時,第一行將根據(jù)函數(shù)的簽名自動建立。)

    示例:

       
       
       
       
    1. /*[clinic input]
    2. Write a pickled representation of obj to the open file.
    3. [clinic start generated code]*/
  6. 如果文檔字符串中沒有“摘要”行,Argument Clinic 會報錯。所以應確保帶有摘要行。 “摘要”行應為在文檔字符串開頭的一個段落,由一個80列的單行構(gòu)成。

    (示例的文檔字符串只包括一個摘要行,所以示例代碼這一步不需改動)。

  7. 在文檔字符串上方,輸入函數(shù)的名稱,后面是空行。這應是函數(shù)的 Python 名稱,而且應是句點分隔的完整路徑——以模塊的名稱開始,包含所有子模塊名,若函數(shù)為類方法則還應包含類名。

    示例:

       
       
       
       
    1. /*[clinic input]
    2. _pickle.Pickler.dump
    3. Write a pickled representation of obj to the open file.
    4. [clinic start generated code]*/
  8. 如果是第一次在此 C 文件中用到 Argument Clinic 的模塊或類,必須對其進行聲明。清晰的 Argument Clinic 寫法應于 C 文件頂部附近的某個單獨塊中聲明這些,就像 include 文件和 statics 放在頂部一樣。(在這里的示例代碼中,將這兩個塊相鄰給出。)

    類和模塊的名稱應與暴露給 Python 的相同。請適時檢查 PyModuleDef 或 PyTypeObject 中定義的名稱。

    在聲明某個類時,還必須指定其 C 語言類型的兩個部分:用于指向該類實例的指針的類型聲明,和指向該類 PyTypeObject 的指針。

    示例:

       
       
       
       
    1. /*[clinic input]
    2. module _pickle
    3. class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    4. [clinic start generated code]*/
    5. /*[clinic input]
    6. _pickle.Pickler.dump
    7. Write a pickled representation of obj to the open file.
    8. [clinic start generated code]*/
  9. 聲明函數(shù)的所有參數(shù)。每個參數(shù)都應另起一行。所有的參數(shù)行都應對齊函數(shù)名和文檔字符串進行縮進。

    這些參數(shù)行的常規(guī)形式如下:

       
       
       
       
    1. name_of_parameter: converter

    如果參數(shù)帶有缺省值,請加在轉(zhuǎn)換器之后:

       
       
       
       
    1. name_of_parameter: converter = default_value

    Argument Clinic 對 “缺省值” 的支持方式相當復雜;更多信息請參見 關(guān)于缺省值的部分 。

    在參數(shù)行下面添加一個空行。

    What’s a “converter”? It establishes both the type of the variable used in C, and the method to convert the Python value into a C value at runtime. For now you’re going to use what’s called a “l(fā)egacy converter”—a convenience syntax intended to make porting old code into Argument Clinic easier.

    每個參數(shù)都要從``PyArg_Parse()`` 格式參數(shù)中復制其 “格式單元”,并以帶引號字符串的形式指定其轉(zhuǎn)換器。(“格式單元”是 format 參數(shù)的1-3個字符的正式名稱,用于讓參數(shù)解析函數(shù)知曉該變量的類型及轉(zhuǎn)換方法。關(guān)于格式單位的更多信息,請參閱 解析參數(shù)并構(gòu)建值變量 )。

    對于像 z# 這樣的多字符格式單元,要使用2-3個字符組成的整個字符串。

    示例:

       
       
       
       
    1. /*[clinic input]
    2. module _pickle
    3. class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    4. [clinic start generated code]*/
    5. /*[clinic input]
    6. _pickle.Pickler.dump
    7. obj: 'O'
    8. Write a pickled representation of obj to the open file.
    9. [clinic start generated code]*/
  10. 如果函數(shù)的格式字符串包含 |,意味著有些參數(shù)帶有缺省值,這可以忽略。Argument Clinic 根據(jù)參數(shù)是否有缺省值來推斷哪些參數(shù)是可選的。

    如果函數(shù)的格式字符串中包含 $,意味著只接受關(guān)鍵字參數(shù),請在第一個關(guān)鍵字參數(shù)之前單獨給出一行 *,縮進與參數(shù)行對齊。

    _pickle.Pickler.dump 兩種格式字符串都沒有,所以這里的示例不用改動。)

  11. 如果 C 函數(shù)調(diào)用的是 PyArg_ParseTuple() (而不是 PyArg_ParseTupleAndKeywords()),那么其所有參數(shù)均是僅限位置參數(shù)。

    若要在 Argument Clinic 中把所有參數(shù)都標記為只認位置,請在最后一個參數(shù)后面一行加入一個 /,縮進程度與參數(shù)行對齊。

    目前這個標記是全體生效;要么所有參數(shù)都是只認位置,要么都不是。(以后 Argument Clinic 可能會放寬這一限制。)

    示例:

       
       
       
       
    1. /*[clinic input]
    2. module _pickle
    3. class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    4. [clinic start generated code]*/
    5. /*[clinic input]
    6. _pickle.Pickler.dump
    7. obj: 'O'
    8. /
    9. Write a pickled representation of obj to the open file.
    10. [clinic start generated code]*/
  12. 為每個參數(shù)都編寫一個文檔字符串,這很有意義。但這是可選項;可以跳過這一步。

    下面介紹如何添加逐參數(shù)的文檔字符串。逐參數(shù)文檔字符串的第一行必須比參數(shù)定義多縮進一層。第一行的左邊距即確定了所有逐參數(shù)文檔字符串的左邊距;所有文檔字符串文本都要同等縮進。文本可以用多行編寫。

    示例:

       
       
       
       
    1. /*[clinic input]
    2. module _pickle
    3. class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    4. [clinic start generated code]*/
    5. /*[clinic input]
    6. _pickle.Pickler.dump
    7. obj: 'O'
    8. The object to be pickled.
    9. /
    10. Write a pickled representation of obj to the open file.
    11. [clinic start generated code]*/
  13. 保存并關(guān)閉該文件,然后運行 Tools/clinic/clinic.py 。 運氣好的話就萬事大吉——程序塊現(xiàn)在有了輸出信息,并且生成了一個 .c.h 文件!在文本編輯器中重新打開該文件,可以看到:

       
       
       
       
    1. /*[clinic input]
    2. _pickle.Pickler.dump
    3. obj: 'O'
    4. The object to be pickled.
    5. /
    6. Write a pickled representation of obj to the open file.
    7. [clinic start generated code]*/
    8. static PyObject *
    9. _pickle_Pickler_dump(PicklerObject *self, PyObject *obj)
    10. /*[clinic end generated code: output=87ecad1261e02ac7 input=552eb1c0f52260d9]*/

    顯然,如果 Argument Clinic 未產(chǎn)生任何輸出,那是因為在輸入信息中發(fā)現(xiàn)了錯誤。繼續(xù)修正錯誤并重試,直至 Argument Clinic 正確地處理好文件。

    為了便于閱讀,大部分“膠水”代碼已寫入 .c.h 文件中。需在原 .c 文件中包含這個文件,通常是在 clinic 模塊之后:

       
       
       
       
    1. #include "clinic/_pickle.c.h"
  14. 請仔細檢查 Argument Clinic 生成的參數(shù)解析代碼,是否與原有代碼基本相同。

    首先,確保兩種代碼使用相同的參數(shù)解析函數(shù)。原有代碼必須調(diào)用 PyArg_ParseTuple() 或 PyArg_ParseTupleAndKeywords() ;確保 Argument Clinic 生成的代碼調(diào)用 完全 相同的函數(shù)。

    其次,傳給 PyArg_ParseTuple() 或 PyArg_ParseTupleAndKeywords() 的格式字符串應該 完全 與原有函數(shù)中的相同,直到冒號或分號為止。

    (Argument Clinic 生成的格式串一定是函數(shù)名后跟著 :。如果現(xiàn)有代碼的格式串以 ; 結(jié)尾,這種改動不會影響使用,因此不必擔心。)

    第三,如果格式單元需要指定兩個參數(shù)(比如長度、編碼字符串或指向轉(zhuǎn)換函數(shù)的指針),請確保第二個參數(shù)在兩次調(diào)用時 完全 相同。

    第四,在輸出部分會有一個預處理器宏,為該內(nèi)置函數(shù)定義合適的靜態(tài) PyMethodDef 結(jié)構(gòu):

       
       
       
       
    1. #define __PICKLE_PICKLER_DUMP_METHODDEF \
    2. {"dump", (PyCFunction)__pickle_Pickler_dump, METH_O, __pickle_Pickler_dump__doc__},

    此靜態(tài)結(jié)構(gòu)應與本內(nèi)置函數(shù)現(xiàn)有的靜態(tài)結(jié)構(gòu) PyMethodDef 完全 相同。

    只要上述這幾點存在不一致,請調(diào)整 Argument Clinic 函數(shù)定義,并重新運行 Tools/clinic/clinic.py ,直至 完全 相同。

  15. 注意,輸出部分的最后一行是“實現(xiàn)”函數(shù)的聲明。也就是該內(nèi)置函數(shù)的實現(xiàn)代碼所在。刪除需要修改的函數(shù)的現(xiàn)有原型,但保留開頭的大括號。再刪除其參數(shù)解析代碼和輸入變量的所有聲明。注意現(xiàn)在 Python 所見的參數(shù)即為此實現(xiàn)函數(shù)的參數(shù);如果實現(xiàn)代碼給這些變量采用了不同的命名,請進行修正。

    因為稍顯怪異,所以還是重申一下?,F(xiàn)在的代碼應該如下所示:

       
       
       
       
    1. static return_type
    2. your_function_impl(...)
    3. /*[clinic end generated code: checksum=...]*/
    4. {
    5. ...

    上面是 Argument Clinic 生成的校驗值和函數(shù)原型。函數(shù)應該帶有閉合的大括號,實現(xiàn)代碼位于其中。

    示例:

       
       
       
       
    1. /*[clinic input]
    2. module _pickle
    3. class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    4. [clinic start generated code]*/
    5. /*[clinic end generated code: checksum=da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
    6. /*[clinic input]
    7. _pickle.Pickler.dump
    8. obj: 'O'
    9. The object to be pickled.
    10. /
    11. Write a pickled representation of obj to the open file.
    12. [clinic start generated code]*/
    13. PyDoc_STRVAR(__pickle_Pickler_dump__doc__,
    14. "Write a pickled representation of obj to the open file.\n"
    15. "\n"
    16. ...
    17. static PyObject *
    18. _pickle_Pickler_dump_impl(PicklerObject *self, PyObject *obj)
    19. /*[clinic end generated code: checksum=3bd30745bf206a48f8b576a1da3d90f55a0a4187]*/
    20. {
    21. /* Check whether the Pickler was initialized correctly (issue3664).
    22. Developers often forget to call __init__() in their subclasses, which
    23. would trigger a segfault without this check. */
    24. if (self->write == NULL) {
    25. PyErr_Format(PicklingError,
    26. "Pickler.__init__() was not called by %s.__init__()",
    27. Py_TYPE(self)->tp_name);
    28. return NULL;
    29. }
    30. if (_Pickler_ClearBuffer(self) < 0)
    31. return NULL;
    32. ...
  16. 還記得用到 PyMethodDef 結(jié)構(gòu)的宏吧?找到函數(shù)中已有的 PyMethodDef 結(jié)構(gòu),并替換為宏的引用。(如果函數(shù)是模塊級的,可能會在文件的末尾附近;如果函數(shù)是個類方法,則可能會在靠近實現(xiàn)代碼的下方。)

    注意,宏尾部帶有一個逗號。所以若用宏替換已有的靜態(tài)結(jié)構(gòu) PyMethodDef 時,請勿 在結(jié)尾添加逗號了。

    示例:

       
       
       
       
    1. static struct PyMethodDef Pickler_methods[] = {
    2. __PICKLE_PICKLER_DUMP_METHODDEF
    3. __PICKLE_PICKLER_CLEAR_MEMO_METHODDEF
    4. {NULL, NULL} /* sentinel */
    5. };
  17. Compile, then run the relevant portions of the regression-test suite. This change should not introduce any new compile-time warnings or errors, and there should be no externally visible change to Python’s behavior.

    差別只有一個,即 inspect.signature() 運行于新的函數(shù)上,現(xiàn)在應該新提供一個有效的簽名!

    祝賀你,現(xiàn)在已經(jīng)用 Argument Clinic 移植了第一個函數(shù)。

進階

現(xiàn)在 Argument Clinic 的使用經(jīng)驗已具備了一些,該介紹一些高級內(nèi)容了。

符號化默認值

提供給參數(shù)的默認值不能是表達式。目前明確支持以下形式:

  • 數(shù)值型常數(shù)(整數(shù)和浮點數(shù))。

  • 字符串常量

  • True 、 FalseNone

  • 以模塊名開頭的簡單符號常量,如 sys.maxsize。

(未來可能需要加以細化,以便可以采用 CONSTANT - 1 之類的完整表達式。)

對 Argument Clinic 生成的 C 函數(shù)和變量進行重命名

Argument Clinic 會自動為其生成的函數(shù)命名。如果生成的名稱與現(xiàn)有的 C 函數(shù)沖突,這偶爾可能會造成問題,有一個簡單的解決方案:覆蓋 C 函數(shù)的名稱。只要在函數(shù)聲明中加入關(guān)鍵字 "as" ,然后再加上要使用的函數(shù)名。Argument Clinic 將以該函數(shù)名為基礎(chǔ)作為(生成的)函數(shù)名,然后在后面加上 "_impl",并用作實現(xiàn)函數(shù)的名稱。

例如,若對 pickle.Pickler.dump 生成的 C 函數(shù)進行重命名,應如下所示:

 
 
 
 
  1. /*[clinic input]
  2. pickle.Pickler.dump as pickler_dumper
  3. ...

原函數(shù)會被命名為 pickler_dumper(),而實現(xiàn)函數(shù)現(xiàn)在被命名為``pickler_dumper_impl()``。

同樣的問題依然會出現(xiàn):想給某個參數(shù)取個 Python 用名,但在 C 語言中可能用不了。Argument Clinic 允許在 Python 和 C 中為同一個參數(shù)取不同的名字,依然是利用 "as" 語法:

 
 
 
 
  1. /*[clinic input]
  2. pickle.Pickler.dump
  3. obj: object
  4. file as file_obj: object
  5. protocol: object = NULL
  6. *
  7. fix_imports: bool = True

這里 Python(簽名和 keywords 數(shù)組中)中用的名稱是 file,而 C 語言中的變量命名為 file_obj。

self 參數(shù)也可以進行重命名。

函數(shù)轉(zhuǎn)換會用到 PyArg_UnpackTuple

若要將函數(shù)轉(zhuǎn)換為采用 PyArg_UnpackTuple() 解析其參數(shù),只需寫出所有參數(shù),并將每個參數(shù)定義為 object??梢灾付?type 參數(shù),以便能轉(zhuǎn)換為合適的類型。所有參數(shù)都應標記為只認位置(在最后一個參數(shù)后面加上 /)。

目前,所生成的代碼將會用到 PyArg_ParseTuple() ,但很快會做出改動。

可選參數(shù)組

有些過時的函數(shù)用到了一種讓人頭疼的函數(shù)解析方式:計算位置參數(shù)的數(shù)量,據(jù)此用 switch 語句進行各個不同的 PyArg_ParseTuple() 調(diào)用。(這些函數(shù)不能接受只認關(guān)鍵字的參數(shù)。)在沒有 PyArg_ParseTupleAndKeywords() 之前,這種方式曾被用于模擬可選參數(shù)。

雖然這種函數(shù)通??梢赞D(zhuǎn)換為采用 PyArg_ParseTupleAndKeywords() 、可選參數(shù)和默認值的方式,但并不是全都可以做到。這些過時函數(shù)中, PyArg_ParseTupleAndKeywords() 并不能直接支持某些功能。最明顯的例子是內(nèi)置函數(shù) range(),它的必需參數(shù)的 邊存在一個可選參數(shù)!另一個例子是 curses.window.addch(),它的兩個參數(shù)是一組,必須同時指定。(參數(shù)名為 xy;如果調(diào)用函數(shù)時傳入了 x,則必須同時傳入``y``;如果未傳入 x ,則也不能傳入 y)。

不管怎么說,Argument Clinic 的目標就是在不改變語義的情況下支持所有現(xiàn)有 CPython 內(nèi)置參數(shù)的解析。因此,Argument Clinic 采用所謂的 可選組 方案來支持這種解析方式??蛇x組是必須一起傳入的參數(shù)組。他們可以在必需參數(shù)的左邊或右邊,只能 用于只認位置的參數(shù)。

備注

可選組 適用于多次調(diào)用 PyArg_ParseTuple() 的函數(shù)!采用 任何 其他方式解析參數(shù)的函數(shù),應該 幾乎不 采用可選組轉(zhuǎn)換為 Argument Clinic 解析。目前,采用可選組的函數(shù)在 Python 中無法獲得準確的簽名,因為 Python 不能理解這個概念。請盡可能避免使用可選組。

若要定義可選組,可在要分組的參數(shù)前面加上 [,在這些參數(shù)后加上``]`` ,要在同一行上。舉個例子,下面是 curses.window.addch 采用可選組的用法,前兩個參數(shù)和最后一個參數(shù)可選:

 
 
 
 
  1. /*[clinic input]
  2. curses.window.addch
  3. [
  4. x: int
  5. X-coordinate.
  6. y: int
  7. Y-coordinate.
  8. ]
  9. ch: object
  10. Character to add.
  11. [
  12. attr: long
  13. Attributes for the character.
  14. ]
  15. /
  16. ...

注:

  • 每一個可選組,都會額外傳入一個代表分組的參數(shù)。 參數(shù)為 int 型,名為 group_{direction}_{number},其中 {direction} 取決于此參數(shù)組位于必需參數(shù) right 還是 left,而 {number} 是一個遞增數(shù)字(從 1 開始),表示此參數(shù)組與必需參數(shù)之間的距離。 在調(diào)用函數(shù)時,若未用到此參數(shù)組則此參數(shù)將設為零,若用到了參數(shù)組則該參數(shù)為非零。 所謂的用到或未用到,是指在本次調(diào)用中形參是否收到了實參。

  • 如果不存在必需參數(shù),可選組的行為等同于出現(xiàn)在必需參數(shù)的右側(cè)。

  • 在模棱兩可的情況下,參數(shù)解析代碼更傾向于參數(shù)左側(cè)(在必需參數(shù)之前)。

  • 可選組只能包含只認位置的參數(shù)。

  • 可選組 僅限 用于過時代碼。請勿在新的代碼中使用可選組。

采用真正的 Argument Clinic 轉(zhuǎn)換器,而不是 “傳統(tǒng)轉(zhuǎn)換器”

為了節(jié)省時間,盡量減少要學習的內(nèi)容,實現(xiàn)第一次適用 Argument Clinic 的移植,上述練習簡述的是“傳統(tǒng)轉(zhuǎn)換器”的用法?!皞鹘y(tǒng)轉(zhuǎn)換器”只是一種簡便用法,目的就是更容易地讓現(xiàn)有代碼移植為適用于 Argument Clinic 。說白了,在移植 Python 3.4 的代碼時,可以考慮采用。

不過從長遠來看,可能希望所有代碼塊都采用真正的 Argument Clinic 轉(zhuǎn)換器語法。原因如下:

  • 合適的轉(zhuǎn)換器可讀性更好,意圖也更清晰。

  • 有些格式單元是“傳統(tǒng)轉(zhuǎn)換器”無法支持的,因為這些格式需要帶上參數(shù),而傳統(tǒng)轉(zhuǎn)換器的語法不支持指定參數(shù)。

  • 后續(xù)可能會有新版的參數(shù)解析庫,提供超過 PyArg_ParseTuple() 支持的功能;而這種靈活性將無法適用于傳統(tǒng)轉(zhuǎn)換器轉(zhuǎn)換的參數(shù)。

因此,若是不介意多花一點精力,請使用正常的轉(zhuǎn)換器,而不是傳統(tǒng)轉(zhuǎn)換器。

簡而言之,Argument Clinic(非傳統(tǒng))轉(zhuǎn)換器的語法看起來像是 Python 函數(shù)調(diào)用。但如果函數(shù)沒有明確的參數(shù)(所有函數(shù)都取默認值),則可以省略括號。因此 boolbool() 是完全相同的轉(zhuǎn)換器。

Argument Clinic 轉(zhuǎn)換器的所有參數(shù)都只認關(guān)鍵字。所有 Argument Clinic 轉(zhuǎn)換器均可接受以下參數(shù):

c_default

該參數(shù)在 C 語言中的默認值。具體來說,將是在“解析函數(shù)”中聲明的變量的初始化器。用法參見 the section on default values 。定義為字符串。

annotation

參數(shù)的注解值。目前尚不支持,因為 PEP 8 規(guī)定 Python 庫不得使用注解。

此外,某些轉(zhuǎn)換器還可接受額外的參數(shù)。下面列出了這些額外參數(shù)及其含義:

accept

一些 Python 類型的集合(可能還有偽類型);用于限制只接受這些類型的 Python 參數(shù)。(并非通用特性;只支持傳統(tǒng)轉(zhuǎn)換器列表中給出的類型)。

若要能接受 None,請在集合中添加 NoneType。

bitwise

僅用于無符號整數(shù)。寫入形參的將是 Python 實參的原生整數(shù)值,不做任何越界檢查,即便是負值也一樣。

converter

僅用于 object 轉(zhuǎn)換器。為某個 C 轉(zhuǎn)換函數(shù) 指定名稱,用于將對象轉(zhuǎn)換為原生類型。

encoding

僅用于字符串。指定將 Python str(Unicode) 轉(zhuǎn)換為 C 語言的 char * 時應該采用的編碼。

subclass_of

僅用于 object 轉(zhuǎn)換器。要求 Python 值是 Python 類型的子類,用 C 語言表示。

type

僅用于 objectself 轉(zhuǎn)換器。指定用于聲明變量的 C 類型。 默認值是 "PyObject *"。

zeroes

僅用于字符串。如果為 True,則允許在值中嵌入 NUL 字節(jié)('\\0')。字符串的長度將通過名為 _length 的參數(shù)傳入,跟在字符串參數(shù)的后面。

請注意,并不是所有參數(shù)的組合都能正常生效。通常這些參數(shù)是由相應的 PyArg_ParseTuple 格式單元 實現(xiàn)的,行為是固定的。比如目前不能不指定 bitwise=True 就去調(diào)用 unsigned_short。雖然完全有理由認為這樣可行,但這些語義并沒有映射到任何現(xiàn)有的格式單元。所以 Argument Clinic 并不支持。(或者說,至少目前還不支持。)

下表列出了傳統(tǒng)轉(zhuǎn)換器與真正的 Argument Clinic 轉(zhuǎn)換器之間的映射關(guān)系。左邊是傳統(tǒng)的轉(zhuǎn)換器,右邊是應該換成的文本。

‘B’

unsigned_char(bitwise=True)

‘b’

unsigned_char

‘c’

char

‘C’

int(accept={str})

‘d’

double

‘D’

Py_complex

‘es’

str(encoding=’name_of_encoding’)

‘es#’

str(encoding=’name_of_encoding’, zeroes=True)

‘et’

str(encoding=’name_of_encoding’, accept={bytes, bytearray, str})

‘et#’

str(encoding=’name_of_encoding’, accept={bytes, bytearray, str}, zeroes=True)

‘f’

float

‘h’

short

‘H’

unsigned_short(bitwise=True)

‘i’

int

‘I’

unsigned_int(bitwise=True)

‘k’

unsigned_long(bitwise=True)

‘K’

unsigned_long_long(bitwise=True)

‘l’

long

‘L’

long long

‘n’

Py_ssize_t

‘O’

object

‘O!’

object(subclass_of=’&PySomething_Type’)

‘O&’

object(converter=’name_of_c_function’)

‘p’

bool

‘S’

PyBytesObject

‘s’

str

‘s#’

str(zeroes=True)

‘s

Py_buffer(accept={buffer, str})

‘U’

unicode

‘u’

Py_UNICODE

‘u#’

Py_UNICODE(zeroes=True)

‘w‘

Py_buffer(accept={rwbuffer})

‘Y’

PyByteArrayObject

‘y’

str(accept={bytes})

‘y#’

str(accept={robuffer}, zeroes=True)

‘y

Py_buffer

‘Z’

Py_UNICODE(accept={str, NoneType})

‘Z#’

Py_UNICODE(accept={str, NoneType}, zeroes=True)

‘z’

str(accept={str, NoneType})

‘z#’

str(accept={str, NoneType}, zeroes=True)

‘z‘

Py_buffer(accept={buffer, str, NoneType})

舉個例子,下面是采用合適的轉(zhuǎn)換器的例子 pickle.Pickler.dump

 
 
 
 
  1. /*[clinic input]
  2. pickle.Pickler.dump
  3. obj: object
  4. The object to be pickled.
  5. /
  6. Write a pickled representation of obj to the open file.
  7. [clinic start generated code]*/

真正的轉(zhuǎn)換器有一個優(yōu)點,就是比傳統(tǒng)的轉(zhuǎn)換器更加靈活。例如,unsigned_int 轉(zhuǎn)換器(以及所有 unsigned_ 轉(zhuǎn)換器)可以不設置 bitwise=True 。 他們默認會對數(shù)值進行范圍檢查,而且不會接受負數(shù)。 用傳統(tǒng)轉(zhuǎn)換器就做不到這一點。

Argument Clinic 會列明其全部轉(zhuǎn)換器。每個轉(zhuǎn)換器都會給出可接受的全部參數(shù),以及每個參數(shù)的默認值。只要運行 Tools/clinic/clinic.py --converters 就能得到完整的列表。

Py_buffer

在使用 Py_buffer 轉(zhuǎn)換器(或者 's*'、'w*'、'*y''z*' 傳統(tǒng)轉(zhuǎn)換器)時,不可 在所提供的緩沖區(qū)上調(diào)用 PyBuffer_Release()。 Argument Clinic 生成的代碼會自動完成此操作(在解析函數(shù)中)。

高級轉(zhuǎn)換器

還記得編寫第一個函數(shù)時跳過的那些格式單元嗎,因為他們是高級內(nèi)容?下面就來介紹這些內(nèi)容。

其實訣竅在于,這些格式單元都需要給出參數(shù)——要么是轉(zhuǎn)換函數(shù),要么是類型,要么是指定編碼的字符串。(但 “傳統(tǒng)轉(zhuǎn)換器”不支持參數(shù)。這就是為什么第一個函數(shù)要跳過這些內(nèi)容)。為格式單元指定的參數(shù)于是就成了轉(zhuǎn)換器的參數(shù);參數(shù)可以是 converter``(對于 ``O&)、subclass_of``(對于 ``O!),或者是 encoding (對于 e 開頭的格式單元)。

在使用 subclass_of 時,可能還需要用到 object() 的另一個自定義參數(shù):type,用于設置參數(shù)的實際類型。例如,為了確保對象是 PyUnicode_Type 的子類,可能想采用轉(zhuǎn)換器 object(type='PyUnicodeObject *', subclass_of='&PyUnicode_Type')

Argument Clinic 用起來可能存在一個問題:喪失了 e 開頭的格式單位的一些靈活性。在手工編寫 PyArg_Parse 調(diào)用時,理論上可以在運行時決定傳給 PyArg_ParseTuple() 的編碼字符串。但現(xiàn)在這個字符串必須在 Argument-Clinic 預處理時進行硬編碼。這個限制是故意設置的;以便簡化對這種格式單元的支持,并允許以后進行優(yōu)化。這個限制似乎并不合理;CPython 本身總是為 e 開頭的格式單位參數(shù)傳入靜態(tài)的硬編碼字符串。

參數(shù)的默認值

參數(shù)的默認值可以是多個值中的一個。最簡單的可以是字符串、int 或 float 字面量。

 
 
 
 
  1. foo: str = "abc"
  2. bar: int = 123
  3. bat: float = 45.6

還可以使用 Python 的任何內(nèi)置常量。

 
 
 
 
  1. yep: bool = True
  2. nope: bool = False
  3. nada: object = None

對默認值 NULL 和簡單表達式還提供特別的支持,下面將一一介紹。

默認值 NULL

對于字符串和對象參數(shù)而言,可以設為 None,表示沒有默認值。但這意味著會將 C 變量初始化為 Py_None。為了方便起見,提供了一個特殊值``NULL``,目的就是為了讓 Python 認為默認值就是 None,而 C 變量則會初始化為 NULL。

設為默認值的表達式

參數(shù)的默認值不僅可以是字面量。還可以是一個完整的表達式,可采用數(shù)學運算符及對象的屬性。但這種支持并沒有那么簡單,因為存在一些不明顯的語義。

請考慮以下例子:

 
 
 
 
  1. foo: Py_ssize_t = sys.maxsize - 1

sys.maxsize 在不同的系統(tǒng)平臺可能有不同的值。因此,Argument Clinic 不能簡單地在本底環(huán)境對表達式求值并用 C 語言硬編碼。所以默認值將用表達式的方式存儲下來,運行的時候在請求函數(shù)簽名時會被求值。

在對表達式進行求值時,可以使用什么命名空間呢?求值過程運行于內(nèi)置模塊的上下文中。 因此,如果模塊帶有名為 max_widgets 的屬性,直接引用即可。

 
 
 
 
  1. foo: Py_ssize_t = max_widgets

如果表達式不在當前模塊中,就會去 sys.modules 查找。比如 sys.maxsize 就是如此找到的。(因為事先不知道用戶會加載哪些模塊到解釋器中,所以最好只用到 Python 會預加載的模塊。)

僅當運行時才對缺省值求值,意味著 Argument Clinic 無法計算出正確的 C 缺省值。所以需顯式給出。在使用表達式時,必須同時用轉(zhuǎn)換器的``c_default`` 參數(shù)指定 C 語言中的等價表達式。

 
 
 
 
  1. foo: Py_ssize_t(c_default="PY_SSIZE_T_MAX - 1") = sys.maxsize - 1

還有一個問題也比較復雜。Argument Clinic 無法事先知道表達式是否有效。 解析只能保證看起來是有效值,但無法 實際 知曉。在用表達式時須十分小心,確保在運行時能得到有效值。

最后一點,由于表達式必須能表示為靜態(tài)的 C 語言值,所以存在許多限制。 以下列出了不得使用的 Python 特性:

  • 功能

  • 行內(nèi) if 語句(3 if foo else 5

  • 序列類自動解包(*[1, 2, 3]

  • 列表、集合、字典的解析和生成器表達式。

  • 元組、列表、集合、字典的字面量

返回值轉(zhuǎn)換器

Argument Clinic 生成的植入函數(shù)默認會返回 PyObject *。但是通常 C 函數(shù)的任務是要對某些 C 類型進行計算,然后將其轉(zhuǎn)換為 PyObject * 作為結(jié)果。Argument Clinic 可以將輸入?yún)?shù)由 Python 類型轉(zhuǎn)換為本地 C 類型——為什么不讓它將返回值由本地 C 類型轉(zhuǎn)換為 Python 類型呢?

這就是“返回值轉(zhuǎn)換器”的用途。它將植入函數(shù)修改成返回某種 C 語言類型,然后在生成的(非植入)函數(shù)中添加代碼,以便將 C 語言值轉(zhuǎn)換為合適的 PyObject *。

返回值轉(zhuǎn)換器的語法與參數(shù)轉(zhuǎn)換器的類似。返回值轉(zhuǎn)換器的定義方式,類似于函數(shù)返回值的注解。返回值轉(zhuǎn)換器的行為與參數(shù)轉(zhuǎn)換器基本相同,接受參數(shù),參數(shù)只認關(guān)鍵字,如果不修改默認參數(shù)則可省略括號。

(如果函數(shù)同時用到了 "as" 和返回值轉(zhuǎn)換器, "as" 應位于返回值轉(zhuǎn)換器之前。)

返回值轉(zhuǎn)換器還存在一個復雜的問題:出錯信息如何表示?通常函數(shù)在執(zhí)行成功時會返回一個有效(非 NULL)指針,失敗則返回 NULL。但如果使用了整數(shù)的返回值轉(zhuǎn)換器,所有整數(shù)都是有效值。Argument Clinic 怎么檢測錯誤呢?解決方案是:返回值轉(zhuǎn)換器會隱含尋找一個代表錯誤的特殊值。如果返回該特殊值,且設置了出錯標記( PyErr_Occurred() 返回 True),那么生成的代碼會傳遞該錯誤。否則,會對返回值進行正常編碼。

目前 Argument Clinic 只支持少數(shù)幾種返回值轉(zhuǎn)換器。

 
 
 
 
  1. bool
  2. int
  3. unsigned int
  4. long
  5. unsigned int
  6. size_t
  7. Py_ssize_t
  8. float
  9. double
  10. DecodeFSDefault

這些轉(zhuǎn)換器都不需要參數(shù)。前3個轉(zhuǎn)換器如果返回 -1 則表示出錯。DecodeFSDefault 的返回值類型是 const char *;若返回 NULL 指針則表示出錯。

(還有一個 NoneType 轉(zhuǎn)換器是實驗性質(zhì)的,成功時返回 Py_None ,失敗則返回 NULL,且不會增加 Py_None 的引用計數(shù)。此轉(zhuǎn)換器是否值得適用,尚不明確)。

只要運行 Tools/clinic/clinic.py --converters ,即可查看 Argument Clinic 支持的所有返回值轉(zhuǎn)換器,包括其參數(shù)。

克隆已有的函數(shù)

如果已有一些函數(shù)比較相似,或許可以采用 Clinic 的“克隆”功能。 克隆之后能夠復用以下內(nèi)容:

  • 參數(shù),包括:

    • 名稱

    • 轉(zhuǎn)換器(帶有全部參數(shù))

    • 默認值

    • 參數(shù)前的文檔字符串

    • 類別 (只認位置、位置或關(guān)鍵字、只認關(guān)鍵字)

  • 返回值轉(zhuǎn)換器

唯一不從原函數(shù)中復制的是文檔字符串;這樣就能指定一個新的文檔串。

下面是函數(shù)的克隆方法:

 
 
 
 
  1. /*[clinic input]
  2. module.class.new_function [as c_basename] = module.class.existing_function
  3. Docstring for new_function goes here.
  4. [clinic start generated code]*/

(原函數(shù)可以位于不同的模塊或類中。示例中的 module.class 只是為了說明,兩個 函數(shù)都必須使用全路徑)。

Sorry, there’s no syntax for partially cloning a function, or cloning a function then modifying it. Cloning is an all-or nothing proposition.

另外,要克隆的函數(shù)必須在當前文件中已有定義。

調(diào)用 Python 代碼

下面的高級內(nèi)容需要編寫 Python 代碼,存于 C 文件中,并修改 Argument Clinic 的運行狀態(tài)。其實很簡單:只需定義一個 Python 塊。

Python 塊的分隔線與 Argument Clinic 函數(shù)塊不同。如下所示:

 
 
 
 
  1. /*[python input]
  2. # python code goes here
  3. [python start generated code]*/

Python 塊內(nèi)的所有代碼都會在解析時執(zhí)行。塊內(nèi)寫入 stdout 的所有文本都被重定向到塊后的“輸出”部分。

以下例子包含了 Python 塊,用于在 C 代碼中添加一個靜態(tài)整數(shù)變量:

 
 
 
 
  1. /*[python input]
  2. print('static int __ignored_unused_variable__ = 0;')
  3. [python start generated code]*/
  4. static int __ignored_unused_variable__ = 0;
  5. /*[python checksum:...]*/

self 轉(zhuǎn)換器的用法

Argument Clinic 用一個默認的轉(zhuǎn)換器自動添加一個“self”參數(shù)。自動將 self 參數(shù)的 type 設為聲明類型時指定的“指向?qū)嵗闹羔槨?。不過 Argument Clinic 的轉(zhuǎn)換器可被覆蓋,也即自己指定一個轉(zhuǎn)換器。只要將自己的 self 參數(shù)作為塊的第一個參數(shù)即可,并確保其轉(zhuǎn)換器是 self_converter 的實例或其子類。

這有什么用呢?可用于覆蓋 self 的類型,或為其給個不同的默認名稱。

如何指定 self 對應的自定義類型呢?如果只有 self 類型相同的一兩個函數(shù),可以直接使用 Argument Clinic 現(xiàn)有的 self 轉(zhuǎn)換器,把要用的類型作為 type 參數(shù)傳入:

 
 
 
 
  1. /*[clinic input]
  2. _pickle.Pickler.dump
  3. self: self(type="PicklerObject *")
  4. obj: object
  5. /
  6. Write a pickled representation of the given object to the open file.
  7. [clinic start generated code]*/

如果有很多函數(shù)將使用同一類型的 self,則最好創(chuàng)建自己的轉(zhuǎn)換器,繼承自 self_converter 類但要覆蓋其 type 成員:

 
 
 
 
  1. /*[python input]
  2. class PicklerObject_converter(self_converter):
  3. type = "PicklerObject *"
  4. [python start generated code]*/
  5. /*[clinic input]
  6. _pickle.Pickler.dump
  7. self: PicklerObject
  8. obj: object
  9. /
  10. Write a pickled representation of the given object to the open file.
  11. [clinic start generated code]*/

“定義類”轉(zhuǎn)換器

Argument Clinic 為訪問方法定義所在的類提供了便利。因為 heap type 方法需要獲取模塊級的運行狀態(tài),所以就十分有用。PyType_FromModuleAndSpec() 會將堆類型與模塊關(guān)聯(lián)起來。然后類就可用 PyType_GetModuleState() 獲取模塊狀態(tài)了,比如利用模塊的方法進行獲取。

示例來自 Modules/zlibmodule.c。首先,在 clinic 的輸入塊添加 defining_class

 
 
 
 
  1. /*[clinic input]
  2. zlib.Compress.compress
  3. cls: defining_class
  4. data: Py_buffer
  5. Binary data to be compressed.
  6. /

運行 Argument Clinic 工具后,會生成以下函數(shù)簽名:

 
 
 
 
  1. /*[clinic start generated code]*/
  2. static PyObject *
  3. zlib_Compress_compress_impl(compobject *self, PyTypeObject *cls,
  4. Py_buffer *data)
  5. /*[clinic end generated code: output=6731b3f0ff357ca6 input=04d00f65ab01d260]*/

現(xiàn)在,以下代碼可以用 PyType_GetModuleState(cls) 獲取模塊狀態(tài)了:

 
 
 
 
  1. zlibstate *state = PyType_GetModuleState(cls);

每個方法只能有一個參數(shù)用到轉(zhuǎn)換器,且須位于 self 之后 ,若未用到 self 則為第一個參數(shù)。該參數(shù)的類型為 PyTypeObject *。__text_signature__ 中不會包含該參數(shù)。

defining_class 轉(zhuǎn)換器與 __init____new__ 方法不兼容,他們不能使用 METH_METHOD 。

It is not possible to use defining_class with slot methods. In order to fetch the module state from such methods, use PyType_GetModuleByDef() to look up the module and then PyModule_GetState() to fetch the module state. Example from the setattro slot method in Modules/_threadmodule.c:

 
 
 
 
  1. static int
  2. local_setattro(localobject *self, PyObject *name, PyObject *v)
  3. {
  4. PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &thread_module);
  5. thread_module_state *state = get_thread_state(module);
  6. ...
  7. }

參見 PEP 573。

編寫自定義轉(zhuǎn)換器

上一節(jié)中已有提及……可以編寫自己的轉(zhuǎn)換器!轉(zhuǎn)換器就是一個繼承自``CConverter`` 的 Python 類。假如有個參數(shù)采用了 O& 格式,對此參數(shù)進行解析就會去調(diào)用某個“轉(zhuǎn)換器函數(shù)” PyArg_ParseTuple() ,也就會用到自定義轉(zhuǎn)換器。

自定義轉(zhuǎn)換器類應命名為 *something*_converter。只要按此規(guī)則命名,自定義轉(zhuǎn)換器類就會在 Argument Clinic 中自動注冊;轉(zhuǎn)換器的名稱就是去除了 _converter 后綴的類名。(通過元類完成)。

不得由 CConverter.__init__ 派生子類。而應編寫一個 converter_init() 函數(shù)。converter_init() 必須能接受一個 self 參數(shù);所有后續(xù)的其他參數(shù) 必須 是只認關(guān)鍵字的參數(shù)。傳給 Argument Clinic 轉(zhuǎn)換器的所有參數(shù)都會傳入自定義 converter_init() 函數(shù)。

CConverter 的其他一些成員,可能需要在自定義子類中定義。下面列出了目前的成員:

type

變量要采用的 C 語言數(shù)據(jù)類型。type 應為 int 之類的 Python 字符串,用于指定變量的類型。若為指針類型,則字符串應以 ' *' 結(jié)尾。

default

該參數(shù)的缺省值,為 Python 數(shù)據(jù)類型。若無缺省值,則為 unspecified。

<
分享標題:創(chuàng)新互聯(lián)Python教程:ArgumentClinic的用法
文章路徑:http://m.5511xx.com/article/djoeshp.html