使用getattr获取方法的对象引用

4 投票
3 回答
6010 浏览
提问于 2025-04-17 15:34

假设我有一个叫做 Test 的类,这个类里面有一个叫 start 的方法。

>>> class Test:
...     def __init__(self, *args, **kwargs):
...         pass
...     def start(self):
...         pass
... 

现在,我有一个独立的函数,叫 func

>>> def func():
...     print 'this is a func and not a method!!!'
... 
>>> 

[1] 现在,t.start 是一个属于 __main__.Test 实例的方法,它的地址是 0xb769678c

>>> t = Test()
>>> t.start
<bound method Test.start of <__main__.Test instance at 0xb769678c>>
>>> 

[2] func 是一个函数,它的地址是 0xb767ec6c

>>> func
<function func at 0xb767ec6c>
>>> 

现在,我们可以通过内置的 __module__ 来提取 t.startfunc 的模块信息。没什么意外,funct.start 都属于同一个模块,也就是 __main__

>>> func.__module__
'__main__'
>>> t.__module__
'__main__'
>>> 

[3] 现在,让我们把 t.start__module__ 存储在一个变量 obj 中。

>>> obj = __import__(t.start.__module__)
>>> obj
<module '__main__' (built-in)>
>>> 

接下来,我使用 getattr() 来获取函数 func 的句柄,输出结果是 <function func at 0xb767ec6c>,这个输出和 [2] 是一样的。

>>> print getattr(obj, 'func')
<function func at 0xb767ec6c>
>>> 
>>> print getattr(__import__('__main__'), 'func')
<function func at 0xb767ec6c>
>>> 

问题:

我该如何使用 getattr() 和模块名 [3] 来获取 Test.start 的句柄 [1],这个句柄应该是 <bound method Test.start of <__main__.Test instance at 0xb769678c>>

当我尝试在 't.start' 上使用 getattr() 时,得到了以下的错误信息:

>>> print getattr(obj, 'Test.start')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'Test.start'
>>> 
>>> 
>>> print getattr(__import__('__main__'), 'Test.start')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'Test.start'
>>> 

换句话说,我手里有两个数据:

  1. __import__('__main__')
  2. 字符串 'Test.start'

现在,我该如何获取 t.start 的句柄(注意这里是 instance),这个句柄应该是 <bound method Test.start of <__main__.Test instance at 0xb769678c>>

3 个回答

0

我在想,你为什么写了 obj = __import__(t.start.__module__)
我觉得这样做是为了获取主模块的名字,这样就可以通过 getattr( ) 函数来获取模块命名空间中的对象作为模块的属性。

我想告诉你,其实你不需要这样做。
globals( ) 是一个字典,表示主空间的全局命名空间。
所以你可以写,比如 globals()["func"] 来获取对象 func

我还想告诉你,还有另一种方法可以通过 __dict__ 方法来获取对象 N 的命名空间中的对象 xyz,只需用它的名字 xyz,就是 N.__dict__,这样可以访问对象 N 的命名空间。


我在想,你的问题是不是因为 Python 的某些细节导致你出错了。

在你的问题中,有一处你写了 t.__module__,而另一处你写了 t.start.__module__
这两种情况的结果是一样的,都是 "__main__"

但是,
1) t 并没有名为 __module__ 的属性。
2) t 甚至没有名为 start 的属性!
如果你打印 t.__dict__,你会看到结果是 { }

start 实际上并不属于实例 t 的命名空间,因为它实际上属于 Test 的命名空间。

以下代码证明了这些说法:

class Test:
    def __init__(self, *args, **kwargs):
        pass
    def start(self):
        pass

y = Test()
y.ku = 102

print 'y.__dict__',y.__dict__
print 'Test.__dict__',Test.__dict__

结果

y.__dict__ {'ku': 102}
Test.__dict__ {'start': <function start at 0x011E11B0>,
'__module__': '__main__',
'__doc__': None,
'__init__': <function __init__ at 0x011E1170>}

.

之所以 t.__module__t.start.__module__ 都能返回结果,是因为这段引文的解释:

一个类的 实例 有一个实现为字典的命名空间,这是查找属性引用的 第一个地方
当在这里找不到属性时,如果实例的 有同名属性,搜索会继续在类属性中进行。

http://docs.python.org/2/reference/datamodel.html#index-55

所以:
1) t 没有名为 __module__ 的属性,所以 Python 会在 t 的类中查找。
2) t 没有名为 start 的属性,所以 Python 也会在 t 的类中查找。

.

所以,Python 会去 t 的类中查找 t.__module__t.start.__module__ 的值。
这个机制可以通过以下代码观察到:

class Test:
    def __init__(self, *args, **kwargs):
        pass
    def start(self):
        pass

t = Test()

print 'getattr(t,"start")'
print getattr(t,"start")

# result is
getattr(t,"start")
<bound method Test.start of <__main__.Test instance at 0x011DF288>>

我的意思是,Python 认为 t 的名为 "start" 的属性是 Test.start,而不是 t.start

.

因此,由于 t.start.__module__"__main__",我觉得你可能误以为 t.start 属于模块命名空间,所以你应该能写类似 getattr(obj,"func") 的代码来获取对象 start

但这是错误的。
你无法在全局命名空间(即模块的命名空间)中找到方法 start(我说的是方法),因为它在 Test 的命名空间中。你必须通过实例或类,或者直接通过类来访问它。


还有一件事。
我强调说是:方法,因为我认为方法 t.start 是基于一个 函数 的,而这个函数与它是不同的,并且 位于模块的命名空间中。实际上,方法是指向实例和这个全局命名空间函数的指针的封装:

如果你仍然不明白方法是如何工作的,可以看看实现,这可能会澄清问题。当引用一个实例属性时,如果它不是数据属性,就会搜索它的类。如果这个名字表示一个有效的类属性且是一个函数对象,就会通过将(指向)实例对象和刚找到的函数对象打包在一起,创建一个方法对象:这就是方法对象。当调用方法对象并传入参数列表时,会根据实例对象和参数列表构建一个新的参数列表,然后用这个新的参数列表调用函数对象。

http://docs.python.org/2/tutorial/classes.html#method-objects

我想强调的是,似乎有一个函数(而不是方法)存在于某个地方,而这个方法是基于这个函数的。

我认为这个函数位于模块命名空间中,这就是为什么 Test.__dict__["start"]
会返回
<function start at 0x011E40F0>

getattr(Test,"start")
getattr(t,"start")
会返回
<unbound method Test.start>
<bound method Test.start of <__main__.Test instance at 0x011DF530>>
而没有定位绑定和未绑定的方法。

所以,在我看来,只要我正确理解了文档(我觉得文档没有很好地解释这些点),方法是一个封装,并且是基于 一个真实的函数,这个函数位于模块命名空间中,这就是为什么 t.start.__module__Test.start.__module__ 都是 "__main__"

可能看起来有点奇怪的是,模块命名空间中存在一个函数,但当我们用 globals() 打印它们时却没有显示:

class Test:
    def __init__(self, *args, **kwargs):
        pass
    def start(self):
        pass

def func():
    print 'this is a func and not a method!!!'

t = Test()

print '* globals()["func"]'
print globals()["func"]
print id(globals()["func"])
print "===================================="
print '* Test.__dict__["start"]'
print Test.__dict__["start"]
print id(Test.__dict__["start"])
print '----------------------------------------------'
print '* getattr(Test,"start")'
print getattr(Test,"start")
print id(getattr(Test,"start"))
print '----------------------------------------------'
print '* getattr(t,"start")'
print getattr(t,"start")
print id(getattr(t,"start"))
print "===================================="
print globals()

结果

* globals()["func"]
<function func at 0x011C27B0>
18622384
====================================
* Test.__dict__["start"]
<function start at 0x011DEFB0>
18739120
----------------------------------------------
* getattr(Test,"start")
<unbound method Test.start>
18725304
----------------------------------------------
* getattr(t,"start")
<bound method Test.start of <__main__.Test instance at 0x011DF418>>
18725304

{'__builtins__': <module '__builtin__' (built-in)>,
'__package__': None,
't': <__main__.Test instance at 0x011DF418>,
'func': <function func at 0x011C27B0>,
'Test': <class __main__.Test at 0x011DC538>,
'__name__': '__main__',
'__doc__': None}

但事实上,我们确实看到了 Test.__dict__["start"] 函数,其地址 18739120 与绑定和未绑定方法的地址 18725304 不同。
而且,我不知道还有什么其他解释可以解释所有这些事实。

实际上,一个没有被赋予任何名字的函数不出现在模块命名空间中并不奇怪。
这是一个内部函数,对 Python 来说是必要的,不是程序员可以使用的,仅此而已。


关于 func,使用你所用的所有方法:

class Test:
    def __init__(self, *args, **kwargs):
        pass
    def start(self):
        pass

def func():
    print 'this is a func and not a method!!!'

t = Test()
print 'func --->',func
print id(func)

print '\nobj = __import__(t.start.__module__)  done\n'
obj = __import__(t.start.__module__)

print '* globals()["func"]'
print globals()["func"]
print id(globals()["func"])
print '* obj.__dict__["func"]'
print obj.__dict__["func"] 
print "* getattr(obj, 'func')"
print getattr(obj, 'func')
print "* getattr(__import__('__main__'), 'func')"
print getattr(__import__('__main__'), 'func')

结果

func ---> <function func at 0x011C2470>
18621552

obj = __import__(t.start.__module__)  done

* globals()["func"]
<function func at 0x011C2470> # <== address in hexadecimal
18621552                      # <== address in decimal
* obj.__dict__["func"]
<function func at 0x011C2470>
* getattr(obj, 'func')
<function func at 0x011C2470>
* getattr(__import__('__main__'), 'func')
<function func at 0x011C2470>

.

现在把你使用 obj 的方法分开:

class Test:
    def __init__(self, *args, **kwargs):
        pass
    def start(self):
        pass

print 'Test.start -->',Test.start
print id(Test.start)
print '----------------------------------------------'
print '* Test.__dict__["start"]'
print Test.__dict__["start"]
print id(Test.__dict__["start"])

print '* getattr(Test,"start")'
print getattr(Test,"start")
print id(getattr(Test,"start"))
print '\n'


print 't.start -->',t.start
print id(t.start)
print '----------------------------------------------'
print '* t.__dict__["start"]'
try:
    print t.__dict__["start"]
    print id(t.__dict__["start"])
except KeyError as e:
    print 'KeyError :',e

print '* getattr(t,"start")'
print getattr(t,"start")
print id(getattr(t,"start"))

结果

Test.start --> <unbound method Test.start>
18725264
----------------------------------------------
* Test.__dict__["start"]
<function start at 0x011E40F0>
18759920
* getattr(Test,"start")
<unbound method Test.start>
18725264


t.start --> <bound method Test.start of <__main__.Test instance at 0x011DB940>>
18725264
----------------------------------------------
* t.__dict__["start"]
KeyError : 'start'
* getattr(t,"start")
<bound method Test.start of <__main__.Test instance at 0x011DB940>>
18725264

到目前为止,我很少使用 getattr( )
我在这些结果中意识到 getattr( ) 以特定的方式给出结果:属性 start 被描述为方法,是否绑定取决于它是通过实例还是通过类获得的。

然而,只有 __dict__ 能提供关于对象真实属性的精确信息。
因此,我们看到 start 实际上并不在实例的命名空间中,而只在类的命名空间中。
当它作为 Test.__dict__ 的命名空间元素时被描述为函数,而当通过 getattr( ) 作为属性给出时被描述为方法。在最后这种情况下,没有给出地址。

使用 vars( ) 函数也会得到相同的结果。

1
obj = __import__(t.start.__module__)    

test_class = getattr(obj, 'Test')

print getattr(test_class, 'start')

我不太确定你是否需要直接从模块里获取这个(或者说这是否可能) :/

你也可以使用:

obj = __import__(t.start.__module__)

print obj.__dict__["Test"].__dict__["start"]

不过你是问关于 getattr() 的,所以...

3

我不太确定我是否理解你的问题,不过我觉得这个代码可以满足你的需求:

class Test:
    def __init__(self, *args, **kwargs):
        pass
    def start(self):
        pass

def func():
    print('this is a func and not a method!!!')

t = Test()

module = __import__(t.start.__module__)

print(vars(module)['Test'].start)
print(vars(module)['func'])
print(vars(module)['t'].start)

(Python 3) 输出结果:

<function Test.start at 0x00E52460>
<function func at 0x00E524F0>
<bound method Test.start of <__main__.Test object at 0x008DF670>>

撰写回答