4.4. 使用 getattr 获取对象引用

您已经知道 Python 函数是对象。您不知道的是,您可以使用 getattr 函数获取对函数的引用,而无需在运行时之前知道其名称。

示例 4.10. 介绍 getattr

>>> li = ["Larry", "Curly"]
>>> li.pop                       1
<built-in method pop of list object at 010DF884>
>>> getattr(li, "pop")           2
<built-in method pop of list object at 010DF884>
>>> getattr(li, "append")("Moe") 3
>>> li
["Larry", "Curly", "Moe"]
>>> getattr({}, "clear")         4
<built-in method clear of dictionary object at 00F113D4>
>>> getattr((), "pop")           5
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'pop'
1 这将获取对列表的 pop 方法的引用。请注意,这不会调用 pop 方法;那将是 li.pop()。这是方法本身。
2 这也返回对 pop 方法的引用,但这次,方法名称作为字符串参数指定给 getattr 函数。getattr 是一个非常有用的内置函数,它返回任何对象的任何属性。在这种情况下,对象是一个列表,属性是 pop 方法。
3 如果它还没有完全渗透到它的用处,请尝试以下操作:getattr 的返回值 该方法,然后您可以像直接说 li.append("Moe") 一样调用它。但您没有直接调用该函数;您改为将函数名称指定为字符串。
4 getattr 也适用于字典。
5 理论上,getattr 适用于元组,但 元组没有方法,因此无论您给出什么属性名称,getattr 都会引发异常。

4.4.1. 对模块使用 getattr

getattr 不仅仅适用于内置数据类型。它也适用于模块。

示例 4.11. apihelper.py 中的 getattr 函数

>>> import odbchelper
>>> odbchelper.buildConnectionString             1
<function buildConnectionString at 00D18DD4>
>>> getattr(odbchelper, "buildConnectionString") 2
<function buildConnectionString at 00D18DD4>
>>> object = odbchelper
>>> method = "buildConnectionString"
>>> getattr(object, method)                      3
<function buildConnectionString at 00D18DD4>
>>> type(getattr(object, method))                4
<type 'function'>
>>> import types
>>> type(getattr(object, method)) == types.FunctionType
True
>>> callable(getattr(object, method))            5
True
1 这将返回对 第 2 章,您的第一个 Python 程序 中研究的 odbchelper 模块中的 buildConnectionString 函数的引用。(您看到的十六进制地址特定于我的机器;您的输出将有所不同。)
2 使用 getattr,您可以获得对同一函数的相同引用。通常,getattr(对象, "属性") 等效于 对象.属性。如果 对象 是模块,则 属性 可以是模块中定义的任何内容:函数、类或全局变量。
3 这就是您在 info 函数中实际使用的内容。对象 作为参数传递给函数;方法 是一个字符串,它是方法或函数的名称。
4 在这种情况下,方法 是函数的名称,您可以通过获取其 type 来证明这一点。
5 由于 方法 是一个函数,因此它是 可调用的

4.4.2. getattr 作为调度器

getattr 的常见用法模式是作为调度器。例如,如果您有一个程序可以以各种不同的格式输出数据,则可以为每种输出格式定义单独的函数,并使用单个调度函数来调用正确的函数。

例如,假设有一个程序以 HTMLXML 和纯文本格式打印站点统计信息。输出格式的选择可以在命令行上指定,也可以存储在配置文件中。一个 statsout 模块定义了三个函数,output_htmloutput_xmloutput_text。然后主程序定义了一个输出函数,如下所示

示例 4.12. 使用 getattr 创建调度器


import statsout

def output(data, format="text"):                              1
    output_function = getattr(statsout, "output_%s" % format) 2
    return output_function(data)                              3
1 output 函数接受一个必需的参数 data 和一个可选的参数 format。如果未指定 format,则默认为 text,您最终将调用纯文本输出函数。
2 您将 format 参数与 "output_" 连接起来以生成函数名称,然后从 statsout 模块中获取该函数。这使您以后可以轻松扩展程序以支持其他输出格式,而无需更改此调度函数。只需将另一个函数添加到名为 output_pdfstatsout 中,并将 "pdf" 作为 format 传递给 output 函数即可。
3 现在,您可以像调用任何其他函数一样简单地调用输出函数。 output_function 变量是来自 statsout 模块的相应函数的引用。

您看到上一个示例中的错误了吗?这是字符串和函数的非常松散的耦合,并且没有错误检查。如果用户传入的格式在 statsout 中没有定义相应的函数,会发生什么情况?好吧,getattr 将返回 None,它将被分配给 output_function 而不是有效的函数,并且尝试调用该函数的下一行将崩溃并引发异常。那太糟糕了。

幸运的是,getattr 接受一个可选的第三个参数,即默认值。

示例 4.13. getattr 默认值


import statsout

def output(data, format="text"):
    output_function = getattr(statsout, "output_%s" % format, statsout.output_text)
    return output_function(data) 1
1 此函数调用保证有效,因为您向对 getattr 的调用添加了第三个参数。第三个参数是一个默认值,如果未找到第二个参数指定的属性或方法,则返回该默认值。

如您所见,getattr 非常强大。它是自省的核心,您将在后面的章节中看到更强大的例子。