Динамически созданный метод и декоратор, получивший ошибку "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__'
Статьи по Теме:
Ответы
Ответ 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 __)
Ответ 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 (не уверен, какой конкретный релиз был исправлен). Вы можете сделать functools.wraps
в объекте functools.partial
в 2.7.11.