2012-01-23 30 views
6

Tôi đã có một vấn đề thú vị sáng nay. Tôi đã có một lớp cơ sở trông như thế này:Trang trí một phương pháp đã là một classmethod?

# base.py 
class Base(object): 

    @classmethod 
    def exists(cls, **kwargs): 
     # do some work 
     pass 

Và một module trang trí trông như thế này:

# caching.py 

# actual caching decorator 
def cached(ttl): 
    # complicated 

def cached_model(ttl=300): 
    def closure(model_class): 
     # ... 
     # eventually: 
     exists_decorator = cached(ttl=ttl) 
     model_class.exists = exists_decorator(model_class.exists)) 

     return model_class 
    return closure 

Dưới đây là mô hình lớp con của tôi:

@cached_model(ttl=300) 
class Model(Base): 
    pass 

Thing là, khi tôi thực sự gọi Model.exists, tôi nhận được khiếu nại về số lượng sai đối số! Kiểm tra các đối số trong trang trí cho thấy không có gì lạ xảy ra - các đối số là chính xác những gì tôi mong đợi, và chúng phù hợp với chữ ký phương thức. Làm thế nào tôi có thể thêm các trang trí thêm vào một phương pháp đã được trang trí với classmethod?

Không phải tất cả các mô hình đều được lưu trong bộ nhớ cache, nhưng phương thức tồn tại() có trên mọi mô hình như một lớp học, vì vậy việc sắp xếp lại trang trí không phải là một tùy chọn: cached_model có thể thêm classmethod vào() tồn tại() một classmethod trên mô hình uncached?

+1

Vì vậy, giải pháp là gì? Nó không rõ ràng. Nó sẽ tốt hơn nhiều nếu bạn để lại câu hỏi của bạn như nó đã được, và đăng một câu trả lời. – Marcin

+1

Bạn * có thể * đăng câu hỏi và tự trả lời câu hỏi, nhưng hãy giữ riêng câu hỏi và trả lời. Xem http://meta.stackexchange.com/questions/17463/can-i-answer-my-own-questions-even-those-where-i-knew-the-answer-before-asking – delnan

+0

Tôi nghĩ bạn đã quên '@ classmethod' trong lớp' Base'. –

Trả lời

1

Trình trang trí classmethod thực sự thêm một đối số class cho các cuộc gọi đến phương thức, trong một số trường hợp nhất định, theo như tôi có thể biết, ngoài việc ràng buộc phương thức với lớp.Các giải pháp đã được chỉnh sửa lớp trang trí đóng cửa của tôi:

def cached_model(ttl=300): 
    def closure(model_class): 
     # ... 
     # eventually: 
     exists_decorator = cached(ttl=ttl, cache_key=exists_cache_key) 
     model_class.exists = classmethod(exists_decorator(model_class.exists.im_func)) 

     return model_class 
    return closure 

Thuộc tính im_func xuất hiện để có được một tham chiếu đến các chức năng ban đầu, cho phép tôi để đạt được trong và trang trí chức năng ban đầu với bộ nhớ đệm trang trí của tôi, và sau đó quấn toàn bộ mà lộn xộn trong một cuộc gọi classmethod. Tóm tắt, classmethod đồ trang trí không được xếp chồng, vì các đối số dường như được tiêm.

5

Trong Python, khi một phương thức được khai báo, trong một hàm chức năng, nó giống hệt như hàm - khi lớp được phân tích cú pháp và tồn tại, truy lục phương thức thông qua "." toán tử biến đổi hàm đó - khi đang bay - thành một phương thức. Biến đổi này không thêm tham số đầu tiên phương pháp này (nếu nó không phải là một staticmethod) -

vậy:

>>> class A(object): 
... def b(self): 
...  pass 
... 
>>> A.b is A.b 
False 

Becasue từng nhặt đồ của "b" thuộc tính của "A" mang lại một trường hợp khác nhau "phương pháp đối tượng b"

>>> A.b 
<unbound method A.b> 

chức năng gốc "b" có thể được lấy ra mà không cần bất kỳ trasnform nếu một trong những hiện

>>> A.__dict__["b"] 
<function b at 0xe36230> 

Đối với một chức năng được trang trí với @classmethod chỉ là tương tự xảy ra, và "đẳng cấp" giá trị được bổ sung vào danh sách tham số khi nó được lấy ra từ A.

Các @classmethod@staticmethod trang trí sẽ quấn chức năng cơ bản trong một mô tả khác nhau so với instancemethod bình thường. Một đối tượng classmethod - là hàm mà một hàm trở thành khi nó được bọc với classmethod là một đối tượng mô tả, có phương thức '__get__' sẽ trả về một hàm bao hàm hàm cơ bản - và thêm tham số "cls" trước tất cả các hàm khác .

Bất kỳ người trang trí nào khác để @classmethod phải "biết" nó thực sự đang xử lý đối tượng mô tả, không phải là hàm. -

>>> class A(object): 
... @classmethod 
... def b(cls): 
...  print b 
... 
>>> A.__dict__["b"] 
<classmethod object at 0xd97a28> 

Vì vậy, nó là dễ dàng hơn nhiều để cho các @classmethod trang trí là người cuối cùng được áp dụng cho các phương pháp (người đầu tiên trên stack) - vì vậy mà các trang trí khác làm việc trên một chức năng đơn giản (biết rằng đối số "cls" sẽ được chèn vào như là đối số đầu tiên).

+1

Đúng, nhưng đôi khi bạn không có quyền kiểm soát thứ tự của trang trí. Trong trường hợp của tôi, phương thức luôn luôn là một classmethod, nhưng chỉ một số lớp có bộ trang trí thêm bộ nhớ đệm - đó là phân lớp cụ thể. Trong trường hợp đó, bạn cần một cách để 'hoàn tác' trang trí classmethod đầu tiên. Xem câu trả lời của tôi cho những gì tôi đã kết thúc với. –

2

Nhờ jsbueno để biết thông tin về Python. Tôi đang tìm câu trả lời cho câu hỏi này dựa trên trường hợp của decorating all methods of a class. Dựa trên tìm kiếm một câu trả lời cho câu hỏi này và phản ứng jsbueno, tôi đã có thể thu thập một cái gì đó dọc theo dòng:

def for_all_methods(decorator): 

    def decorate(cls): 

     for attr in dir(cls): 
      possible_method = getattr(cls, attr) 
      if not callable(possible_method): 
       continue 

      # staticmethod 
      if not hasattr(possible_method, "__self__"): 
       raw_function = cls.__dict__[attr].__func__ 
       decorated_method = decorator(raw_function) 
       decorated_method = staticmethod(decorated_method) 

      # classmethod 
      elif type(possible_method.__self__) == type: 
       raw_function = cls.__dict__[attr].__func__ 
       decorated_method = decorator(raw_function) 
       decorated_method = classmethod(decorated_method) 

      # instance method 
      elif possible_method.__self__ is None: 
       decorated_method = decorator(possible_method) 

      setattr(cls, attr, decorated_method) 

     return cls 
    return decorate 

Có một chút dư thừa và một vài biến thể bạn có thể sử dụng để cắt này xuống một chút.

0

Chỉ cần một ví dụ chức năng để thêm vào câu trả lời tuyệt vời Scott Lobdell của ...

messages.py

from distutils.cmd import Command 

import functools 
import unittest 

def for_all_methods(decorator): 

    def decorate(cls): 

     for attr in cls.__dict__: 
      possible_method = getattr(cls, attr) 
      if not callable(possible_method): 
       continue 

      # staticmethod 
      if not hasattr(possible_method, "__self__"): 
       raw_function = cls.__dict__[attr].__func__ 
       decorated_method = decorator(raw_function) 
       decorated_method = staticmethod(decorated_method) 

      # classmethod 
      if type(possible_method.__self__) == type: 
       raw_function = cls.__dict__[attr].__func__ 
       decorated_method = decorator(raw_function) 
       decorated_method = classmethod(decorated_method) 


      # instance method 
      elif possible_method.__self__ is None: 
       decorated_method = decorator(possible_method) 

      setattr(cls, attr, decorated_method) 

     return cls 

    return decorate 

def add_arguments(func): 
    """ 
    The add_arguments decorator simply add the passed in arguments 
    (args and kwargs) the returned error message. 
    """  
    @functools.wraps(func) 
    def wrapped(self, *args, **kwargs): 
     try: 
      message = func(self, *args, **kwargs) 
      message = ''.join([message, 
           "[ args:'", str(args), "'] ", 
           "[ kwargs:'", str(kwargs), "' ] " 
           ]) 
      return message 

     except Exception as e: 
      err_message = ''.join(["errorhandler.messages.MESSAGE: '", 
            str(func), 
            "(", str(args), str(kwargs), ")' ", 
            "FAILED FOR UNKNOWN REASON. ", 
            " [ ORIGINAL ERROR: ", str(e), " ] " 
            ]) 
      return err_message 

    return wrapped 



@for_all_methods(add_arguments)  
class MESSAGE(object): 
    """ 
      log.error(MSG.triggerPhrase(args, kwargs)) 

    """  
    @classmethod 
    def TEMPLATE(self, *args, **kwargs): 
     message = "This is a template of a pre-digested message." 
     return message 

sử dụng

from messages import MESSAGE 

if __name__ == '__main__': 
    result = MESSAGE.TEMPLATE(1,2,test=3) 
    print result 

đầu ra

This is a template of a pre-digested message.[ args:'(1, 2)'] [ kwargs:'{'test': 3}' ] 
Các vấn đề liên quan