使用getattr获取方法的对象引用
假设我有一个叫做 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.start
和 func
的模块信息。没什么意外,func
和 t.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'
>>>
换句话说,我手里有两个数据:
__import__('__main__')
- 字符串
'Test.start'
现在,我该如何获取 t.start
的句柄(注意这里是 instance
),这个句柄应该是 <bound method Test.start of <__main__.Test instance at 0xb769678c>>
。
3 个回答
我在想,你为什么写了 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__
都能返回结果,是因为这段引文的解释:
一个类的 实例 有一个实现为字典的命名空间,这是查找属性引用的 第一个地方。
当在这里找不到属性时,如果实例的 类 有同名属性,搜索会继续在类属性中进行。
所以:
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( ) 函数也会得到相同的结果。
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()
的,所以...
我不太确定我是否理解你的问题,不过我觉得这个代码可以满足你的需求:
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>>