Подтвердить что ты не робот

Динамически созданный метод и декоратор, получивший ошибку "functools.partial", объект не имеет атрибута "__module__"

В настоящее время я использую EndpointsModel для создания RESTful API для всех моих моделей в AppEngine. Так как это 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 decorator и частичным. Но не знаю, как этого избежать.

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

Статьи по Теме:

4b9b3361

Ответ 1

Я также наткнулся на это, я был очень удивлен, для меня проблема заключалась в том, что частичным объектам не хватает определенных атрибутов, в частности __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)()
>>> 

Здесь мы просто отфильтровываем все те атрибуты, которые не присутствуют.

Другим подходом было бы строго иметь дело только с частичными объектами, вы могли бы сложить wraps с помощью singledispatch и создать обернутые частичные объекты, атрибуты которых будут взяты из самого глубокого атрибута 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'
>>>

Это немного лучше, так как теперь мы можем получить исходные функции docs, которые до того, как были дефолтны для использования строки doc partial objects.

Это может быть изменено только для поиска, если текущий частичный объект еще не имеет атрибута 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))) 

нужно отметить пару вещей:

  • мы определяем новую wraps функцию только в том случае, если мы не скроем частичный, если будущие версии python2 или других версий исправят эту проблему.
  • мы используем оригинал wraps для копирования документов и другой информации
  • мы не используем ifilter, так как он был удален в python3, у меня есть timeit с и без 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'

Ответ 3

Если дело в том, что это вызвано проблемой с "обертками" в functools, нет ничего, что помешает вам написать свой собственный частичный, который не вызывает обертывания. Согласно документации python, это допустимая реализация частичного:

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, который является backport для python3 functools для python2. 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__

NB, вы также можете использовать __getattr__ для перенаправления запросов, но я решил, что это на самом деле менее читаемо (и затрудняет вставку любых полезных метаданных, как с __name __)

Ответ 7

Эта проблема исправлена ​​с Python 2.7.11 (не уверен, какой конкретный релиз был исправлен). Вы можете сделать functools.wraps в объекте functools.partial в 2.7.11.