networkx.utils.decorators.argmap#

class argmap(func, *args, try_finally=False)[source]#

在调用函数之前将映射应用于参数的装饰器

这个类提供了一个装饰器,它在函数被调用之前映射(转换)函数的参数。例如,在许多函数中,我们都有类似的代码来确定一个参数是待创建的节点数,还是待处理的节点列表。这个装饰器提供了接受这两种情况的代码——在实际函数被调用之前将指定的参数转换为节点列表。

这个装饰器类允许我们处理单个或多个参数。待处理的参数可以通过字符串(参数名)或索引(args 列表中的项)来指定。

参数:
func可调用对象 (callable)

应用于参数的函数

*args可迭代对象 (int, str 或 tuple)

一个参数列表,可以指定为字符串(参数名)、整数(数字索引)或元组,元组可以包含整数、字符串和(递归的)元组。每个元素都指定了装饰器应该映射哪些参数。元组表示映射函数接收(并返回)多个参数,其顺序和嵌套结构与此处指示的相同。

try_finallybool (默认为: False)

当为 True 时,将函数调用包装在一个 try-finally 块中,其中 finally 块的代码由 func 创建。当映射函数构造一个需要后处理(如关闭)的对象(如文件句柄)时使用此功能。

注意:try_finally 装饰器不能用于装饰生成器函数。

另请参阅

not_implemented_for
open_file
nodes_or_number
py_random_state
networkx.algorithms.community.quality.require_partition

注释

此类的对象是可调用的,旨在用于定义装饰器。通常,装饰器将一个函数作为输入并构造一个函数作为输出。具体来说,argmap 对象返回被装饰/包装的输入函数,以便在调用装饰函数之前将指定的参数映射(转换)为新值。

概述而言,argmap 对象返回一个新函数,该函数具有原始函数的所有“双下划线”值(如 __doc____name__ 等)。这个装饰函数的代码是基于原始函数的签名构建的。它首先将输入参数映射到可能的新值。然后它调用装饰函数,用这些新值替换已被映射的指定参数。最后返回原始函数的返回值。这个新函数就是用户实际调用的函数。

提供了三个附加功能。

1) 代码是延迟编译的。也就是说,新函数以对象形式返回,代码尚未编译,但包含了所有必要的信息,以便在第一次调用时进行编译。这节省了导入时的时间,但增加了函数首次调用的时间。随后的调用则与正常情况一样快。

2) 如果“try_finally”关键字参数为 True,则每个映射参数后面都会跟着一个 try 块,在被包装调用的另一侧,由一个 finally 块对应关闭该映射。我们期望 func 返回一个包含两个元素的元组:映射后的值和一个将在 finally 子句中调用的函数。此功能被包含在内,以便 open_file 装饰器可以向被装饰函数提供文件句柄并在函数调用后关闭文件句柄。它甚至会根据是否需要打开文件或输入是否已打开来跟踪是否关闭文件句柄。因此,被装饰函数无需包含任何打开或关闭文件的代码。

3) 应用的映射可以处理多个参数。例如,您可以使用映射交换两个参数,或将它们转换为它们的和与差。此功能被包含在内,以便在 quality.py 模块中提供一个装饰器,该装饰器检查输入 partition 是否是输入图 G 节点的有效划分。在此示例中,映射的输入为 (G, partition)。在检查有效划分后,映射要么引发异常,要么保持输入不变。因此,许多进行此检查的函数可以使用此装饰器,而无需在每个函数中复制代码。更复杂的嵌套参数结构将在下面描述。

剩余的注释概述了此类的代码结构和方法,以帮助理解如何使用它。

实例化一个 argmap 对象只是存储了映射函数和指定哪些参数需要映射的输入标识符。结果得到的装饰器就可以使用这个映射来装饰任何函数。调用该对象(argmap.__call__,但通常通过 @my_decorator 完成)会构建一个被装饰函数的延迟编译的轻量级包装器,并包装了必要的函数“双下划线”属性,如 __doc____name__。这个轻量级包装函数作为被装饰函数返回。当调用该装饰函数时,轻量级包装代码会调用 argmap._lazy_compile,该方法编译被装饰函数(使用 argmap.compile)并用新编译的代码替换轻量级包装器的代码。这节省了每次导入 networkx 时的编译步骤,代价是在第一次调用装饰函数时进行编译。

当被装饰函数被编译时,代码会使用 argmap.assemble 方法递归地组装。递归性对于嵌套装饰器是必需的。组装的结果是许多有用的对象。

sig原始装饰函数的函数签名,形式为

argmap.signature() 构造。它是使用 inspect.signature 构造的,但增加了属性字符串 sig_defsig_call,以及其他特定于此函数参数映射的信息。此信息用于构造定义新装饰函数的代码字符串。

wrapped_name由 argmap 构造的内部使用的唯一名称

用于被装饰函数。

functions在此代码内部使用的函数的字典

装饰函数中使用的函数的字典,用作 execglobals。这个字典会递归更新,以支持嵌套装饰。

mapblock用于映射传入参数的代码(作为字符串列表)

值到它们映射后的值。

finallys用于提供可能嵌套的 finally 子句的代码(作为字符串列表)

如果需要,提供一组 finally 子句。

mutable_args一个布尔值,指示 sig.args 元组是否应被转换为

列表以便进行修改。

在此递归组装过程之后,argmap.compile 方法构建代码(作为字符串),以便在需要时将元组 sig.args 转换为列表。它将定义代码与适当的缩进连接起来并编译结果。最后,评估此代码并将原始包装器的实现替换为编译后的版本(有关更多详细信息,请参阅 argmap._lazy_compile)。

其他 argmap 方法包括 _name_count,它们允许内部生成的名称在 Python 会话中保持唯一。_flatten_indent 方法将嵌套的字符串列表处理成适当缩进的 Python 代码,以便进行编译。

更复杂的嵌套参数元组也允许,尽管通常不使用。对于简单的两个参数情况,argmap 输入 (“a”, “b”) 意味着映射函数将接受两个参数并返回一个包含两个映射值的元组。一个更复杂的例子,argmap 输入 ("a", ("b", "c")),要求映射函数接受两个输入,其中第二个预期为一个包含两个元素的元组。然后它必须以相同的嵌套结构 (newa, (newb, newc)) 输出三个映射值。这种通用性水平通常不是必需的,但在处理多个参数时实现起来很方便。

示例

这些示例大多使用 @argmap(...) 将装饰器应用于下一行定义的函数。然而,在 NetworkX 代码库中,argmap 在函数内部用于构造装饰器。也就是说,装饰器定义一个映射函数,然后使用 argmap 构建并返回一个被装饰函数。一个简单的例子是指定报告货币单位的装饰器。该装饰器(名为 convert_to)将按如下方式使用:

@convert_to("US_Dollars", "income")
def show_me_the_money(name, income):
    print(f"{name} : {income}")

创建该装饰器的代码可能是

def convert_to(currency, which_arg):
    def _convert(amount):
        if amount.currency != currency:
            amount = amount.to_currency(currency)
        return amount

    return argmap(_convert, which_arg)

尽管 argmap 有这种常见用法,但以下大多数示例为了节省篇幅使用了 @argmap(...) 的惯用法。

这是 argmap 用于对函数两个参数的元素求和的示例。被装饰函数

@argmap(sum, "xlist", "zlist")
def foo(xlist, y, zlist):
    return xlist - y + zlist

是以下代码的语法糖:

def foo(xlist, y, zlist):
    x = sum(xlist)
    z = sum(zlist)
    return x - y + z

并且等价于(使用参数索引)

@argmap(sum, "xlist", 2)
def foo(xlist, y, zlist):
    return xlist - y + zlist

@argmap(sum, "zlist", 0)
def foo(xlist, y, zlist):
    return xlist - y + zlist

转换函数可以应用于多个参数,例如

def swap(x, y):
    return y, x

# the 2-tuple tells argmap that the map `swap` has 2 inputs/outputs.
@argmap(swap, ("a", "b")):
def foo(a, b, c):
    return a / b * c

等价于

def foo(a, b, c):
    a, b = swap(a, b)
    return a / b * c

更一般地,应用的参数可以是字符串或整数的嵌套元组。语法 @argmap(some_func, ("a", ("b", "c"))) 会期望 some_func 接受两个输入,其中第二个预期为一个包含两个元素的元组。然后它应该返回两个输出,其中第二个也是一个包含两个元素的元组。返回值将分别替换输入参数 “a”、“b” 和 “c”。对于 @argmap(some_func, (0, ("b", 2))) 也是类似的。

另外请注意,对于可变参数函数,索引可以大于命名参数的数量。例如:

def double(a):
    return 2 * a

@argmap(double, 3)
def overflow(a, *args):
    return a, args

print(overflow(1, 2, 3, 4, 5, 6))  # output is 1, (2, 3, 8, 5, 6)

Try Finally

此外,这个 argmap 类可以用来创建一个装饰器,该装饰器启动一个 try…finally 块。装饰器必须编写成同时返回转换后的参数和一个关闭函数。此功能被包含在内,以便启用 open_file 装饰器,该装饰器可能需要关闭文件或不关闭,取决于它是否必须打开该文件。此功能使用了 @argmap 的仅关键字参数 try_finally

例如,这个映射打开一个文件,然后确保它被关闭

def open_file(fn):
    f = open(fn)
    return f, lambda: f.close()

装饰器将其应用于函数 foo

@argmap(open_file, "file", try_finally=True)
def foo(file):
    print(file.read())

是以下代码的语法糖:

def foo(file):
    file, close_file = open_file(file)
    try:
        print(file.read())
    finally:
        close_file()

并且等价于(使用索引)

@argmap(open_file, 0, try_finally=True)
def foo(file):
    print(file.read())

这是 try_finally 功能用于创建装饰器的示例

def my_closing_decorator(which_arg):
    def _opener(path):
        if path is None:
            path = open(path)
            fclose = path.close
        else:
            # assume `path` handles the closing
            fclose = lambda: None
        return path, fclose

    return argmap(_opener, which_arg, try_finally=True)

然后可以按如下方式使用:

@my_closing_decorator("file")
def fancy_reader(file=None):
    # this code doesn't need to worry about closing the file
    print(file.read())

try_finally = True 的装饰器不能与生成器函数一起使用,因为 finally 块在生成器耗尽之前就被评估了

@argmap(open_file, "file", try_finally=True)
def file_to_lines(file):
    for line in file.readlines():
        yield line

等价于

def file_to_lines_wrapped(file):
    for line in file.readlines():
        yield line

def file_to_lines_wrapper(file):
    try:
        file = open_file(file)
        return file_to_lines_wrapped(file)
    finally:
        file.close()

其行为类似于

def file_to_lines_whoops(file):
    file = open_file(file)
    file.close()
    for line in file.readlines():
        yield line

因为 file_to_lines_wrapperfinally 块在调用者有机会耗尽迭代器之前就被执行了。

__init__(func, *args, try_finally=False)[source]#

方法

assemble(f)

收集装饰函数包装 f 的源代码组件。

compile(f)

编译装饰函数。

signature(f)

构造描述 f 的 Signature 对象。