2011-06-18 28 views
52

Khi xác định trang trí bằng cách sử dụng một lớp, làm cách nào để tự động chuyển qua __name__, __module____doc__? Thông thường, tôi sẽ sử dụng trang trí @wraps từ functools. Đây là những gì tôi đã làm thay vì cho một lớp học (đây không phải là hoàn toàn mã của tôi):Tương đương Python functools.wraps cho các lớp

class memoized: 
    """Decorator that caches a function's return value each time it is called. 
    If called later with the same arguments, the cached value is returned, and 
    not re-evaluated. 
    """ 
    def __init__(self, func): 
     super().__init__() 
     self.func = func 
     self.cache = {} 

    def __call__(self, *args): 
     try: 
      return self.cache[args] 
     except KeyError: 
      value = self.func(*args) 
      self.cache[args] = value 
      return value 
     except TypeError: 
      # uncacheable -- for instance, passing a list as an argument. 
      # Better to not cache than to blow up entirely. 
      return self.func(*args) 

    def __repr__(self): 
     return self.func.__repr__() 

    def __get__(self, obj, objtype): 
     return functools.partial(self.__call__, obj) 

    __doc__ = property(lambda self:self.func.__doc__) 
    __module__ = property(lambda self:self.func.__module__) 
    __name__ = property(lambda self:self.func.__name__) 

Có trang trí chuẩn để tự động tạo mô-đun tên và tài liệu không? Ngoài ra, để tự động hóa phương thức get (tôi giả sử đó là để tạo các phương thức bị ràng buộc?) Có phương pháp nào bị thiếu không?

Trả lời

28

Mọi người dường như đã bỏ lỡ giải pháp rõ ràng.

>>> import functools 
>>> class memoized(object): 
    """Decorator that caches a function's return value each time it is called. 
    If called later with the same arguments, the cached value is returned, and 
    not re-evaluated. 
    """ 
    def __init__(self, func): 
     self.func = func 
     self.cache = {} 
     functools.update_wrapper(self, func) ## TA-DA! ## 
    def __call__(self, *args): 
     pass # Not needed for this demo. 

>>> @memoized 
def fibonacci(n): 
    """fibonacci docstring""" 
    pass # Not needed for this demo. 

>>> fibonacci 
<__main__.memoized object at 0x0156DE30> 
>>> fibonacci.__name__ 
'fibonacci' 
>>> fibonacci.__doc__ 
'fibonacci docstring' 
+10

' __name__' và '__doc__' được đặt trên _instance_ , nhưng không phải là lớp, đó là những gì luôn được sử dụng bởi 'help (instance)'. Để khắc phục, việc triển khai trang trí dựa trên lớp không thể được sử dụng và thay vào đó, trình trang trí phải được thực hiện như một hàm. Để biết chi tiết, hãy xem http://stackoverflow.com/a/25973438/1988505. –

+0

Phải, điều này không làm việc để được giúp đỡ (fibonacci) –

+1

Tôi không chắc tại sao câu trả lời của tôi đột nhiên bị đánh dấu xuống ngày hôm qua. Không ai hỏi về việc giúp đỡ() để làm việc. Trong 3.5, inspect.signature() và inspect.from_callable() có một tùy chọn follow_wrapped mới; có lẽ giúp đỡ() nên làm như vậy? – samwyse

18

Tôi không nhận thức được những điều như vậy trong stdlib, nhưng chúng tôi có thể tạo riêng của chúng tôi nếu chúng ta cần.

Something như thế này có thể làm việc:

from functools import WRAPPER_ASSIGNMENTS 


def class_wraps(cls): 
    """Update a wrapper class `cls` to look like the wrapped.""" 

    class Wrapper(cls): 
     """New wrapper that will extend the wrapper `cls` to make it look like `wrapped`. 

     wrapped: Original function or class that is beign decorated. 
     assigned: A list of attribute to assign to the the wrapper, by default they are: 
      ['__doc__', '__name__', '__module__', '__annotations__']. 

     """ 

     def __init__(self, wrapped, assigned=WRAPPER_ASSIGNMENTS): 
      self.__wrapped = wrapped 
      for attr in assigned: 
       setattr(self, attr, getattr(wrapped, attr)) 

      super().__init__(wrapped) 

     def __repr__(self): 
      return repr(self.__wrapped) 

    return Wrapper 

Cách sử dụng:

Output:

<function fibonacci at 0x14627c0> 
__doc__: fibonacci docstring 
__name__: fibonacci 

EDIT:

Và nếu bạn đang tự hỏi tại sao điều này đã không được bao gồm trong stdlib là bởi vì bạn có thể quấn trang trí lớp học của bạn trong một chức năng decorater và sử dụng functools.wraps như thế này:

def wrapper(f): 

    memoize = memoized(f) 

    @functools.wraps(f) 
    def helper(*args, **kws): 
     return memoize(*args, **kws) 

    return helper 


@wrapper 
def fibonacci(n): 
    """fibonacci docstring""" 
    if n <= 1: 
     return n 
    return fibonacci(n-1) + fibonacci(n-2) 
+0

Cảm ơn mouad. Bạn có biết mục đích của phương pháp '__get__' là gì không? –

+0

Ồ, tôi thấy: nó làm cho người trang trí làm việc với các phương pháp? Nó có lẽ nên ở class_wraps sau đó? –

+0

@Neil: Có Để biết thêm chi tiết: http://stackoverflow.com/questions/5469956/python-decorator-self-is-mixed-up/5470017#5470017, IMO tôi không nghĩ vậy vì nó sẽ vi phạm một trong các nguyên tắc mà tôi tin vào chức năng hoặc lớp học là ** trách nhiệm duy nhất **, trong trường hợp của 'class_wraps' sẽ là __Update một lớp bao bọc để trông giống như gói .__ không ít hơn không nhiều :) – mouad

0

Một giải pháp khác sử dụng thừa kế:

import functools 
import types 

class CallableClassDecorator: 
    """Base class that extracts attributes and assigns them to self. 

    By default the extracted attributes are: 
     ['__doc__', '__name__', '__module__']. 
    """ 

    def __init__(self, wrapped, assigned=functools.WRAPPER_ASSIGNMENTS): 
     for attr in assigned: 
      setattr(self, attr, getattr(wrapped, attr)) 
     super().__init__() 

    def __get__(self, obj, objtype): 
     return types.MethodType(self.__call__, obj) 

Và, sử dụng:

class memoized(CallableClassDecorator): 
    """Decorator that caches a function's return value each time it is called. 
    If called later with the same arguments, the cached value is returned, and 
    not re-evaluated. 
    """ 
    def __init__(self, function): 
     super().__init__(function) 
     self.function = function 
     self.cache = {} 

    def __call__(self, *args): 
     try: 
      return self.cache[args] 
     except KeyError: 
      value = self.function(*args) 
      self.cache[args] = value 
      return value 
     except TypeError: 
      # uncacheable -- for instance, passing a list as an argument. 
      # Better to not cache than to blow up entirely. 
      return self.function(*args) 
+0

Lý do bạn không nên sử dụng điều này là vì, như bạn thấy, bạn phải gọi phương thức '__init__' của các lớp cha (không nhất thiết chỉ là' super() '; bạn nên google cho' python order python') . – ninjagecko

+0

@ninjagecko: Không phải là đến lớp siêu để gọi phương thức '__init__' của các lớp cha khác? –

+0

Nó phần nào của một câu hỏi mở, theo như tôi biết, tôi có thể là sai mặc dù. http://fuhm.net/super-harmful/ Cũng http://stackoverflow.com/questions/1385759/should-init-call-the-parent-classs-init dường như không chỉ ra bất kỳ sự đồng thuận nào. – ninjagecko

1

Tất cả chúng ta thực sự cần phải làm là thay đổi hành vi của các trang trí sao cho nó là "hợp vệ sinh", tức là nó là bảo toàn thuộc tính.

#!/usr/bin/python3 

def hygienic(decorator): 
    def new_decorator(original): 
     wrapped = decorator(original) 
     wrapped.__name__ = original.__name__ 
     wrapped.__doc__ = original.__doc__ 
     wrapped.__module__ = original.__module__ 
     return wrapped 
    return new_decorator 

Đây là tất cả những gì bạn cần. Nói chung. Nó không bảo vệ chữ ký, nhưng nếu bạn thực sự muốn rằng bạn có thể sử dụng một thư viện để làm điều đó. Tôi cũng đã đi trước và viết lại mã ghi nhớ để nó hoạt động trên các đối số từ khóa. Ngoài ra còn có một lỗi mà không chuyển đổi nó thành một bộ băm có thể làm cho nó không hoạt động trong 100% các trường hợp.

Bản trình diễn được viết lại memoized trang trí với @hygienic sửa đổi hành vi của nó. memoized bây giờ là hàm kết thúc lớp gốc, mặc dù bạn có thể (như câu trả lời khác) viết một lớp gói thay thế, hoặc thậm chí tốt hơn, cái gì đó phát hiện nếu nó là một lớp và nếu như vậy kết thúc tốt đẹp phương pháp __init__.

@hygienic 
class memoized: 
    def __init__(self, func): 
     self.func = func 
     self.cache = {} 

    def __call__(self, *args, **kw): 
     try: 
      key = (tuple(args), frozenset(kw.items())) 
      if not key in self.cache: 
       self.cache[key] = self.func(*args,**kw) 
      return self.cache[key] 
     except TypeError: 
      # uncacheable -- for instance, passing a list as an argument. 
      # Better to not cache than to blow up entirely. 
      return self.func(*args,**kw) 

Trong hành động:

@memoized 
def f(a, b=5, *args, keyword=10): 
    """Intact docstring!""" 
    print('f was called!') 
    return {'a':a, 'b':b, 'args':args, 'keyword':10} 

x=f(0) 
#OUTPUT: f was called! 
print(x) 
#OUTPUT: {'a': 0, 'b': 5, 'keyword': 10, 'args':()}     

y=f(0) 
#NO OUTPUT - MEANS MEMOIZATION IS WORKING 
print(y) 
#OUTPUT: {'a': 0, 'b': 5, 'keyword': 10, 'args':()}   

print(f.__name__) 
#OUTPUT: 'f' 
print(f.__doc__) 
#OUTPUT: 'Intact docstring!' 
+0

Thao tác này không hoạt động đối với ví dụ được ghi nhớ. –

+0

@Neil: ở đó chúng tôi đi, cập nhật – ninjagecko

+0

@hygienic không hoạt động cho mã nơi lớp trang trí được bao bọc có thuộc tính lớp. Giải pháp của Mouad hoạt động. Vấn đề được báo cáo là: đối tượng 'AttributeError: 'function' không có thuộc tính 'level'' khi cố gắng làm' decoratorclassname.level + = 1' bên trong '__call__' – cfi

1

tôi cần cái gì đó sẽ quấn cả hai lớp và các hàm và viết này:

def wrap_is_timeout(base): 
    '''Adds `.is_timeout=True` attribute to objects returned by `base()`. 

    When `base` is class, it returns a subclass with same name and adds read-only property. 
    Otherwise, it returns a function that sets `.is_timeout` attribute on result of `base()` call. 

    Wrappers make best effort to be transparent. 
    ''' 
    if inspect.isclass(base): 
     class wrapped(base): 
      is_timeout = property(lambda _: True) 

     for k in functools.WRAPPER_ASSIGNMENTS: 
      v = getattr(base, k, _MISSING) 
      if v is not _MISSING: 
       try: 
        setattr(wrapped, k, v) 
       except AttributeError: 
        pass 
     return wrapped 

    @functools.wraps(base) 
    def fun(*args, **kwargs): 
     ex = base(*args, **kwargs) 
     ex.is_timeout = True 
     return ex 
    return fun 
+0

Lưu ý phụ, tôi mời mọi người sử dụng thành ngữ '.is_timeout = True' này để đánh dấu thời gian chờ của bạn gây ra lỗi và chấp nhận API này từ gói. – temoto

Các vấn đề liên quan