【问题标题】:Textual reference of a method方法的文本参考
【发布时间】:2013-02-19 22:03:01
【问题描述】:

假设我有以下内容:

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.TestFuncobj_to_ref 函数提供__main__:Test.TestFunc 作为文本表示,但不能使用相同的文本生成对象。

问题:

Python 中是否有一种方法可以表示像

这样的对象
>>> t.TestFunc
<bound method Test.TestFunc of <__main__.Test instance at 0xb771b28c>>
>>> 

在字符串中并从字符串中重建对象?

如果我们将地址0xb771b28c保存为字符串的一部分并通过取消引用该地址重新生成对象,是否有可能?!

【问题讨论】:

  • 实际用例是什么?你会用它做什么?至于实际重构:可以测试eval('t.TestFunc')或者ref_to_obj('__main__:t.TestFunc')
  • 你好。 for name in modulename.split('.')[1:] + rest.split('.'): 这条线应该怎么做?在您的代码中modulename.split('.')[1:] 给出[ ]
  • 我认为问题出在get_callable_nameget_callable_name(t.TestFunc) 产生 'Test.TestFunc'。这显然是错误的,因为它看起来像是指向类方法而不是绑定方法。
  • @entropy 如果get_callable_name() 为假,您必须证明该缺陷。我个人认为它没有缺陷。这就是为产生这种明显怪异的类实现的 Python 模型和函数。
  • @eyquem,如果您想将t.TestFunc 作为字符串的唯一路径,它应该是:'__main__:t.TestFunc' 而不是'__main__:Test.TestFunc',这是get_callable_name 的结果。如果您随后使用ref_to_obj 解析从get_callable_name 获得的结果,您将获得对未绑定类方法而不是绑定实例方法的引用。在下面的答案中检查我的代码,它确实有效,修复在get_callable_name

标签: python serialization pickle apscheduler


【解决方案1】:

正如我在上面的评论中所说,问题出在get_callable_nameget_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 字典查找,其中 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))

编辑:PS:variable_name_in_module 如果找不到任何东西,它可能应该抛出异常,尽管我不知道这是怎么发生的。

【讨论】:

  • 从某种意义上说,你是对的。问题是函数get_callable_name() 没有返回一个好的引用,从该引用中可以获得与起始对象(t.TestFunc)相同的对象,也就是说一个绑定方法。
  • 但是你的函数 variable_name_in_module() 是一个基于 Python 欺骗性功能的技巧:我的意思是,当调用 t.TestFunc 完成时,Python 找不到名为 TestFunc 的方法在实例t 中,它进入实例的类以获取并执行其中的方法。虽然如此到达的方法被描述为“绑定方法”,但与通过Test.TestFunc 到达同一个方法时的“未绑定方法”不同,它只是一种唯一的方法。
  • 那么为了尊重 Python 从t.TestFunc 到类中存在的方法test.TestFunc 的欺骗方式,t.TestFunc 必须由get_callable_name() 返回!
  • 但是variable_name_in_module()返回的值不是通过基于数据模型的逻辑方式获得的,就像它是由方法的__name__属性产生的那样。这就是为什么我认为遵循 Python 已经很特殊的行为是一个棘手的绕过。
【解决方案2】:

你的问题很有趣,但很纠结。

1) 你不应该调用funcget_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 获得的引用(在我的代码中传递给参数 X)是 '__main__:Test.TestFunc' ,而不是 '__main__:t.TestFunc' 因为它“应该”是.

正如entropy所说,决定这一点的秘密步骤是在函数get_callable_name()中。
由于f.selft 并且X 有一个名称(TestFunc)但不是type 类型的类(因为t 是一个实例),
指令return '%s.%s' % (f_self.__class__.__name__, X.__name__) 被执行。

但是你把表达式 f_self.__class__.__name 写错了:它是 t 的类的名称,而不是 t 本身的名称。

.

问题在于,对于一​​个类(具有属性__name__)来说,Python 语言中没有任何内容可以按需提供实例的名称:实例具有不同类型的属性@987654342 @ 作为一个类,它将给出实例的名称。

因此,由于难以获得它,因此必须采用某种绕过方式。
每次需要一个未引用的名称时,绕过方法是在命名空间的所有名称中进行搜索,并针对相关对象测试相应的对象。
这就是 entropy 的函数 get__callable_name() 的作用。

.

有了这个功能,它就可以工作了。

但我想强调的是,这只是一个棘手的绕过,没有真正的基础。
我的意思是该方法的名称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") 给出了:
&lt;bound method Test.TestFunc of &lt;__main__.Test instance at 0x011D8DC8&gt;&gt;
方法 TestFunc 不在 t 的命名空间中: t.__dict__ 的结果是{ }

我只是想指出这一点,因为函数 get_callable_name() 只复制和模仿 Python 的明显行为和实现。
然而,真正的行为和实现是不同的。

.

在以下代码中,我通过使用 istruction
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_callanle_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>>
>>>

【讨论】:

    猜你喜欢
    • 2012-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-28
    相关资源
    最近更新 更多