方法的文本引用
假设我有以下内容:
def func():
print 'this is a function and not a method!!!'
class Test:
def TestFunc(self):
print 'this is Test::TestFunc method'
我有以下几个函数(这些函数来自于 https://bitbucket.org/agronholm/apscheduler/src/d2f00d9ac019/apscheduler/util.py):
def get_callable_name(func):
"""
Returns the best available display name for the given function/callable.
"""
f_self = getattr(func, '__self__', None) or getattr(func, 'im_self', None)
if f_self and hasattr(func, '__name__'):
if isinstance(f_self, type):
# class method
clsname = getattr(f_self, '__qualname__', None) or f_self.__name__
return '%s.%s' % (clsname, func.__name__)
# bound method
return '%s.%s' % (f_self.__class__.__name__, func.__name__)
if hasattr(func, '__call__'):
if hasattr(func, '__name__'):
# function, unbound method or a class with a __call__ method
return func.__name__
# instance of a class with a __call__ method
return func.__class__.__name__
raise TypeError('Unable to determine a name for %s -- '
'maybe it is not a callable?' % repr(func))
def obj_to_ref(obj):
"""
Returns the path to the given object.
"""
ref = '%s:%s' % (obj.__module__, get_callable_name(obj))
try:
obj2 = ref_to_obj(ref)
if obj != obj2:
raise ValueError
except Exception:
raise ValueError('Cannot determine the reference to %s' % repr(obj))
return ref
def ref_to_obj(ref):
"""
Returns the object pointed to by ``ref``.
"""
if not isinstance(ref, basestring):
raise TypeError('References must be strings')
if not ':' in ref:
raise ValueError('Invalid reference')
modulename, rest = ref.split(':', 1)
try:
obj = __import__(modulename)
except ImportError:
raise LookupError('Error resolving reference %s: '
'could not import module' % ref)
try:
for name in modulename.split('.')[1:] + rest.split('.'):
obj = getattr(obj, name)
return obj
except Exception:
raise LookupError('Error resolving reference %s: '
'error looking up object' % ref)
上面的函数 - obj_to_ref
用来返回一个给定函数对象的文本引用,而 ref_to_obj
则是用来根据给定的文本引用返回一个对象。比如,我们来试试 func
这个函数。
>>>
>>> func
<function func at 0xb7704924>
>>>
>>> obj_to_ref(func)
'__main__:func'
>>>
>>> ref_to_obj('__main__:func')
<function func at 0xb7704924>
>>>
func
函数运行得很好。但是当我试图在 class Test
的一个实例上使用这些函数时,它却无法得到一个文本引用。
>>>
>>> t = Test()
>>>
>>> t
<__main__.Test instance at 0xb771b28c>
>>>
>>> t.TestFunc
<bound method Test.TestFunc of <__main__.Test instance at 0xb771b28c>>
>>>
>>>
>>> obj_to_ref(t.TestFunc)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in obj_to_ref
ValueError: Cannot determine the reference to <bound method Test.TestFunc of <__main__.Test instance at 0xb771b28c>>
>>>
>>>
对于给定的输入 t.TestFunc
,obj_to_ref
函数返回了 __main__:Test.TestFunc
作为文本表示,但同样的文本却无法用来生成对象。
问题:
在 Python
中,有没有办法可以像这样:
>>> t.TestFunc
<bound method Test.TestFunc of <__main__.Test instance at 0xb771b28c>>
>>>
把一个对象表示成字符串,并从这个字符串重建这个对象呢?
如果我们把地址 0xb771b28c
作为字符串的一部分保存起来,然后通过解引用这个地址来重新生成对象,这样可行吗?
2 个回答
正如我在上面的评论中提到的,问题出在 get_callable_name
这个函数上。调用 get_callable_name(t.TestFunc)
的时候,它返回了 'Test.TestFunc'
,这显然是错误的。正确的应该是 't.TestFunc'。我添加了 variable_name_in_module
,并在 get_callable_name
中使用了它,现在代码可以正常工作了。底部的检查返回了 True
。不过,variable_name_in_module
的实现方式有点不太优雅,我找不到更干净的做法。
如果你只是需要处理一些小的事情,这样做应该没问题,但要注意的是,variable_name_in_module
每次调用 get_callable_name
时都会进行一次字典查找,N 是模块中变量的数量。
代码如下:
def variable_name_in_module(module, var):
for name in dir(module):
if getattr(module, name) == var:
return name
def get_callable_name(func):
"""
Returns the best available display name for the given function/callable.
"""
f_self = getattr(func, '__self__', None) or getattr(func, 'im_self', None)
if f_self and hasattr(func, '__name__'):
if isinstance(f_self, type):
# class method
clsname = getattr(f_self, '__qualname__', None) or f_self.__name__
return '%s.%s' % (clsname, func.__name__)
# bound method
return '%s.%s' % (variable_name_in_module(__import__(f_self.__module__), f_self), func.__name__)
if hasattr(func, '__call__'):
if hasattr(func, '__name__'):
# function, unbound method or a class with a __call__ method
return func.__name__
# instance of a class with a __call__ method
return func.__class__.__name__
raise TypeError('Unable to determine a name for %s -- '
'maybe it is not a callable?' % repr(func))
def obj_to_ref(obj):
"""
Returns the path to the given object.
"""
ref = '%s:%s' % (obj.__module__, get_callable_name(obj))
try:
obj2 = ref_to_obj(ref)
if obj != obj2:
raise ValueError
except Exception:
raise ValueError('Cannot determine the reference to %s' % repr(obj))
return ref
def ref_to_obj(ref):
"""
Returns the object pointed to by ``ref``.
"""
if not isinstance(ref, basestring):
raise TypeError('References must be strings')
if not ':' in ref:
raise ValueError('Invalid reference')
modulename, rest = ref.split(':', 1)
try:
obj = __import__(modulename)
except ImportError:
raise LookupError('Error resolving reference %s: '
'could not import module' % ref)
try:
for name in modulename.split('.')[1:] + rest.split('.'):
obj = getattr(obj, name)
return obj
except Exception:
raise LookupError('Error resolving reference %s: '
'error looking up object' % ref)
class Test:
def TestFunc(self):
print "test"
t = Test()
print t.TestFunc == ref_to_obj(obj_to_ref(t.TestFunc))
编辑:附言:如果 variable_name_in_module
找不到任何东西,可能应该抛出一个异常,虽然我不太明白这种情况怎么会发生。
你的问题很有意思,但有点复杂。
1) 你不应该把 func
当作 get_callable_name(func)
的参数。
在我的回答中,我把它换成了 X
。
2) 你把一部分代码放错了地方。
try:
obj2 = ref_to_obj(ref)
print 'obj != obj2 : ',obj != obj2
if obj != obj2:
raise ValueError
except Exception:
raise ValueError('Cannot determine the reference to %s' % repr(obj))
return ref
在 obj_to_ref()
函数里面没有任何关系。
在我的回答中,我把它移到了这个函数外面。
3) 你代码问题的明显原因是,你为对象 t.TestFunc
获取的引用是 '__main__:Test.TestFunc'
,而不是 '__main__:t.TestFunc'
,这本来应该是这样的。
这个决定是在 get_callable_name()
函数中做出的,正如 entropy 所说。
因为 f.self
是 t
,而 X
有一个名字(TestFunc),但它不是 type
类型的类(因为 t
是一个实例),
所以执行了指令 return '%s.%s' % (f_self.__class__.__name__, X.__name__)
。
但是你把表达式 f_self.__class__.__name
放错了:它是 t
的类名,而不是 t
本身的名字。
.
问题在于,与类(有一个属性 __name__
)不同,Python 语言并没有提供一种方法来按需获取实例的名字:实例没有像类那样的 __name__
属性来表示实例的名字。
所以,由于获取实例名字比较麻烦,必须使用一种变通的方法。
每当需要一个未引用的名字时,变通的方法就是在命名空间中搜索所有名字,并测试对应的对象是否是相关对象。
这就是 get_callable_name()
函数的作用,正如 entropy 所说。
.
使用这个函数就能正常工作。
但我想强调,这只是一个巧妙的变通方法,并没有真正的基础。
我的意思是,方法名 t.TestFunc
是一种错觉。这里有个微妙之处:没有方法属于某个实例。虽然这听起来有点奇怪,但我相信这就是事实。
我们通过像 t.TestFunc
这样的表达式调用方法,容易让人误以为 TestFunc
属于这个实例。实际上,它属于类,Python 是通过实例找到它的类来获取方法的。
我并不是在胡说,我是看过的:
一个类的实例有一个以字典实现的命名空间,这是查找属性引用的第一站。当在这里找不到某个属性时,如果实例的类有一个同名属性,搜索将继续在类属性中进行。如果找到的类属性是一个用户定义的函数对象或一个未绑定的用户定义方法对象,并且其关联的类是实例的类(称为 C)或其基类之一,则它会被转换为一个绑定的用户定义方法对象,其
im_class
属性是 C,im_self
属性是实例。http://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes
不过,这又是另一个故事,我想我会被争论,我没有时间参与其中。
只需验证以下几点:
尽管 getattr(t,"TestFunc")
返回:<bound method Test.TestFunc of <__main__.Test instance at 0x011D8DC8>>
但方法 TestFunc 并不在 t
的命名空间中:t.__dict__
的结果是 { }
!
我只是想指出这一点,因为 get_callable_name()
函数只是复制和模仿了 Python 的表面行为和实现。
然而,实际的行为和底层实现是不同的。
.
在下面的代码中,我通过使用指令return '%s.%s' % ('t', X.__name__)
得到了正确的结果,而不是return '%s.%s' % (f_self.__class__.__name__, func.__name__)
或者return '%s.%s' % (variable_name_in_module(__import__(f_self.__module__), f_self), func.__name__)
因为这基本上就是 get_callable_name()
函数的作用(它并没有使用正常的过程,而是使用了一种巧妙的方法)。
def get_callable_name(X):
"""
Returns the best available display name for the given function/callable.
"""
print '- inside get_callable_name()'
print ' object X arriving in get_callable_name() :\n ',X
f_self = getattr(X, '__self__', None) or getattr(X, 'im_self', None)
print ' X.__call__ ==',X.__call__
print ' X.__name__ ==',X.__name__
print '\n X.__self__== X.im_self ==',f_self
print ' isinstance(%r, type) is %r' % (f_self,isinstance(f_self, type))
if f_self and hasattr(X, '__name__'): # it is a method
if isinstance(f_self, type):
# class method
clsname = getattr(f_self, '__qualname__', None) or f_self.__name__
return '%s.%s' % (clsname, X.__name__)
# bound method
print '\n f_self.__class__ ==',f_self.__class__
print ' f_self.__class__.__name__ ==',f_self.__class__.__name__
return '%s.%s' % ('t', X.__name__)
if hasattr(X, '__call__'):
if hasattr(X, '__name__'):
# function, unbound method or a class with a __call__ method
return X.__name__
# instance of a class with a __call__ method
return X.__class__.__name__
raise TypeError('Unable to determine a name for %s -- '
'maybe it is not a callable?' % repr(X))
def obj_to_ref(obj):
"""
Returns the path to the given object.
"""
print '- obj arriving in obj_to_ref :\n %r' % obj
ref = '%s:%s' % (obj.__module__, get_callable_name(obj))
return ref
def ref_to_obj(ref):
"""
Returns the object pointed to by ``ref``.
"""
print '- ref arriving in ref_to_obj == %r' % ref
if not isinstance(ref, basestring):
raise TypeError('References must be strings')
if not ':' in ref:
raise ValueError('Invalid reference')
modulename, rest = ref.split(':', 1)
try:
obj = __import__(modulename)
except ImportError:
raise LookupError('Error resolving reference %s: '
'could not import module' % ref)
print ' we start with dictionary obj == ',obj
try:
for name in modulename.split('.')[1:] + rest.split('.'):
print ' object of name ',name,' searched in',obj
obj = getattr(obj, name)
print ' got obj ==',obj
return obj
except Exception:
raise LookupError('Error resolving reference %s: '
'error looking up object' % ref)
class Test:
def TestFunc(self):
print 'this is Test::TestFunc method'
t = Test()
print 't ==',t
print '\nt.TestFunc ==',t.TestFunc
print "getattr(t,'TestFunc') ==",getattr(t,'TestFunc')
print ('\nTrying to obtain reference of t.TestFunc\n'
'----------------------------------------')
print '- REF = obj_to_ref(t.TestFunc) done'
REF = obj_to_ref(t.TestFunc)
print '\n- REF obtained: %r' % REF
print ("\n\nVerifying what is ref_to_obj(REF)\n"
"---------------------------------")
try:
print '- obj2 = ref_to_obj(REF) done'
obj2 = ref_to_obj(REF)
if obj2 != t.TestFunc:
raise ValueError
except Exception:
raise ValueError('Cannot determine the object of reference %s' % REF)
print '\n- object obtained : ',obj2
结果
t == <__main__.Test instance at 0x011DF5A8>
t.TestFunc == <bound method Test.TestFunc of <__main__.Test instance at 0x011DF5A8>>
getattr(t,'TestFunc') == <bound method Test.TestFunc of <__main__.Test instance at 0x011DF5A8>>
Trying to obtain reference of t.TestFunc
----------------------------------------
- REF = obj_to_ref(t.TestFunc) done
- obj arriving in obj_to_ref :
<bound method Test.TestFunc of <__main__.Test instance at 0x011DF5A8>>
- inside get_callable_name()
object X arriving in get_callable_name() :
<bound method Test.TestFunc of <__main__.Test instance at 0x011DF5A8>>
X.__call__ == <method-wrapper '__call__' of instancemethod object at 0x011DB990>
X.__name__ == TestFunc
X.__self__== X.im_self == <__main__.Test instance at 0x011DF5A8>
isinstance(<__main__.Test instance at 0x011DF5A8>, type) is False
f_self.__class__ == __main__.Test
f_self.__class__.__name__ == Test
- REF obtained: '__main__:t.TestFunc'
Verifying what is ref_to_obj(REF)
---------------------------------
- obj2 = ref_to_obj(REF) done
- ref arriving in ref_to_obj == '__main__:t.TestFunc'
we start with dictionary obj == <module '__main__' (built-in)>
object of name t searched in <module '__main__' (built-in)>
got obj == <__main__.Test instance at 0x011DF5A8>
object of name TestFunc searched in <__main__.Test instance at 0x011DF5A8>
got obj == <bound method Test.TestFunc of <__main__.Test instance at 0x011DF5A8>>
- object obtained : <bound method Test.TestFunc of <__main__.Test instance at 0x011DF5A8>>
>>>