2017-02-15 19 views
2

Это мой первый вопрос здесь, после долгого консультирования. Я много разбираюсь в этой проблеме, и, думаю, я не нашел полезной информации, рассматривающей ее.functools.wraps возвращает __dict__ типа dict вместо типа mappingproxy

Я пытаюсь создать декоративный декодер python для моего кода в Python 3. Цель состоит в том, чтобы правильно обернуть функцию или класс, выполнив две вещи: напечатать предупреждение об отказе и добавить __doc__ с информацией об устаревании (которая его можно обработать также sphinx). Я думаю, что я разобрал большую часть того, что хотел, я все еще боюсь с Sphinx, но я нашел кое-что любопытное о поведении functools.wraps. В принципе, __dict__ обернутого класса имеет тип dict, а в развернутом классе - тип mappingproxy.

Вот некоторые витрина код:

import functools 

class Deprecated(object): 
    def __call__(self, cls): 
     myassigns = functools.WRAPPER_ASSIGNMENTS 
     myassigns += ('__mro__',) 

     @functools.wraps(cls, assigned=myassigns) 
     def new_func(*args, **kwargs): 
      warnings.warn(message, category=DeprecationWarning, stacklevel=2) 
      return cls(*args, **kwargs) 

     return new_func 


@Deprecated() 
class SomeOldClass: 
    def some_method(self, x, y): 
     return x + y 

class SomeClass: 
    def some_method(self, x, y): 
     return x + y 

print(type(SomeOldClass.__dict__)) 
print(type(SomeClass.__dict__)) 

И выход:

<class 'dict'> 
<class 'mappingproxy'> 

ли functools.wraps или Сбой в я делаю что-то не так?

+3

Ваш «завернутый класс» - это функция. Что вы ожидали от 'functools.wraps'? – user2357112

+0

Возможно, вы имели в виду 'some_method'? Поскольку, как есть, вы завернули сам класс, какие работы (не выглядели глубоко, но я считаю, что он должен предупреждать, когда вы создаете экземпляр), но это также означает, что это имя относится к функции-оболочке, а не к классу больше, поэтому на «SomeOldClass» не видно никаких связанных с классом вещей без явного сверления с атрибутом '__wrapped__'. Если вы хотите обесценить классы таким образом, ваша обертка должна свернуться в класс и полностью отключить '__init__' или' __new__' (возвращая исходные 'cls'), а не возвращать функцию, переносящую класс. – ShadowRanger

+0

@ user2357112 Я ожидаю, что он замаскирует мою функцию обертывания, так что появляется, если это оригинальный класс, за исключением моих изменений. Конкретно, я хотел, чтобы функции, подобные 'help()', работали правильно в моем устаревшем классе. – j3mdamas

ответ

0

я настоятельно рекомендую сделать ваш класс обертка известно, поэтому он не завернуть сам класс:

class Deprecated(object): 
    def __init__(self, messagefmt='{wrapped} is deprecated'): 
     self.messagefmt = messagefmt 

    def __call__(self, wrapped): 
     message = self.messagefmt.format(wrapped=wrapped.__name__) 
     wrappingclass = isinstance(wrapped, type) 
     if wrappingclass: 
      wrappedfunc = wrapped.__init__ 
     else: 
      wrappedfunc = wrapped 

     @functools.wraps(wrappedfunc) 
     def new_func(*args, **kwargs): 
      warnings.warn(message, category=DeprecationWarning, stacklevel=2) 
      return wrappedfunc(*args, **kwargs) 
     wrapped.__doc__ = 'Deprecated\n\n' + message + '\n\n' + (wrapped.__doc__ or '') 

     if wrappingclass: 
      wrapped.__init__ = new_func 
      return wrapped 
     else: 
      return new_func 

Это позволяет использовать его с обоими классами:

@Deprecated() 
class SomeOldClass: 
    def some_method(self, x, y): 
     return x + y 

без затемнения поведение класса и с функциями (возможно, с пользовательским сообщением об отказе):

@Deprecated('foo is dead') 
def foo(): 
    pass 

Итак, когда вы используете их:

>>> import warnings 
>>> warnings.filterwarnings('always', category=DeprecationWarning) 
>>> SomeOldClass() 
/usr/local/bin/ipython3:1: DeprecationWarning: SomeOldClass is deprecated 
    #!/usr/bin/python3 
<__main__.SomeOldClass at 0x7fea8f744a20> 
>>> foo() 
/usr/local/bin/ipython3:1: DeprecationWarning: foo is dead 
    #!/usr/bin/python3 

И аналогично, __doc__ для обоего модифицируются предварять устаревание уведомления (вы можете настроить его, чтобы Sphinxify, я просто обеспечение основного примера).

+0

Большое вам спасибо! Это тот ответ, который я искал! Я собираюсь проверить это, но я уверен, что это работает. – j3mdamas

+0

@ j3mdamas: Я проверил его на показанных примерах (и убедился, что он может правильно изменить строки '__doc__', что' class' остался классом и т. Д.), Но я уверен, что есть некоторые случаи краев (для Например, мой тест для класса против не-класса не является дружественным по стилю в стиле Py2, поэтому вам нужно немного его настроить для поддержки классов старого стиля). Несмотря на это, он должен быть достаточно близок, с минимальной настройкой. – ShadowRanger

+0

Просто небольшой комментарий: для методов '__doc__' не обновлялся. Я переместил назначение до '@ functools.wraps (wrappedfunc)', и он начал работать. – j3mdamas

0

functools.wrap не ошибается, и вы не совершаете что-то не так, вы вернули функцию, которая возвращает при вызове.

После упаковки, SomeOldClass функция new_func вы завернуты и вернулись:

>>> SomeOldClass 
<function __main__.SomeOldClass> 

не класс. Не оформленный SomeClass сохраняется как класс, который __dict__ является mappingproxy.

+0

Спасибо за ваш ответ! Трудно найти рабочие примеры украшенных Классов, поэтому я пошел с этим подходом. – j3mdamas