动态创建方法和装饰器,得到错误'functools.partial'对象没有属性'__module__'

luc*_*mia 18 python google-app-engine functools google-cloud-endpoints endpoints-proto-datastore

我目前正在使用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
Run Code Online (Sandbox Code Playgroud)

我想让它们变得通用.所以我尝试动态添加方法到类.到目前为止我尝试了什么:

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)
Run Code Online (Sandbox Code Playgroud)

但是我'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__'
Run Code Online (Sandbox Code Playgroud)

相关文章:

更新

  1. 完成调用堆栈
  2. 缩短问题

Sam*_*lar 10

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

作为在wraps默认情况下使用functools.WRAPPER_ASSIGNMENTS更新的属性,默认为('__module__', '__name__', '__doc__')在Python 2.7.6反正有处理这个的几种方法...

更新当前属性...

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)()
>>> 
Run Code Online (Sandbox Code Playgroud)

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

另一种方法是严格与部分对象只处理,你可以折叠wrapssingledispatch创造包裹局部对象,其属性将取自最深的 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'
>>>
Run Code Online (Sandbox Code Playgroud)

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

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

UPDATE

似乎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))) 
Run Code Online (Sandbox Code Playgroud)

有几点需要注意:

  • 我们定义一个新wraps函数只有当我们未能包装部分时,以防未来版本的python2或其他版本修复此问题.
  • 我们使用原件wraps来复制文档和其他信息
  • 我们不使用,ifilter因为它已经在python3中被删除了,我有时间和没有ifilter但是结果没有结果,至少在python(CPython)2.7.6中,差异在最好的方式是最小的...


Seb*_*zny 6

在 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'
Run Code Online (Sandbox Code Playgroud)