【问题标题】:Dynamically created method and decorator, got error 'functools.partial' object has no attribute '__module__'动态创建的方法和装饰器,得到错误 'functools.partial' 对象没有属性 '__module__'
【发布时间】:2014-01-02 20:33:12
【问题描述】:

我目前正在使用 EndpointsModel 为我在 AppEngine 上的所有模型创建一个 RESTful API。由于是 RESTful,这些 api 有很多我想避免的重复代码。

例如:

class Reducer(EndpointsModel):
    name = ndb.StringProperty(indexed=False)

@endpoints.api(
    name="bigdata",
    version="v1",
    description="""The BigData API""",
    allowed_client_ids=ALLOWED_CLIENT_IDS,
)
class BigDataApi(remote.Service):
    @Reducer.method(
        path="reducer",
        http_method="POST",
        name="reducer.insert",
        user_required=True,
    )
    def ReducerInsert(self, obj):
        pass

    ## and GET, POST, PUT, DELETE
    ## REPEATED for each model

我想让它们变得通用。所以我尝试向类动态添加方法。 到目前为止我尝试过的:

from functools import partial, wraps

def GenericInsert(self, obj, cls):
    obj.owner = endpoints.get_current_user()
    obj.put()
    return obj

# Ignore GenericDelete, GenericGet, GenericUpdate ...

import types
from functools import partial

def register_rest_api(api_server, endpoint_cls):
    name = endpoint_cls.__name__

    # create list method 
    query_method = types.MethodType(
    endpoint_cls.query_method(
        query_fields=('limit', 'pageToken'),
        path="%ss" % name,
        http_method="GET",
        name="%s.list" % name,
        user_required=True
    )(partial(GenericList, cls=endpoint_cls)))

    setattr(api_server, "%sList", query_method)

    # create insert method
    # ...

register_rest_api(BigDataApi, Reducer)

但我得到了'functools.partial' object has no attribute '__module__' exception. 我认为是因为endpoints.method的装饰器和部分装饰器之间存在一些冲突。但不知道如何避免它。

Traceback (most recent call last):
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 239, in Handle
    handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 298, in _LoadHandler
    handler, path, err = LoadObject(self._handler)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 84, in LoadObject
    obj = __import__(path[0])
  File "/Users/Sylvia/gcdc2013/apis.py", line 795, in <module>
    register_rest_api(BigDataApi, Reducer)
  File "/Users/Sylvia/gcdc2013/apis.py", line 788, in register_rest_api
    )(partial(GenericList, cls=endpoint_cls)))
  File "/Users/Sylvia/gcdc2013/endpoints_proto_datastore/ndb/model.py", line 1544, in RequestToQueryDecorator
    @functools.wraps(api_method)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'functools.partial' object has no attribute '__module__'

相关文章:

【问题讨论】:

  • 这个问题可能会更短,例如,只需解释GenericInsert 的问题。
  • 不仔细看,似乎如果您有命名冲突,那么from functools import partial -> import functools 甚至from functools import partial as part 或其他可能会起作用。
  • 问题的本质是 functools.wraps (被端点代码使用,但不是以任何方式错误或不寻常的方式)期望被包装的可调用对象具有 __module__ 属性(除其他外)。您传递的方法没有。 bugs.python.org/issue3445 表明这被认为对于 python 3.3 是值得修复的,但对于 2.7 则不是。所以你可能只需要以不同的方式做事......没有太多的答案,抱歉。
  • @Greg 谢谢,该链接对解释问题非常有用
  • @Greg。您还建议通过哪些其他方式来修复一个函数的参数?

标签: python google-app-engine google-cloud-endpoints functools endpoints-proto-datastore


【解决方案1】:

我也偶然发现了这一点,我真的很惊讶,对我来说,问题是部分对象缺少某些属性,特别是 __module__ 和 __name__

默认情况下wraps 使用functools.WRAPPER_ASSIGNMENTS 更新属性,无论如何在python 2.7.6 中默认为('__module__', '__name__', '__doc__'),有几种方法可以处理这个...

更新仅存在属性...

import functools
import itertools

def wraps_safely(obj, attr_names=functools.WRAPPER_ASSIGNMENTS):
    return wraps(obj, assigned=itertools.ifilter(functools.partial(hasattr, obj), attr_names))

>>> def foo():
...     """ Ubiquitous foo function ...."""
... 
>>> functools.wraps(partial(foo))(foo)()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'functools.partial' object has no attribute '__module__'
>>> wraps_safely(partial(foo))(foo)()
>>> 

这里我们只是过滤掉所有不存在的属性。

另一种方法是严格仅处理部分对象,您可以将wraps 与singledispatch 折叠并创建包装的部分对象,其属性将从最深中获取em> func 属性。

类似的东西:

import functools

def wraps_partial(wrapper, *args, **kwargs):
    """ Creates a callable object whose attributes will be set from the partials nested func attribute ..."""
    wrapper = wrapper.func
    while isinstance(wrapper, functools.partial):
        wrapper = wrapper.func
    return functools.wraps(wrapper, *args, **kwargs)

def foo():
    """ Foo function.
    :return: None """
    pass

>>> wraps_partial(partial(partial(foo)))(lambda : None).__doc__
' Foo Function, returns None '
>>> wraps_partial(partial(partial(foo)))(lambda : None).__name__
'foo'
>>> wraps_partial(partial(partial(foo)))(lambda : None)()
>>> pfoo = partial(partial(foo))
>>> @wraps_partial(pfoo)
... def not_foo():
...     """ Not Foo function ... """
... 
>>> not_foo.__doc__
' Foo Function, returns None '
>>> not_foo.__name__
'foo'
>>>

这稍微好一些,因为现在我们可以得到原始函数文档,之前默认使用部分对象文档字符串。

这可以修改为仅在当前部分对象还没有 set 属性时才搜索,嵌套许多部分对象时应该会稍微快一些...

更新

python(CPython) 3(至少 3.4.3)似乎没有这个问题,因为我不知道也不应该假设所有版本的 python 3 或 Jython 等其他实现也有这个问题这是另一种为未来做好准备的方法

from functools import wraps, partial, WRAPPER_ASSIGNMENTS

try:
    wraps(partial(wraps))(wraps)
except AttributeError:
    @wraps(wraps)
    def wraps(obj, attr_names=WRAPPER_ASSIGNMENTS, wraps=wraps):
        return wraps(obj, assigned=(name for name in attr_names if hasattr(obj, name))) 

有几点需要注意:

  • 我们定义一个新的 wraps 函数仅当我们无法包装部分,以防将来的 python2 版本或其他版本解决此问题。
  • 我们使用原始wraps 复制文档和其他信息
  • 我们不使用 ifilter,因为它在 python3 中被删除,我已经计算了有和没有 ifilter 的时间,但结果尚无定论,至少在 python (CPython) 2.7.6 中,差异很小最好的方法...

【讨论】:

    【解决方案2】:

    在 Python 3.5 中,我发现在 partial 中维护了对原始函数的引用。您可以通过.func 访问它:

    from functools import partial
    
    def a(b):
        print(b)
    
    
    In[20]:  c=partial(a,5)
    
    In[21]:  c.func.__module__
    Out[21]: '__main__'
    
    In[22]:  c.func.__name__
    Out[22]: 'a'
    

    【讨论】:

    • 在 Python 2.7.16 中也是如此
    【解决方案3】:

    如果它是由 functools 中的“wraps”问题引起的,那么没有什么能阻止您编写自己的不调用 wraps 的部分。根据python文档,这是partial的有效实现:

    def partial(func, *args, **keywords):
        def newfunc(*fargs, **fkeywords):
            newkeywords = keywords.copy()
            newkeywords.update(fkeywords)
            return func(*(args + fargs), **newkeywords)
        newfunc.func = func
        newfunc.args = args
        newfunc.keywords = keywords
        return newfunc
    

    【讨论】:

      【解决方案4】:

      我偶然发现了这一点,并认为会提到我的解决方法。

      正如@samy-vilar 正确提到的那样,python3 没有这个问题。 我有一些使用 functools.wrap 的代码,需要在 python2 和 python3 上运行。

      对于python2,我们使用functools32,它是python3 的python2 functools 的反向移植。 wraps 这个包的实现非常完美。此外,它还提供了 lru_cache,它只在 python3 functools 中可用。

      import sys 
      
      if sys.version[0] == '2':
         from functools32 import wraps
      else:
         from functools import wraps
      

      【讨论】:

        【解决方案5】:

        在我们的例子中,我通过子类化 functools.partial 解决了这个问题:

        class WrappablePartial(functools.partial):
        
            @property
            def __module__(self):
                return self.func.__module__
        
            @property
            def __name__(self):
                return "functools.partial({}, *{}, **{})".format(
                    self.func.__name__,
                    self.args,
                    self.keywords
                )
        
            @property
            def __doc__(self):
                return self.func.__doc__
        

        注意您也可以使用 __getattr__ 来重定向查询,但我认为这实际上不太可读(并且像使用 __name__ 一样插入任何有用的元数据变得更加困难)

        【讨论】:

          【解决方案6】:

          这里描述了一个非常方便的 python 2.7 解决方案:http://louistiao.me/posts/adding-name-and-doc-attributes-to-functoolspartial-objects/

          即:

          from functools import partial, update_wrapper
          
          def wrapped_partial(func, *args, **kwargs):
              partial_func = partial(func, *args, **kwargs)
              update_wrapper(partial_func, func)
          
              return partial_func
          

          【讨论】:

            【解决方案7】:

            从 Python 2.7.11 开始,此问题已得到修复(不确定它是在哪个特定版本中修复的)。您可以在 2.7.11 中对 functools.partial 对象执行 functools.wraps。

            【讨论】:

            猜你喜欢
            • 2019-11-13
            • 2019-11-05
            • 1970-01-01
            • 1970-01-01
            • 2016-03-31
            • 2021-10-18
            • 2014-10-02
            • 2019-06-16
            • 1970-01-01
            相关资源
            最近更新 更多