动态创建的方法和装饰器,出现错误"functools.partial"对象没有属性"__module__" [英] Dynamically created method and decorator, got error 'functools.partial' object has no attribute '__module__'

查看:139
本文介绍了动态创建的方法和装饰器,出现错误"functools.partial"对象没有属性"__module__"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am currently using EndpointsModel to create a RESTful API for all my models on AppEngine. Since it is RESTful, these api have a lot of repeat code which I want to avoid.

例如:

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

我想让它们变得通用.因此,我尝试将动态添加方法添加到类中. 到目前为止,我已经尝试过:

I want to make them become generic. So I try to dynamic add method to the class. What I have tried so far:

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的装饰器和局部装饰器之间存在一些冲突.但是不知道如何避免.

But I got 'functools.partial' object has no attribute '__module__' exception. I think it is because there are some conflicts between endpoints.method's decorator and partial. But no idea how to avoid it.

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__'

相关文章:

  • Class method differences in Python: bound, unbound and static
  • Python - can I programmatically decorate class methods from a class instance?
  • Programmatically generate methods for a class
  • Adding a Method to an Existing Object Instance

推荐答案

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

I also stumbled upon this, I was really surprised, for me the issue was that partial objects are missing certain attributes, specifically __module__ and __name__

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

Being that wraps by default uses functools.WRAPPER_ASSIGNMENTS to update attributes, which defaults to ('__module__', '__name__', '__doc__') in python 2.7.6 anyway, there are a couple ways of dealing with this ...

更新仅存在个属性...

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)()
>>> 

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

Here we simply filter out all those attribute which aren't present.

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

Another approach would be to strictly deal with partial objects only, you could fold wraps with singledispatch and create wrapped partial objects whose attributes would be taken from the deepest func attribute.

大致内容:

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'
>>>

这会稍微好一点,因为现在我们可以获取原始函数docs,而这些函数在默认之前使用部分对象doc字符串.

This is slightly better since now we can get the original functions docs which before defaulted to using the partial objects doc string.

可以将其修改为仅在当前部分对象尚不具有set属性的情况下进行搜索,当嵌套许多部分对象时,该属性应该会稍微快一点……

This can be modified to only search if the current partial object doesn't already have the set attribute, which should be slightly faster when nesting many partial objects ...

更新

似乎python(CPython)3(至少3.4.3)没有此问题,因为我不知道也不应该假设python 3的所有版本或其他实现(例如Jython)也存在此问题这是另一种面向未来的方法

It seems that python(CPython) 3 (at least 3.4.3) doesn't have this issue, since I don't know nor should I assume all versions of python 3 or other implementations such as Jython also share this issue here is another future ready approach

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和不使用ifilter的情况下都进行了计时,但结果不确定,至少在python(CPython)2.7.6中,其差值在无论哪种方式最好...
  • we define a new wraps function only if we fail to wrap a partial, in case future versions of python2 or other versions fix this issue.
  • we use the original wraps to copy the docs and other info
  • we don't use ifilter since it was removed in python3, I've timeit with and without ifilter but the results where inconclusive, at least in python (CPython) 2.7.6, the difference was marginal at best either way...

这篇关于动态创建的方法和装饰器,出现错误"functools.partial"对象没有属性"__module__"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆