PEP 727 – 带注释的元数据中的文档

猫勺猫勺 03-22 135 阅读 0 评论

抽象

PEP 提出了一种为 Python 提供文档字符串的标准化方法 使用 new 类定义的符号。Annotatedtyping.Doc

赋予动机

已经有一种定义明确的方式来为类提供文档, 函数、类方法和模块:使用 DocStrings。

目前还没有正式的标准来为其他文档字符串提供文档字符串 符号类型:参数、返回值、类范围变量(类变量 和实例变量)、局部变量和类型别名。

然而,为了允许记录这些附加符号中的大多数,几个 约定已创建为文档字符串中的微语法,并且是 目前常用:狮身人面像、numpydoc、Google、Keras等。

在两种情况下,工具将支持这些约定: 作者,在编辑文档字符串的内容时,对于用户, 同时以某种方式呈现该内容(在文档网站、工具提示中 在编辑器中等)。

由于这些约定中的每一个都使用字符串中的微语法,因此在编辑这些文档字符串时,编辑器无法轻松提供对自动完成的支持, 语法损坏等的内联错误。任何类型的编辑支持 约定将建立在对编辑标准 Python 语法的支持之上。

使用当前约定记录参数时,因为文档字符串位于 代码中的位置与实际参数不同,它需要 重复的信息(参数名称)有关的信息 参数很容易位于代码中离 声明实际参数,并且它与它断开连接。 这意味着很容易重构函数、删除参数并忘记 删除其文档。添加新参数时也会发生同样的情况:很容易忘记 为其添加文档字符串。

并且由于信息(参数名称)的重复,编辑器和 其他工具需要复杂的自定义逻辑来检查或确保 参数,或者它们根本没有 完全支持这一点。

由于这些现有约定是 字符串,稳健地解析它们以进行渲染需要复杂的逻辑 需要由支持它们的工具实现。此外,图书馆 而且工具没有一种直接的方法来获取文档 每个单独的参数或变量在运行时,不依赖于 特定的 Docstring 约定解析器。访问参数文档 运行时的字符串会很有用,例如,用于测试内容 每个参数的文档,以确保多个参数的一致性 类似的函数,或者提取和公开相同的参数 以其他方式进行文档记录(例如,带有 FastAPI 的 API、带有 Typer 的 CLI 等)。

其中一些以前的格式试图解释缺少类型注释的问题 在较旧的 Python 版本中,通过在文档字符串中包含键入信息(例如 Sphinx、numpydoc) 但是现在这些信息不需要在文档字符串中,因为现在有类型注释的官方语法。

理由

该提案旨在通过扩展和补充 文档字符串中的信息,保持与现有文档字符串的向后兼容性 (它不会弃用它们),并以利用 Python 的方式进行 语言和结构,通过带有 和 的类型注释 中的新类。AnnotatedDoctyping

这属于标准 Python 库而不是 外部包是因为虽然实现会很琐碎, 它的实际力量和好处将来自成为一个标准,以促进 库作者的用法,并提供记录 Python 的默认方式 符号使用 .一些工具提供程序(至少是 VS Code 和 PyCharm)已经表明,只有在以下情况下,他们才会考虑实现对此的支持 这是一个标准。Annotated

这不会弃用文档字符串的当前用法,应考虑文档字符串 首选文档方法(如果可用)(在类型别名中不可用, 参数等)。 文档字符串将通过该提案作为补充,该提案专门针对 可以声明的符号(目前仅由几种可用的微语法约定涵盖)。Annotated

这对普通开发人员(库用户)来说应该是相对透明的,除非 他们手动打开采用它的库的源文件。

对于想要采用它的图书馆作者来说,它应该被视为选择加入,并且 他们应该可以自由决定是否使用它。

它仅对愿意使用可选类型提示的库有用。

总结

以下是该提案与当前提案相比的功能的简短摘要 约定:

  • 默认情况下,任何编辑器都已经完全支持编辑(当前 或将来)支持 Python 语法,包括语法错误、语法 突出显示等

  • 通过静态工具实现渲染将相对简单 (不需要运行时执行的工具),因为可以提取信息 从他们通常已经创建的 AST。

  • 重复数据删除:参数的名称将在单个 放置,而不是在文档字符串中重复。

  • 消除了删除参数时出现不一致的可能性 或类变量而忘记删除其文档。

  • 最小化添加新参数或类变量的概率 并忘记添加其文档。

  • 消除名称不一致的可能性 参数和重命名时文档字符串中的名称。

  • 在运行时访问每个符号的文档字符串,包括现有的 (较旧)Python 版本。

  • 一种更正式的方式来记录其他符号,例如类型别名,可以 用。Annotated

  • 对于新手来说,没有微语法需要学习,它只是 Python 语法。

  • 捕获函数的参数文档继承 由 .

规范

主要建议是引入一个新类。 此类只能在批注中使用。 它采用单个仅位置字符串参数。它应该用于 记录使用 声明的符号的预期含义和用途。typing.DocAnnotatedAnnotated

例如:

from typing import Annotated, Docclass User:
    name: Annotated[str, Doc("The user's name")]
    age: Annotated[int, Doc("The user's age")]

    ...

Annotated通常用作类型注释,在这些情况下, 它里面的任何内容都会记录被注释的符号。typing.Doc

当用于声明类型别名时,将记录类型别名符号。Annotatedtyping.Doc

例如:

from typing import Annotated, Doc, TypeAlias
from external_library import UserResolver
CurrentUser: TypeAlias = Annotated[str, Doc("The current system user"), UserResolver()]
def create_user(name: Annotated[str, Doc("The user's name")]): ...
def delete_user(name: Annotated[str, Doc("The user to delete")]): ...

在这种情况下,如果用户导入 ,编辑器等工具可以提供 当用户将鼠标悬停在该符号上时,带有文档字符串的工具提示,或者 文档工具可以包含类型别名及其文档 生成的输出。CurrentUser

对于在运行时提取信息的工具,它们通常会与参数一起使用 , 并且按照规范化(即使使用类型别名),这 这意味着他们应该使用最后一个可用的,如果不止一个 使用,因为这是最后一个使用的。include_extras=TrueAnnotatedtyping.Doc

在运行时,实例具有一个属性,其中包含 传递给它的字符串。typing.Docdocumentation

当函数的签名被 捕获时, 应保留与参数关联的任何文档字符串。

任何工具处理对象都应将字符串解释为 文档字符串,因此应规范化空格 好像被使用过一样。typing.Docinspect.cleandoc()

传递给的字符串应采用以下形式: 有效的文档字符串。 这意味着不应使用 f 字符串和字符串操作。 由于 Python 运行时无法强制执行此操作, 工具不应依赖于此行为。typing.Doc

当提供渲染的工具显示原始签名时,它们可以允许 配置是否应显示整个原始代码, 但它们应该默认不包括及其 内部代码元数据,仅注释符号的类型。当这些工具 以其他方式支持和渲染,而不仅仅是原始签名, 它们应该以一种方便的方式显示传递给的字符串值 显示记录的符号与文档字符串之间的关系。AnnotatedAnnotatedtyping.Doctyping.Doc

提供渲染的工具可以允许配置显示 参数文档和散文文档以不同的方式进行。否则,他们 可以简单地先显示散文文档字符串,然后再显示参数文档。

例子

类属性可以记录在案:

from typing import Annotated, Docclass User:
    name: Annotated[str, Doc("The user's name")]
    age: Annotated[int, Doc("The user's age")]

    ...

函数或方法参数和返回值也可以:

from typing import Annotated, Doc
def create_user(
    name: Annotated[str, Doc("The user's name")],
    age: Annotated[int, Doc("The user's age")],
    cursor: DatabaseConnection | None = None,
    ) -> Annotated[User, Doc("The created user after saving in the database")]:
        """Create a new user in the system.    
        It needs the database connection to be already initialized.    """
    pass

向后兼容性

该提案与现有代码完全向后兼容,但事实并非如此 弃用 Docstring 约定的现有用法。

对于希望在标准库中提供之前采用它的开发人员, 或者为了支持旧版本的 Python,他们可以使用和 从那里导入和使用。typing_extensionsDoc

例如:

from typing import Annotated
from typing_extensions import Doc
class User:
    name: Annotated[str, Doc("The user's name")]
    age: Annotated[int, Doc("The user's age")]

    ...

安全隐患

没有已知的安全隐患。

如何教这个

文档的主要机制应继续是 散文信息,这适用于模块、类、函数和方法。

对于想要采用此提案以增加更多粒度的作者,他们可以在符号的注释中使用内部 支持它。typing.DocAnnotated

希望在保持向后兼容性的同时采用此提案的图书馆作者 使用旧版本的 Python 应该使用而不是 .typing_extensions.Doctyping.Doc

    

参考实现

typing.Doc等效地实现:

class Doc:    
        def __init__(self, documentation: str, /):
            self.documentation = documentation

它已在 typing_extensions 包中实现。

其他语言调查

以下是对其他语言如何记录其符号的简短调查。

java岛

Java 函数及其参数记录在 Javadoc 中, 一种特殊的注释格式,用于放置在函数定义之上的注释。这将是 类似于 Python 当前的文档字符串微语法约定(但只有一个)。

例如:

/**
* Returns an Image object that can then be painted on the screen.
* The url argument must specify an absolute <a href="#{@link}">{@link URL}</a>. The name
* argument is a specifier that is relative to the url argument.
* <p>
* This method always returns immediately, whether or not the
* image exists. When this applet attempts to draw the image on
* the screen, the data will be loaded. The graphics primitives
* that draw the image will incrementally paint on the screen.
*
* @param  url  an absolute URL giving the base location of the image
* @param  name the location of the image, relative to the url argument
* @return      the image at the specified URL
* @see         Image
*/
public Image getImage(URL url, String name) {
  try {
      return getImage(new URL(url, name));  }
       catch (MalformedURLException e) {
           return null;
             }
}

JavaScript的

JavaScript 和 TypeScript 都使用与 Javadoc 类似的系统。

JavaScript 使用 JSDoc。

例如:

/**
* Represents a book.
* @constructor
* @param {string} title - The title of the book.
* @param {string} author - The author of the book.
*/
function Book(title, author) {
}

TypeScript(打字稿)

TypeScript 有自己的 JSDoc 引用,但有一些变体。

例如:

// Parameters may be declared in a variety of syntactic forms
/**
* @param {string}  p1 - A string param.
* @param {string=} p2 - An optional param (Google Closure syntax)
* @param {string} [p3] - Another optional param (JSDoc syntax).
* @param {string} [p4="test"] - An optional param with a default value
* @returns {string} This is the result
*/
function stringsStringStrings(p1, p2, p3, p4) {
    // TODO
}

Rust

Rust 在 Doc 注释中使用了另一种类似的微语法变体。

但它没有一个特别明确的微语法结构来表示什么 文档是指除了可以推断的符号/参数之外的符号/参数 纯粹的 Markdown。

例如:

#![crate_name = "doc"]

/// A human being is represented here
pub struct Person {
   /// A person must have a name, no matter how much Juliet may hate it
   name: String,
   }impl Person {
      /// Returns a person with the name given them   
      ///   
      /// # Arguments   
      ///   
      /// * `name` - A string slice that holds the name of the person   
      ///   
      /// # Examples   
      ///   
      /// ```   
      /// // You can have rust code between fences inside the comments   
      /// // If you pass --test to `rustdoc`, it will even test it for you!   
      /// use doc::Person;   /// let person = Person::new("name");   
      /// ```   
      pub fn new(name: &str) -> Person {      
         Person {            
                 name: name.to_string(),      
         }   
      }   
      
         /// Gives a friendly hello!   
         ///   
         /// Says "Hello, [name](Person::name)" to the `Person` it is called on.   
         pub fn hello(& self) {      
             println!("Hello, {}!", self.name);
            }
         }
         fn main() {   
         let john = Person::new("John");   
         john.hello();
         }

戈朗

Go 还使用一种 Doc Comments 形式。

它没有一个明确定义的微语法结构来表示什么文档 指哪个符号/参数,但参数可以按名称引用,而没有 任何特殊的语法或标记,这也意味着可能出现的普通单词 在文档中,应避免将文本作为参数名称。

package strconv
// Quote returns a double-quoted Go string literal representing s.
// The returned string uses Go escape sequences (\t, \n, \xFF, \u0100)
// for control characters and non-printable characters as defined by IsPrint.
func Quote(s string) string {   
    ...
}

被拒绝的想法

标准化当前文档字符串

一个可能的替代方案是支持并尝试将 现有文档字符串格式。但这只能解决标准化问题。

它不会解决因在内部使用微语法而产生的任何其他问题 文档字符串而不是纯 Python 语法,与上面描述的相同 基本原理 - 摘要。

额外的元数据和装饰器

在此提案之前的一些想法包括使用函数而不是 具有多个参数的单个类,用于指示是否 不鼓励使用对象,可能会引起哪些例外情况等。 为了允许弃用函数和类,也希望这样做 可以用作装饰器。但此功能已涵盖 在 PEP 702 中,因此它从该提案中删除。doc()Docdoc()typing.deprecated()

声明其他信息的方法将来可能仍然有用, 但是对这个想法的早期反馈,所有这些都被推迟到未来 建议。

这也将重点从具有多个参数的包罗万象的函数转移到一个可以与其他参数组合的单个类 未来的提案。doc()DocAnnotated

这种设计更改还允许与其他提案具有更好的互操作性 就像 ,将来可以考虑 允许同时弃用 单个参数,与 共存。typing.deprecated()typing.deprecated()AnnotatedDoc

定义下的字符串

讨论中提出的替代方案是在定义下声明一个字符串 并提供对这些值的运行时访问:

class User:
    name: str
    "The user's name"
    age: int
    "The user's age"

    ...

这在 PEP 224 中已经提出并被拒绝,主要是由于 字符串如何与它所记录的符号连接。

此外,在以前的值中,无法提供对此值的运行时访问 Python 的版本。

    

带注释的纯字符串

在讨论中,还建议在以下内容中使用普通字符串:Annotated

from typing import Annotatedclass User:
    name: Annotated[str, "The user's name"]
    age: Annotated[int, "The user's age"]

    ...

但这将为 中的任何纯字符串以及其中使用纯字符串的任何工具创建预定义的含义 用于目前允许的任何其他目的现在都是无效的。Annotated

具有显式使其与 的当前有效用法兼容。typing.DocAnnotated

另一种类似注释的类型

在讨论中,有人建议定义一个类似于 的新类型,它将采用类型和参数 文档字符串:Annotated

from typing import Docclass User:
    name: Doc[str, "The user's name"]
    age: Doc[int, "The user's age"]

    ...

这个想法被拒绝了,因为它只会支持该用例,并且会使其更加 难以将其与其他目的结合使用( 例如,使用 FastAPI 元数据、Pydantic 字段等)或添加其他元数据 除了文档字符串(例如弃用)。Annotated

从类型别名传输文档

该提案的早期版本规定,当使用声明的类型别名时,这些类型别名用于 注释,文档字符串将被转移到注释符号。Annotated

例如:

from typing import Annotated, Doc, TypeAlias

UserName: TypeAlias = Annotated[str, Doc("The user's name")]

def create_user(name: UserName): ...
def delete_user(name: UserName): ...

在收到其中一个主要维护者的反馈后,这被拒绝了 用于提供编辑器支持的组件。

切片速记

在讨论中,有人建议使用切片的速记:

is_approved: Annotated[str: "The status of a PEP."]

虽然这是一个非常聪明的想法,并且会消除对新类的需求, 运行时执行当前版本的 Python 不允许这样做。Doc

在运行时,至少需要两个参数,并且它 要求第一个参数是 type,如果它是切片,它会崩溃。Annotated

未解决的问题

冗长

反对这一点的主要论点是冗长程度的增加。

如果签名不是独立于文档和正文查看的 还测量了带有文档字符串的功能,总详细程度为 有点相似,因为该提案所做的是移动一些内容 从正文中的文档字符串到签名。

仅考虑签名,如果没有正文,它们可能比 它们目前是,它们最终可能超过一页长。作为交换, 当前长度超过一页的等效文档字符串将是 短得多。

在比较总详细程度(包括签名和文档字符串)时, 由此增加的主要额外冗长将来自使用 和 。如果有更多的用法,那么为它和 它将携带的元数据类型。但这只有在被更广泛地使用时才有意义。Annotatedtyping.DocAnnotatedAnnotated

另一方面,这种详细程度不会影响最终用户,因为他们不会看到 使用 .大多数用户会与 通过编辑器进行库,而不看内部结构,如果有的话,它们 将获得支持此提案的编辑的工具提示。typing.Doc

处理额外冗长内容的费用将主要由承担 由使用此功能的库维护者提供。

此参数可能类似于反对类型注释的参数 一般来说,因为它们确实增加了冗长,以换取他们的 特征。但同样,与类型注释一样,这将是可选的,并且仅 供那些愿意承担额外冗长作为交换的人使用 为了好处。

当然,更高级的用户可能想要查看库的源代码 如果这些库的作者采用了这一点,那么这些高级用户最终将 必须以额外的签名冗长而不是文档字符串来查看该代码 冗长。

任何决定不采用它的作者都应该可以自由地继续使用文档字符串 使用他们决定的任何特定格式,根本没有文档字符串,等等。

尽管如此,图书馆作者很有可能会受到压力 如果它成为有福的解决方案,请采用它。

文档不是打字

也可以说,文档并不是打字的真正部分,或者 它应该位于不同的模块中。或者这些信息不应该是其中的一部分 的签名,但存在于另一个地方(如文档字符串)。

尽管如此,默认情况下,已经可以考虑 Python 中的类型注解了, 附加元数据:它们携带有关变量的附加信息, 参数、返回类型,默认情况下,它们没有任何运行时行为。和 该提案将向它们添加另一种类型的元数据。

可以说,该提案扩展了以下信息类型: 类型注释携带,与 PEP 702 扩展它们以包含的方式相同 弃用信息。

Annotated被精确地添加到标准库中,以精确地 支持向注解添加额外的元数据,并且由于新提议的类与 紧密耦合,它使 感觉它存在于同一个模块中。如果被移动了 到另一个模块,与它一起移动是有意义的。DocAnnotatedAnnotatedDoc

多种标准

反对这一点的另一个论点是,它将创建另一个标准, 并且已经有几种文档字符串的约定。它可以 似乎最好将当前现有的标准之一正式化。

然而,如上所述,这些公约中没有一项涵盖一般 基于文档的方法的缺点,该提案自然解决了。

要查看基于文档字符串的方法的缺点列表,请参阅上面的部分 在基本原理 - 摘要中。

同样,可以看出,在许多情况下,一个新标准 利用新功能并解决以前的几个问题 方法可能值得拥有。与新的 、 、 新的打字管道/联合 () 运算符和其他情况一样。pyproject.tomldataclass_transform|

采用

由于这是一个新的标准提案,因此只有具有 来自社区的兴趣。

幸运的是,已经有几家主流图书馆对此感兴趣 来自多个开发人员和团队,包括 FastAPI、Typer、SQLModel、 Asyncer(来自本提案的作者)、Pydantic、Strawberry (GraphQL) 和 别人。

还有来自文档工具的兴趣和支持,比如 mkdocstrings,它添加了 甚至支持此提案的早期版本。

所有联系以获得早期反馈的 CPython 核心开发人员(至少 4 人)都有 对这一提议表示了兴趣和支持。

编辑器开发人员(VS Code 和 PyCharm)表现出了一些兴趣,同时显示 对提案的签名冗长表示担忧,尽管不关注 实施(这是对他们影响最大的)。他们已经表明 如果它成为 官方标准。在这种情况下,他们只需要添加对 渲染,作为对编辑的支持,这通常不存在 其他标准,已经存在,因为它们已经支持编辑标准 Python 语法。

版权

本文档被置于公共领域或位于 CC0-1.0-通用许可证,以更宽松的许可证为准。

The End 微信扫一扫

文章声明:以上内容(如有图片或视频在内)除非注明,否则均为腾龙猫勺儿原创文章,转载或复制请以超链接形式并注明出处。

本文作者:猫勺本文链接:https://www.jo6.cn/post/72.html

上一篇 下一篇

相关阅读

发表评论

访客 访客
快捷回复: 表情:
评论列表 (暂无评论,135人围观)

还没有评论,来说两句吧...

取消
微信二维码
微信二维码
支付宝二维码