# torch.package > 原文:[`pytorch.org/docs/stable/package.html`](https://pytorch.org/docs/stable/package.html) `torch.package` 支持创建包含工件和任意 PyTorch 代码的包。这些包可以保存、共享,用于在以后的日期或不同机器上加载和执行模型,甚至可以使用`torch::deploy`部署到生产环境。 本文包含教程、指南、解释和 API 参考,将帮助您更多地了解`torch.package`及如何使用它。 警告 这个模块依赖于不安全的`pickle`模块。只有解包您信任的数据。 可能构造恶意的 pickle 数据,将在解包时执行任意代码。永远不要解包可能来自不受信任来源或可能被篡改的数据。 有关`pickle`模块的更多信息,请查看[文档](https://docs.python.org/3/library/pickle.html)。 + 教程 + 打包您的第一个模型 + 我该如何... + 查看包内部内容? + 查看为什么某个模块被包含为依赖? + 如何在我的包中包含任意资源并以后访问它们? + 自定义类如何打包? + 在我的源代码中测试是否在包内执行? + 将代码打补丁到包中? + 从打包的代码中访问包内容? + 区分打包代码和非打包代码? + 重新导出导入的对象? + 打包一个 TorchScript 模块? + 解释 + `torch.package` 格式概述 + `torch.package` 如何找到您代码的依赖 + 依赖管理 + `torch.package` 的尖锐边缘 + `torch.package` 如何保持包之间的隔离 + API 参考 ## 教程 ### 打包您的第一个模型 提供了一个指导您打包和解包简单模型的教程[在 Colab 上](https://colab.research.google.com/drive/1lFZkLyViGfXxB-m3jqlyTQuYToo3XLo-)。完成这个练习后,您将熟悉用于创建和使用 Torch 包的基本 API。 ## 我该如何... ### 查看包内部内容? #### 将包像 ZIP 存档一样处理 `torch.package`的容器格式是 ZIP,因此任何可以处理标准 ZIP 文件的工具都可以用于探索内容。与 ZIP 文件交互的一些常见方法: + `unzip my_package.pt` 将`torch.package`存档解压到磁盘上,您可以自由检查其内容。 ```py $ unzip my_package.pt && tree my_package my_package ├── .data │ ├── 94304870911616.storage │ ├── 94304900784016.storage │ ├── extern_modules │ └── version ├── models │ └── model_1.pkl └── torchvision └── models ├── resnet.py └── utils.py ~ cd my_package && cat torchvision/models/resnet.py ... ``` + Python 的`zipfile`模块提供了一种标准的方法来读取和写入 ZIP 存档内容。 ```py from zipfile import ZipFile with ZipFile("my_package.pt") as myzip: file_bytes = myzip.read("torchvision/models/resnet.py") # edit file_bytes in some way myzip.writestr("torchvision/models/resnet.py", new_file_bytes) ``` + vim 有本地读取 ZIP 存档的能力。您甚至可以编辑文件并将其`:write`回存档中! ```py # add this to your .vimrc to treat `*.pt` files as zip files au BufReadCmd *.pt call zip#Browse(expand("")) ~ vi my_package.pt ``` #### 使用`file_structure()` API `PackageImporter` 提供了一个 `file_structure()` 方法,它将返回一个可打印和可查询的 `Directory` 对象。`Directory` 对象是一个简单的目录结构,您可以用它来探索 `torch.package` 的当前内容。 `Directory` 对象本身是可以直接打印的,将打印出一个文件树表示。要过滤返回的内容,使用类似 glob 的 `include` 和 `exclude` 过滤参数。 ```py with PackageExporter('my_package.pt') as pe: pe.save_pickle('models', 'model_1.pkl', mod) importer = PackageImporter('my_package.pt') # can limit printed items with include/exclude args print(importer.file_structure(include=["**/utils.py", "**/*.pkl"], exclude="**/*.storage")) print(importer.file_structure()) # will print out all files ``` 输出: ```py # filtered with glob pattern: # include=["**/utils.py", "**/*.pkl"], exclude="**/*.storage" ─── my_package.pt ├── models │ └── model_1.pkl └── torchvision └── models └── utils.py # all files ─── my_package.pt ├── .data │ ├── 94304870911616.storage │ ├── 94304900784016.storage │ ├── extern_modules │ └── version ├── models │ └── model_1.pkl └── torchvision └── models ├── resnet.py └── utils.py ``` 您还可以使用 `has_file()` 方法查询 `Directory` 对象。 ```py importer_file_structure = importer.file_structure() found: bool = importer_file_structure.has_file("package_a/subpackage.py") ``` ### 查看为什么一个给定模块被包含为一个依赖项?[](#see-why-a-given-module-was-included-as-a-dependency "Permalink to this heading") 假设有一个给定的模块 `foo`,您想知道为什么您的 `PackageExporter` 将 `foo` 作为一个依赖项引入。 `PackageExporter.get_rdeps()` 将返回所有直接依赖于 `foo` 的模块。 如果您想查看给定模块 `src` 如何依赖于 `foo`,`PackageExporter.all_paths()` 方法将返回一个以 DOT 格式显示的图形,显示 `src` 和 `foo` 之间的所有依赖路径。 如果您只想查看您的 `PackageExporter` 的整个依赖图,您可以使用 `PackageExporter.dependency_graph_string()`。 ### 如何在我的包中包含任意资源并以后访问它们?[](#include-arbitrary-resources-with-my-package-and-access-them-later "Permalink to this heading") `PackageExporter` 提供了三种方法,`save_pickle`、`save_text` 和 `save_binary`,允许您将 Python 对象、文本和二进制数据保存到一个包中。 ```py with torch.PackageExporter("package.pt") as exporter: # Pickles the object and saves to `my_resources/tensor.pkl` in the archive. exporter.save_pickle("my_resources", "tensor.pkl", torch.randn(4)) exporter.save_text("config_stuff", "words.txt", "a sample string") exporter.save_binary("raw_data", "binary", my_bytes) ``` `PackageImporter` 提供了名为 `load_pickle`、`load_text` 和 `load_binary` 的补充方法,允许您从一个包中加载 Python 对象、文本和二进制数据。 ```py importer = torch.PackageImporter("package.pt") my_tensor = importer.load_pickle("my_resources", "tensor.pkl") text = importer.load_text("config_stuff", "words.txt") binary = importer.load_binary("raw_data", "binary") ``` ### 如何自定义类的打包方式?[](#customize-how-a-class-is-packaged "Permalink to this heading") `torch.package` 允许自定义类的打包方式。通过在一个类上定义方法 `__reduce_package__` 并定义相应的解包函数来访问这种行为。这类似于为 Python 的普通 pickling 过程定义 `__reduce__`。 步骤: 1. 在目标类上定义方法 `__reduce_package__(self, exporter: PackageExporter)`。这个方法应该完成将类实例保存在包中的工作,并应该返回一个元组,其中包含相应的解包函数以及调用解包函数所需的参数。当 `PackageExporter` 遇到目标类的实例时,会调用这个方法。 1. 为这个类定义一个解包函数。这个解包函数应该完成重建并返回类实例的工作。函数签名的第一个参数应该是一个 `PackageImporter` 实例,其余参数由用户定义。 ```py # foo.py [Example of customizing how class Foo is packaged] from torch.package import PackageExporter, PackageImporter import time class Foo: def __init__(self, my_string: str): super().__init__() self.my_string = my_string self.time_imported = 0 self.time_exported = 0 def __reduce_package__(self, exporter: PackageExporter): """ Called by ``torch.package.PackageExporter``'s Pickler's ``persistent_id`` when saving an instance of this object. This method should do the work to save this object inside of the ``torch.package`` archive. Returns function w/ arguments to load the object from a ``torch.package.PackageImporter``'s Pickler's ``persistent_load`` function. """ # use this pattern to ensure no naming conflicts with normal dependencies, # anything saved under this module name shouldn't conflict with other # items in the package generated_module_name = f"foo-generated._{exporter.get_unique_id()}" exporter.save_text( generated_module_name, "foo.txt", self.my_string + ", with exporter modification!", ) time_exported = time.clock_gettime(1) # returns de-packaging function w/ arguments to invoke with return (unpackage_foo, (generated_module_name, time_exported,)) def unpackage_foo( importer: PackageImporter, generated_module_name: str, time_exported: float ) -> Foo: """ Called by ``torch.package.PackageImporter``'s Pickler's ``persistent_load`` function when depickling a Foo object. Performs work of loading and returning a Foo instance from a ``torch.package`` archive. """ time_imported = time.clock_gettime(1) foo = Foo(importer.load_text(generated_module_name, "foo.txt")) foo.time_imported = time_imported foo.time_exported = time_exported return foo ``` ```py # example of saving instances of class Foo import torch from torch.package import PackageImporter, PackageExporter import foo foo_1 = foo.Foo("foo_1 initial string") foo_2 = foo.Foo("foo_2 initial string") with PackageExporter('foo_package.pt') as pe: # save as normal, no extra work necessary pe.save_pickle('foo_collection', 'foo1.pkl', foo_1) pe.save_pickle('foo_collection', 'foo2.pkl', foo_2) pi = PackageImporter('foo_package.pt') print(pi.file_structure()) imported_foo = pi.load_pickle('foo_collection', 'foo1.pkl') print(f"foo_1 string: '{imported_foo.my_string}'") print(f"foo_1 export time: {imported_foo.time_exported}") print(f"foo_1 import time: {imported_foo.time_imported}") ``` ```py # output of running above script ─── foo_package ├── foo-generated │ ├── _0 │ │ └── foo.txt │ └── _1 │ └── foo.txt ├── foo_collection │ ├── foo1.pkl │ └── foo2.pkl └── foo.py foo_1 string: 'foo_1 initial string, with reduction modification!' foo_1 export time: 9857706.650140837 foo_1 import time: 9857706.652698385 ``` ### 在我的源代码中测试是否正在包内执行?[](#test-in-my-source-code-whether-or-not-it-is-executing-inside-a-package "Permalink to this heading") `PackageImporter`将在初始化的每个模块上添加属性`__torch_package__`。您的代码可以检查此属性的存在来确定它是否在打包上下文中执行。 ```py # In foo/bar.py: if "__torch_package__" in dir(): # true if the code is being loaded from a package def is_in_package(): return True UserException = Exception else: def is_in_package(): return False UserException = UnpackageableException ``` 现在,代码的行为将取决于它是通过 Python 环境正常导入还是从`torch.package`导入。 ```py from foo.bar import is_in_package print(is_in_package()) # False loaded_module = PackageImporter(my_package).import_module("foo.bar") loaded_module.is_in_package() # True ``` **警告**:通常,根据代码是打包还是未打包而表现不同是不好的实践。这可能导致难以调试的问题,这些问题对您导入代码的方式很敏感。如果您的包打算被广泛使用,请考虑重新组织您的代码,使其无论如何加载都表现相同。 ### 将代码打补丁到包中?[](#patch-code-into-a-package "跳转到此标题的永久链接") `PackageExporter`提供了一个`save_source_string()`方法,允许将任意 Python 源代码保存到您选择的模块中。 ```py with PackageExporter(f) as exporter: # Save the my_module.foo available in your current Python environment. exporter.save_module("my_module.foo") # This saves the provided string to my_module/foo.py in the package archive. # It will override the my_module.foo that was previously saved. exporter.save_source_string("my_module.foo", textwrap.dedent( """\ def my_function(): print('hello world') """ )) # If you want to treat my_module.bar as a package # (e.g. save to `my_module/bar/__init__.py` instead of `my_module/bar.py) # pass is_package=True, exporter.save_source_string("my_module.bar", "def foo(): print('hello')\n", is_package=True) importer = PackageImporter(f) importer.import_module("my_module.foo").my_function() # prints 'hello world' ``` ### 从打包代码中访问包内容?[](#access-package-contents-from-packaged-code "跳转到此标题的永久链接") `PackageImporter`实现了[importlib.resources](https://docs.python.org/3/library/importlib.html#module-importlib.resources) API,用于从包内访问资源。 ```py with PackageExporter(f) as exporter: # saves text to my_resource/a.txt in the archive exporter.save_text("my_resource", "a.txt", "hello world!") # saves the tensor to my_pickle/obj.pkl exporter.save_pickle("my_pickle", "obj.pkl", torch.ones(2, 2)) # see below for module contents exporter.save_module("foo") exporter.save_module("bar") ``` `importlib.resources` API 允许从打包代码中访问资源。 ```py # foo.py: import importlib.resources import my_resource # returns "hello world!" def get_my_resource(): return importlib.resources.read_text(my_resource, "a.txt") ``` 使用`importlib.resources`是从打包代码中访问包内容的推荐方式,因为它符合 Python 标准。但是,也可以从打包代码中访问父`PackageImporter`实例本身。 ```py # bar.py: import torch_package_importer # this is the PackageImporter that imported this module. # Prints "hello world!", equivalent to importlib.resources.read_text def get_my_resource(): return torch_package_importer.load_text("my_resource", "a.txt") # You also do things that the importlib.resources API does not support, like loading # a pickled object from the package. def get_my_pickle(): return torch_package_importer.load_pickle("my_pickle", "obj.pkl") ``` ### 区分打包代码和非打包代码?[](#distinguish-between-packaged-code-and-non-packaged-code "跳转到此标题的永久链接") 要确定对象的代码是否来自`torch.package`,请使用`torch.package.is_from_package()`函数。注意:如果对象来自包但其定义来自标记为`extern`或`stdlib`的模块,此检查将返回`False`。 ```py importer = PackageImporter(f) mod = importer.import_module('foo') obj = importer.load_pickle('model', 'model.pkl') txt = importer.load_text('text', 'my_test.txt') assert is_from_package(mod) assert is_from_package(obj) assert not is_from_package(txt) # str is from stdlib, so this will return False ``` ### 重新导出已导入的对象?[](#re-export-an-imported-object "跳转到此标题的永久链接") 重新导出之前由`PackageImporter`导入的对象,您必须让新的`PackageExporter`知道原始的`PackageImporter`,以便它可以找到您对象的依赖项的源代码。 ```py importer = PackageImporter(f) obj = importer.load_pickle("model", "model.pkl") # re-export obj in a new package with PackageExporter(f2, importer=(importer, sys_importer)) as exporter: exporter.save_pickle("model", "model.pkl", obj) ``` ### 打包 TorchScript 模块?[](#package-a-torchscript-module "跳转到此标题的永久链接") 要打包 TorchScript 模型,请使用与任何其他对象相同的`save_pickle`和`load_pickle` API。支持保存属性或子模块的 TorchScript 对象,无需额外工作。 ```py # save TorchScript just like any other object with PackageExporter(file_name) as e: e.save_pickle("res", "script_model.pkl", scripted_model) e.save_pickle("res", "mixed_model.pkl", python_model_with_scripted_submodule) # load as normal importer = PackageImporter(file_name) loaded_script = importer.load_pickle("res", "script_model.pkl") loaded_mixed = importer.load_pickle("res", "mixed_model.pkl" ``` ## 解释 ### `torch.package`格式概述[](#torch-package-format-overview "跳转到此标题的永久链接") `torch.package`文件是一个 ZIP 存档,通常使用`.pt`扩展名。在 ZIP 存档中,有两种类型的文件: + 框架文件,放置在`.data/`中。 + 用户文件,即其他所有文件。 例如,这是一个来自`torchvision`的完全打包的 ResNet 模型的样子: ```py resnet ├── .data # All framework-specific data is stored here. │ │ # It's named to avoid conflicts with user-serialized code. │ ├── 94286146172688.storage # tensor data │ ├── 94286146172784.storage │ ├── extern_modules # text file with names of extern modules (e.g. 'torch') │ ├── version # version metadata │ ├── ... ├── model # the pickled model │ └── model.pkl └── torchvision # all code dependencies are captured as source files └── models ├── resnet.py └── utils.py ``` #### 框架文件 `.data/`目录由 torch.package 拥有,其内容被视为私有实现细节。`torch.package`格式不对`.data/`的内容做任何保证,但任何更改都将向后兼容(即,新版本的 PyTorch 始终能够加载旧的`torch.packages`)。 目前,`.data/`目录包含以下项目: + `version`:序列化格式的版本号,以便`torch.package`导入基础设施知道如何加载此包。 + `extern_modules`:一个被视为`extern`的模块列表。`extern`模块将使用加载环境的系统导入器进行导入。 + `*.storage`:序列化的张量数据。 ```py .data ├── 94286146172688.storage ├── 94286146172784.storage ├── extern_modules ├── version ├── ... ``` #### 用户文件 存档中的所有其他文件都是用户放置的。布局与 Python[常规包](https://docs.python.org/3/reference/import.html#regular-packages)相同。要深入了解 Python 打包的工作原理,请参考[这篇文章](https://www.python.org/doc/essays/packages/)(它略有过时,因此请通过[Python 参考文档](https://docs.python.org/3/library/importlib.html)双重检查实现细节)。 ```py ├── model # the pickled model │ └── model.pkl ├── another_package │ ├── __init__.py │ ├── foo.txt # a resource file , see importlib.resources │ └── ... └── torchvision └── models ├── resnet.py # torchvision.models.resnet └── utils.py # torchvision.models.utils ``` ### 如何`torch.package`找到您代码的依赖项[](#how-torch-package-finds-your-code-s-dependencies "跳转到此标题") #### 分析对象的依赖项[](#analyzing-an-object-s-dependencies "跳转到此标题") 当您发出`save_pickle(obj, ...)`调用时,`PackageExporter`将正常对对象进行 pickle。然后,它使用`pickletools`标准库模块来解析 pickle 字节码。 在 pickle 中,对象与描述对象类型实现位置的`GLOBAL`操作码一起保存,例如: ```py GLOBAL 'torchvision.models.resnet Resnet` ``` 依赖解析器将收集所有`GLOBAL`操作,并将它们标记为您的 pickled 对象的依赖项。有关 pickling 和 pickle 格式的更多信息,请参考[Python 文档](https://docs.python.org/3/library/pickle.html)。 #### 分析模块的依赖项[](#analyzing-a-module-s-dependencies "跳转到此标题") 当 Python 模块被识别为依赖项时,`torch.package`会遍历模块的 Python AST 表示,并查找具有标准形式的导入语句的支持:`from x import y`,`import z`,`from w import v as u`等。当遇到这些导入语句之一时,`torch.package`会将导入的模块注册为依赖项,然后以相同的 AST 遍历方式解析它们自己。 **注意**:AST 解析对于`__import__(...)`语法有限支持,并且不支持`importlib.import_module`调用。一般来说,您不应该期望`torch.package`能够检测到动态导入。 ### 依赖管理[](#dependency-management "跳转到此标题") `torch.package`会自动找到您的代码和对象依赖的 Python 模块。这个过程称为依赖解析。对于依赖解析器找到的每个模块,您必须指定要执行的*操作*。 允许的操作是: + `intern`:将此模块放入包中。 + `extern`:将此模块声明为包的外部依赖项。 + `mock`:模拟此模块。 + `deny`:依赖于此模块将在包导出期间引发错误。 最后,还有一个重要的操作不是技术上`torch.package`的一部分: + 重构:删除或更改代码中的依赖项。 请注意,操作仅在整个 Python 模块上定义。无法仅打包模块中的“只是”函数或类并将其余部分留出。这是有意设计的。Python 没有为模块中定义的对象提供清晰的边界。依赖组织的唯一定义单元是模块,因此`torch.package`使用它。 使用模式对模块应用操作。模式可以是模块名称(`"foo.bar"`)或通配符(如`"foo.**"`)。您可以使用`PackageExporter`上的方法将模式与操作关联起来,例如: ```py my_exporter.intern("torchvision.**") my_exporter.extern("numpy") ``` 如果模块与模式匹配,则将对其应用相应的操作。对于给定的模块,将按照定义的顺序检查模式,并采取第一个操作。 #### `intern` 如果一个模块被`intern`,它将被放入包中。 这个操作是您的模型代码,或者您想要打包的任何相关代码。例如,如果您正在尝试从`torchvision`打包一个 ResNet,您将需要`intern`模块 torchvision.models.resnet。 在包导入时,当您的打包代码尝试导入一个`intern`-ed 模块时,PackageImporter 将在您的包内查找该模块。如果找不到该模块,将引发错误。这确保了每个`PackageImporter`与加载环境隔离——即使您的包和加载环境中都有`my_interned_module`,`PackageImporter`也只会使用您包中的版本。 **注意**:只有 Python 源模块可以被`intern`。其他类型的模块,如 C 扩展模块和字节码模块,如果您尝试`intern`它们,将引发错误。这些类型的模块需要被`mock`或`extern`。 #### `extern` 如果一个模块被`extern`,它将不会被打包。相反,它将被添加到此包的外部依赖项列表中。您可以在`package_exporter.extern_modules`中找到此列表。 在包导入时,当打包的代码尝试导入一个`extern`-ed 模块时,`PackageImporter`将使用默认的 Python 导入器来查找该模块,就好像您执行了`importlib.import_module("my_externed_module")`。如果找不到该模块,将引发错误。 通过这种方式,您可以依赖于第三方库,如`numpy`和`scipy`,而无需将它们也打包。 **警告**:如果任何外部库以不兼容的方式更改,您的包可能无法加载。如果您需要长期的包可重现性,请尽量限制对`extern`的使用。 #### `mock` 如果一个模块被`mock`,它将不会被打包。相反,将会打包一个存根模块。存根模块将允许您从中检索对象(因此`from my_mocked_module import foo`不会出错),但对该对象的任何使用将引发`NotImplementedError`。 `mock`应该用于您“知道”在加载的包中不会需要的代码,但您仍希望在非打包内容中可用。例如,初始化/配置代码,或仅用于调试/训练的代码。 **警告**:一般来说,`mock`应该作为最后的手段使用。它会引入打包代码和非打包代码之间的行为差异,可能会导致后续混淆。相反,最好重构您的代码以删除不需要的依赖项。 #### 重构 管理依赖项的最佳方法是根本不要有依赖项!通常,代码可以重构以删除不必要的依赖项。以下是编写具有干净依赖项的代码的一些指导原则(这些也通常是良好的实践!): **只包含你使用的内容**。不要在你的代码中留下未使用的导入。依赖解析器不够智能,无法判断它们是否确实未使用,并将尝试处理它们。 **限定您的导入**。例如,不要写 import foo 然后在后面使用`foo.bar.baz`,最好写`from foo.bar import baz`。这更精确地指定了您的真实依赖项(`foo.bar`),并让依赖解析器知道您不需要`foo`的全部内容。 **将具有无关功能的大文件拆分为较小的文件**。如果您的`utils`模块包含各种无关功能,任何依赖于`utils`的模块都将需要引入大量无关的依赖项,即使您只需要其中的一小部分。相反,最好定义单一用途的模块,可以独立打包。 #### 模式 模式允许您使用方便的语法指定模块组。模式的语法和行为遵循 Bazel/Buck [glob()](https://docs.bazel.build/versions/master/be/functions.html#glob)。 我们正在尝试匹配的模块称为候选模块。候选模块由一系列由分隔符字符串分隔的段组成,例如`foo.bar.baz`。 模式包含一个或多个段。段可以是: + 文字字符串(例如`foo`),精确匹配。 + 包含通配符的字符串(例如`torch`或`foo*baz*`)。通配符匹配任何字符串,包括空字符串。 + 双通配符(`**`)。这与零个或多个完整段匹配。 示例: + `torch.**`:匹配`torch`及其所有子模块,例如`torch.nn`和`torch.nn.functional`。 + `torch.*`:匹配`torch.nn`或`torch.functional`,但不匹配`torch.nn.functional`或`torch` + `torch*.**`:匹配`torch`、`torchvision`和它们的所有子模块 在指定操作时,可以传递多个模式,例如。 ```py exporter.intern(["torchvision.models.**", "torchvision.utils.**"]) ``` 如果模块与任何模式匹配,它将匹配此操作。 您还可以指定要排除的模式,例如。 ```py exporter.mock("**", exclude=["torchvision.**"]) ``` 如果匹配任何排除模式,模块将不匹配此操作。在此示例中,我们模拟所有模块,除了`torchvision`及其子模块。 当一个模块可能匹配多个操作时,将采取首先定义的操作。 ### `torch.package`尖锐边缘[](#torch-package-sharp-edges "跳转到此标题") #### 避免在您的模块中使用全局状态[](#avoid-global-state-in-your-modules "跳转到此标题") Python 使绑定对象和在模块级别范围运行代码变得非常容易。这通常没问题——毕竟,函数和类是以这种方式绑定到名称的。但是,当您在模块范围定义一个对象以进行突变时,引入可变全局状态时,情况会变得更加复杂。 可变全局状态非常有用——它可以减少样板文件,允许向表中开放注册等。但是,除非非常小心地使用,否则在与`torch.package`一起使用时可能会引起复杂性。 每个`PackageImporter`为其内容创建一个独立的环境。这很好,因为这意味着我们加载多个包并确保它们彼此隔离,但是当模块以假定共享可变全局状态的方式编写时,此行为可能会导致难以调试的错误。 #### 类型不在包之间共享,并且加载环境[](#types-are-not-shared-between-packages-and-the-loading-environment "跳转到此标题") 从`PackageImporter`导入的任何类都将是特定于该导入器的类的版本。例如: ```py from foo import MyClass my_class_instance = MyClass() with PackageExporter(f) as exporter: exporter.save_module("foo") importer = PackageImporter(f) imported_MyClass = importer.import_module("foo").MyClass assert isinstance(my_class_instance, MyClass) # works assert isinstance(my_class_instance, imported_MyClass) # ERROR! ``` 在此示例中,`MyClass`和`imported_MyClass`不是*相同类型*。在这个特定示例中,`MyClass`和`imported_MyClass`具有完全相同的实现,因此您可能认为可以将它们视为相同的类。但是请考虑`imported_MyClass`来自具有完全不同`MyClass`实现的旧包的情况——在这种情况下,将它们视为相同类是不安全的。 在幕后,每个导入器都有一个前缀,允许它唯一识别类: ```py print(MyClass.__name__) # prints "foo.MyClass" print(imported_MyClass.__name__) # prints .foo.MyClass ``` 这意味着当一个参数来自一个包而另一个参数不是时,您不应该期望`isinstance`检查能够工作。如果需要此功能,请考虑以下选项: + 进行鸭子类型(只使用类而不是明确检查它是给定类型)。 + 使类型关系成为类合同的显式部分。例如,您可以添加一个属性标签`self.handler = "handle_me_this_way"`,并让客户端代码检查`handler`的值而不是直接检查类型。 ### 如何使`torch.package`中的包相互隔离[](#how-torch-package-keeps-packages-isolated-from-each-other "跳转到此标题的永久链接") 每个`PackageImporter`实例为其模块和对象创建一个独立的、隔离的环境。包中的模块只能导入其他打包的模块,或标记为`extern`的模块。如果您使用多个`PackageImporter`实例来加载单个包,您将获得多个独立的环境,它们不会相互交互。 这是通过使用自定义导入器扩展 Python 的导入基础设施来实现的。`PackageImporter`提供与`importlib`导入器相同的核心 API;即,它实现了`import_module`和`__import__`方法。 当调用`PackageImporter.import_module()`时,`PackageImporter`将构建并返回一个新模块,就像系统导入器一样。但是,`PackageImporter`会修补返回的模块,以使用`self`(即`PackageImporter`实例)来满足未来的导入请求,通过在包中查找而不是搜索用户的 Python 环境。 #### 混淆 为了避免混淆(“这个`foo.bar`对象是来自我的包还是来自我的 Python 环境?”),`PackageImporter`通过为导入的所有模块添加*混淆前缀*来篡改它们的`__name__`和`__file__`。 对于`__name__`,类似`torchvision.models.resnet18`的名称变为`.torchvision.models.resnet18`。 对于`__file__`,类似`torchvision/models/resnet18.py`的名称变为`.torchvision/modules/resnet18.py`。 名称混淆有助于避免不同包之间模块名称的无意间双关,并通过使堆栈跟踪和打印语句更清晰地显示它们是指向打包代码还是其他内容来帮助您调试。有关混淆的面向开发人员的详细信息,请参阅`torch/package/`中的`mangling.md`。 ## API 参考 ```py class torch.package.PackagingError(dependency_graph, debug=False) ``` 在导出包时出现问题时会引发此异常。`PackageExporter`将尝试收集所有错误并一次性呈现给您。 ```py class torch.package.EmptyMatchError ``` 当在打包过程中遇到将 mock 或 extern 标记为`allow_empty=False`,并且在打包期间未匹配到任何模块时,将引发此异常。 ```py class torch.package.PackageExporter(f, importer=, debug=False) ``` 导出器允许您将代码包、Python 数据、以及任意二进制和文本资源写入一个独立的包中。 导入可以以封闭的方式加载代码,使代码从包中加载而不是从正常的 Python 导入系统加载。这允许打包 PyTorch 模型代码和数据,以便在服务器上运行或将来用于迁移学习。 当创建包时,包中包含的代码是从原始源文件逐个文件复制的,文件格式是一个特别组织的压缩文件。包的未来用户可以解压包,并编辑代码以执行自定义修改。 包导入器确保模块中的代码只能从包内部加载,除非明确列出为外部模块使用`extern()`。压缩文件中的`extern_modules`列出了包在外部依赖的所有模块。这可以防止“隐式”依赖,即因为导入了本地安装的包而在本地运行,但在将包复制到另一台机器时失败。 当源代码添加到包中时,导出器可以选择扫描它以获取更多的代码依赖项(`dependencies=True`)。它查找导入语句,解析相对引用以获取限定模块名称,并执行用户指定的操作(参见:`extern()`,`mock()`和`intern()`). ```py __init__(f, importer=, debug=False) ``` 创建一个导出器。 参数 + **f** ([*Union*](https://docs.python.org/3/library/typing.html#typing.Union "(in Python v3.12)")*[*[*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")*,* [*Path*](https://docs.python.org/3/library/pathlib.html#pathlib.Path "(in Python v3.12)")*,* [*BinaryIO*](https://docs.python.org/3/library/typing.html#typing.BinaryIO "(in Python v3.12)")*]*) – 导出位置。可以是包含文件名的`string`/`Path`对象,也可以是二进制 I/O 对象。 + **importer** ([*Union*](https://docs.python.org/3/library/typing.html#typing.Union "(in Python v3.12)")*[**Importer**,* [*Sequence*](https://docs.python.org/3/library/typing.html#typing.Sequence "(in Python v3.12)")*[**Importer**]**]*) – 如果传递了单个 Importer,则使用该 Importer 搜索模块。如果传递了一系列 importers,将从中构建一个`OrderedImporter`。 + **debug** ([*bool*](https://docs.python.org/3/library/functions.html#bool "(in Python v3.12)")) – 如果设置为 True,则将损坏模块的路径添加到 PackagingErrors 中。 ```py add_dependency(module_name, dependencies=True) ``` 给定一个模块,根据用户指定的模式将其添加到依赖图中。 ```py all_paths(src, dst) ``` 返回子图的点表示 从 src 到 dst 的所有路径。 返回 包含从 src 到 dst 的所有路径的点表示。([`graphviz.org/doc/info/lang.html`](https://graphviz.org/doc/info/lang.html)) 返回类型 [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)") ```py close() ``` 将包写入文件系统。`close()`之后的任何调用现在都是无效的。最好使用资源保护语法: ```py with PackageExporter("file.zip") as e: ... ``` ```py denied_modules() ``` 返回当前被拒绝的所有模块。 返回 包含将在此包中被拒绝的模块名称的列表。 返回类型 [*List*](https://docs.python.org/3/library/typing.html#typing.List "(in Python v3.12)")[[str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")] ```py deny(include, *, exclude=()) ``` 从包可以导入的模块列表中阻止与给定 glob 模式匹配的模块名称。如果找到任何匹配的包的依赖项,将引发`PackagingError`。 参数 + **include** (*Union**[**List**[*[*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")*]**,* [*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")*]*) – 一个字符串,例如 `"my_package.my_subpackage"`,或者字符串列表,用于指定要外部化的模块的名称。这也可以是一个类似于 glob 的模式,如`mock()`中所述。 + **exclude** (*Union**[**List**[*[*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")*]**,* [*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")*]*) – 一个可选模式,用于排除与包含字符串匹配的某些模式。 ```py dependency_graph_string() ``` 返回包中依赖项的双字母字符串表示。 返回 包中依赖项的字符串表示。 返回类型 [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)") ```py extern(include, *, exclude=(), allow_empty=True) ``` 将`module`包含在包可以导入的外部模块列表中。这将阻止依赖项发现将其保存在包中。导入程序将直接从标准导入系统加载外部模块。外部模块的代码也必须存在于加载包的进程中。 参数 + **包括**(*Union**[**List**[*[*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")*]**,* [*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")*])- 一个字符串,例如`"my_package.my_subpackage"`,或者是要导出的模块的名称列表。这也可以是一个类似 glob 的模式,如`mock()`中所述。 + **排除**(*Union**[**List**[*[*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")*]**,* [*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")*])- 一个可选模式,排除一些与包含字符串匹配的模式。 + **allow_empty**([*bool*](https://docs.python.org/3/library/functions.html#bool "(在 Python v3.12 中)"))- 一个可选标志,指定此调用`extern`方法指定的外部模块是否必须在打包过程中与某个模块匹配。如果添加了一个`allow_empty=False`的外部模块 glob 模式,并且在任何模块匹配该模式之前调用了`close()`(显式调用或通过`__exit__`),则会抛出异常。如果`allow_empty=True`,则不会抛出此类异常。 ```py externed_modules() ``` 返回当前所有已经 externed 的模块。 返回 包含在此包中将被 externed 的模块的名称列表。 返回类型 [*List*](https://docs.python.org/3/library/typing.html#typing.List "(在 Python v3.12 中)")[[str](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")] ```py get_rdeps(module_name) ``` 返回一个依赖于模块`module_name`的所有模块的列表。 返回 包含依赖于`module_name`的模块的名称列表。 返回类型 [*List*](https://docs.python.org/3/library/typing.html#typing.List "(在 Python v3.12 中)")[[str](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")] ```py get_unique_id() ``` 获取一个 id。此 id 保证仅在此包中分配一次。 返回类型 [str](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)") ```py intern(include, *, exclude=(), allow_empty=True) ``` 指定应该打包的模块。模块必须匹配一些`intern`模式才能包含在包中,并且其依赖项会被递归处理。 参数 + **包括**(*Union**[**List**[*[*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")*]**,* [*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")*])- 一个字符串,例如“my_package.my_subpackage”,或者是要导出的模块的名称列表。这也可以是一个类似 glob 的模式,如`mock()`中所述。 + **排除**(*Union**[**List**[*[*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")*]**,* [*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")*])- 一个可选模式,排除一些与包含字符串匹配的模式。 + **allow_empty**([*bool*](https://docs.python.org/3/library/functions.html#bool "(在 Python v3.12 中)"))- 一个可选标志,指定此调用`intern`方法指定的 intern 模块是否必须在打包过程中与某个模块匹配。如果添加了一个`allow_empty=False`的`intern`模块 glob 模式,并且在任何模块匹配该模式之前调用了`close()`(显式调用或通过`__exit__`),则会抛出异常。如果`allow_empty=True`,则不会抛出此类异常。 ```py interned_modules() ``` 返回当前所有已经 interned 的模块。 返回 包含将在此软件包中 intern 的模块名称的列表。 返回类型 [*List*](https://docs.python.org/3/library/typing.html#typing.List "(in Python v3.12)")[[str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")] ```py mock(include, *, exclude=(), allow_empty=True) ``` 用模拟实现替换一些必需的模块。模拟的模块将为从中访问的任何属性返回一个虚假对象。因为我们是逐个文件复制的,所以依赖关系解析有时会找到由模型文件导入但其功能从未被使用的文件(例如自定义序列化代码或训练助手)。使用此函数可以模拟此功能,而无需修改原始代码。 参数 + **include**(*Union**[**List**[*[*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")*]**,* [*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")*]*) - 一个字符串,例如`"my_package.my_subpackage"`,或模块名称的字符串列表。字符串也可以是匹配多个模块的 glob 样式模式字符串。与此模式字符串匹配的任何必需依赖项将自动被模拟。 示例: `'torch.**'` - 匹配`torch`和 torch 的所有子模块,例如`'torch.nn'`和`'torch.nn.functional'` `'torch.*'` - 匹配`'torch.nn'`或`'torch.functional'`,但不匹配`'torch.nn.functional'` + **exclude**(*Union**[**List**[*[*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")*]**,* [*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")*]*) - 一个可选模式,用于排除与包含字符串匹配的某些模式。例如 `include='torch.**', exclude='torch.foo'` 将模拟所有 torch 包,除了`'torch.foo'`,默认值为`[]`。 + **allow_empty**(*bool*)- 一个可选标志,指定此调用`mock()`方法指定的模拟实现是否在打包期间必须与某个模块匹配。如果使用`allow_empty=False`添加了一个模拟,并且调用了`close()`(显式调用或通过`__exit__`),并且该模拟未与要导出的包使用的模块匹配,则会抛出异常。如果`allow_empty=True`,则不会抛出此类异常。 ```py mocked_modules() ``` 返回当前模拟的所有模块。 返回 包含将在此软件包中模拟的模块名称的列表。 返回类型 [*List*](https://docs.python.org/3/library/typing.html#typing.List "(in Python v3.12)")[[str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")] ```py register_extern_hook(hook) ``` 在导出器上注册一个 extern 钩子。 每次模块与`extern()`模式匹配时都会调用钩子。它应该具有以下签名: ```py hook(exporter: PackageExporter, module_name: str) -> None ``` 钩子将按注册顺序调用。 返回 通过调用`handle.remove()`可以删除添加的钩子的句柄。 返回类型 `torch.utils.hooks.RemovableHandle` ```py register_intern_hook(hook) ``` 在导出器上注册一个 intern 钩子。 每次模块与`intern()`模式匹配时都会调用钩子。它应该具有以下签名: ```py hook(exporter: PackageExporter, module_name: str) -> None ``` 钩子将按注册顺序调用。 返回 通过调用`handle.remove()`可以删除添加的钩子的句柄。 返回类型 `torch.utils.hooks.RemovableHandle` ```py register_mock_hook(hook) ``` 在导出器上注册一个模拟钩子。 每次模块与`mock()`模式匹配时都会调用钩子。它应该具有以下签名: ```py hook(exporter: PackageExporter, module_name: str) -> None ``` 钩子将按注册顺序调用。 返回 可以通过调用`handle.remove()`来删除添加的钩子的句柄。 返回类型 `torch.utils.hooks.RemovableHandle` ```py save_binary(package, resource, binary) ``` 将原始字节保存到包中。 参数 + **package**([*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")) - 此资源应该放在的模块包的名称(例如`"my_package.my_subpackage"`)。 + **资源**([*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")) - 用于标识要加载的资源的唯一名称。 + **二进制**([*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")) - 要保存的数据。 ```py save_module(module_name, dependencies=True) ``` 将`module`的代码保存到包中。使用`importers`路径解析模块对象的代码,然后使用其`__file__`属性查找源代码。 参数 + **module_name**([*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")) - 例如`my_package.my_subpackage`,将保存代码以提供此包的代码。 + **依赖项**([*bool*](https://docs.python.org/3/library/functions.html#bool "(在 Python v3.12 中)"),可选) - 如果为`True`,我们会扫描源代码以查找依赖项。 ```py save_pickle(package, resource, obj, dependencies=True, pickle_protocol=3) ``` 使用 pickle 将 Python 对象保存到存档中。相当于`torch.save()`,但保存到存档而不是独立文件。标准 pickle 不保存代码,只保存对象。如果`dependencies`为 true,则此方法还将扫描 pickled 对象以确定重建它们所需的模块,并保存相关代码。 为了能够保存一个对象,其中`type(obj).__name__`是`my_module.MyObject`,`my_module.MyObject`必须根据`importer`顺序解析为对象的类。当保存先前已打包的对象时,导入程序的`import_module`方法将需要存在于`importer`列表中才能正常工作。 参数 + **package**([*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")) - 此资源应该放在的模块包的名称(例如`"my_package.my_subpackage"`)。 + **资源**([*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")) - 用于标识要加载的资源的唯一名称。 + **obj**(*Any*) - 要保存的对象,必须是可 picklable 的。 + **依赖项**([*bool*](https://docs.python.org/3/library/functions.html#bool "(在 Python v3.12 中)"),可选) - 如果为`True`,我们会扫描源代码以查找依赖项。 ```py save_source_file(module_name, file_or_directory, dependencies=True) ``` 将本地文件系统中的`file_or_directory`添加到源包中,以提供`module_name`的代码。 参数 + **module_name**([*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")) - 例如`"my_package.my_subpackage"`,将保存代码以提供此包的代码。 + **file_or_directory**([*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")) - 文件或代码目录的路径。当目录时,使用`save_source_file()`递归复制目录中的所有 python 文件。如果文件命名为`"/__init__.py"`,则将代码视为包。 + **依赖项**([*bool*](https://docs.python.org/3/library/functions.html#bool "(在 Python v3.12 中)"),可选) - 如果为`True`,我们会扫描源代码以查找依赖项。 ```py save_source_string(module_name, src, is_package=False, dependencies=True) ``` 在导出的包中将`src`作为`module_name`的源代码添加。 参数 + **module_name**([*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")) - 例如`my_package.my_subpackage`,将保存代码以提供此包的代码。 + **src**([*str*](https://docs.python.org/3/library/stdtypes.html#str "(在 Python v3.12 中)")) - 要保存到此包的 Python 源代码。 + **is_package**(*bool*,可选)- 如果为`True`,则将此模块视为包。允许包具有子模块(例如`my_package.my_subpackage.my_subsubpackage`),并且资源可以保存在其中。默认为`False`。 + **依赖**(*bool*,可选)- 如果为`True`,我们会扫描源代码以获取依赖项。 ```py save_text(package, resource, text) ``` 将文本数据保存到包中。 参数 + **package**(*str*)- 此资源应该放在的模块包的名称(例如`"my_package.my_subpackage"`)。 + **resource**(*str*)- 用于标识资源的唯一名称,用于加载。 + **text**(*str*)- 要保存的内容。 ```py class torch.package.PackageImporter(file_or_buffer, module_allowed=>) ``` 导入器允许您加载由`PackageExporter`编写的包中的代码。代码以封闭方式加载,使用包中的文件而不是正常的 python 导入系统。这允许将 PyTorch 模型代码和数据打包,以便在服务器上运行或在将来用于迁移学习。 包的导入器确保模块中的代码只能从包内加载,除了在导出期间明确列出为外部模块的模块。zip 存档中的`extern_modules`文件列出了包在外部依赖的所有模块。这可以防止“隐式”依赖,其中包在本地运行,因为它正在导入一个本地安装的包,但是当包被复制到另一台机器时会失败。 ```py __init__(file_or_buffer, module_allowed=>) ``` 打开`file_or_buffer`以进行导入。这将检查导入的包是否仅需要`module_allowed`允许的模块。 参数 + **file_or_buffer**(*Union*[*[*str*]*,*PyTorchFileReader*,*[*Path*]*,*[*BinaryIO*]*])- 类似文件的对象(必须实现`read()`,`readline()`,`tell()`和`seek()`),一个字符串,或包含文件名的`os.PathLike`对象。 + **module_allowed**(*Callable**[*[*str*]*,*bool*]*,可选)- 用于确定是否应允许外部提供的模块的方法。可以用于确保加载的包不依赖于服务器不支持的模块。默认允许任何内容。 引发 [**ImportError**](https://docs.python.org/3/library/exceptions.html#ImportError "(in Python v3.12)")- 如果包将使用不允许的模块。 ```py file_structure(*, include='**', exclude=()) ``` 返回包的 zip 文件结构表示。 参数 + **include**(*Union**[**List**[*[*str*]*]**,*[*str*]*])- 一个可选的字符串,例如`"my_package.my_subpackage"`,或者包含在 zip 文件表示中的文件名称的可选字符串列表。这也可以是一个类似 glob 的模式,如`PackageExporter.mock()`中所述。 + **exclude**(*Union**[**List**[*[*str*]*]**,*[*str*]*])- 一个可选的模式,用于排除与模式匹配的文件。 返回 `Directory` 返回类型 *Directory* ```py id() ``` 返回内部标识符,torch.package 用于区分 `PackageImporter` 实例。看起来像: ```py ``` ```py import_module(name, package=None) ``` 如果尚未加载包中的模块,则加载该模块,然后返回模块。模块在导入程序本地加载,并将出现在 `self.modules` 而不是 `sys.modules` 中。 参数 + **name** ([*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")) – 要加载的模块的完全限定名称。 + **package** (*[*[*type*](https://docs.python.org/3/library/functions.html#type "(in Python v3.12)")*]**,* *optional*) – 未使用,但出现以匹配 importlib.import_module 的签名。默认为 `None`。 返回 (可能已经)加载的模块。 返回类型 [types.ModuleType](https://docs.python.org/3/library/types.html#types.ModuleType "(in Python v3.12)") ```py load_binary(package, resource) ``` 加载原始字节。 参数 + **package** ([*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")) – 模块包的名称(例如 `"my_package.my_subpackage"`)。 + **resource** ([*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")) – 资源的唯一名称。 返回 加载的数据。 返回类型 [bytes](https://docs.python.org/3/library/stdtypes.html#bytes "(in Python v3.12)") ```py load_pickle(package, resource, map_location=None) ``` 从包中反序列化资源,加载任何需要构造对象的模块,使用 `import_module()`。 参数 + **package** ([*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")) – 模块包的名称(例如 `"my_package.my_subpackage"`)。 + **resource** ([*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")) – 资源的唯一名称。 + **map_location** – 传递给 torch.load 以确定张量如何映射到设备。默认为 `None`。 返回 反序列化的对象。 返回类型 任何 ```py load_text(package, resource, encoding='utf-8', errors='strict') ``` 加载字符串。 参数 + **package** ([*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")) – 模块包的名称(例如 `"my_package.my_subpackage"`)。 + **resource** ([*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")) – 资源的唯一名称。 + **encoding** ([*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")*,* *optional*) – 传递给 `decode`。默认为 `'utf-8'`。 + **errors** ([*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")*,* *optional*) – 传递给 `decode`。默认为 `'strict'`。 返回 加载的文本。 返回类型 [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)") ```py python_version() ``` 返回用于创建此包的 python 版本。 注意:此函数是实验性的,不具有向前兼容性。计划将其稍后移入锁定文件。 返回 `Optional[str]` 一个 python 版本,例如 3.8.9,如果没有存储与此包相关的版本,则为 None ```py class torch.package.Directory(name, is_dir) ``` 文件结构表示。组织为具有其 Directory 子节点列表的 Directory 节点。通过调用 `PackageImporter.file_structure()` 来创建包的目录。 ```py has_file(filename) ``` 检查文件是否存在于 `Directory` 中。 参数 **filename** ([*str*](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.12)")) – 要搜索的文件路径。 返回 如果 `Directory` 包含指定的文件。 返回类型 [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.12)")