2011-11-29 45 views
11

我有大約20個方法重定向到一個包裝方法採用原始方法,以及的參數的其餘部分的方法:編程方式產生的一類

class my_socket(parent): 

    def _in(self, method, *args, **kwargs): 
     # do funky stuff 

    def recv(self, *args, **kwargs): 
     return self._in(super().recv, *args, **kwargs) 

    def recv_into(self, *args, **kwargs): 
     return self._in(super().recv_into, *args, **kwargs) 

    # and so on... 

我怎麼可以添加更多的這些方法編程?這是關於據我得到的一切纔開始找錯了:通過類的定義,還是其他什麼東西,感覺更自然內分配

for method in 'recv', 'recvfrom', 'recvfrom_into', 'recv_into', ...: 
    setattr(my_socket, method, ???) 

我能做到這一點?

class my_socket(parent): 

    def makes_recv_methods(name): 
     # wraps call to name 

    def recv_meh = makes_recv_methods('recv_meh') 

我寧願使用__get__和朋友時,可能在魔術函數從types

+0

你不能重構代碼做'時髦stuff'在'@ decorator'類 - 或者是有什麼我不你來這裏? – Kimvais

+0

@Kimvais:是的,但是我將如何將帶有名字的裝飾函數綁定到類?我最終會用'@decorator('recv_into'):def recv_into(self):pass',對吧? –

+0

當沒有超類時,「super」有什麼意義? –

回答

8

我會通過運行一些代碼在類定義完成後從列表中生成方法來實現這一點 - 您可以將其放入裝飾器中。

import functools 

def wrap_method(cls, name): 
    # This unbound method will be pulled from the superclass. 
    wrapped = getattr(cls, name) 
    @functools.wraps(wrapped) 
    def wrapper(self, *args, **kwargs): 
     return self._in(wrapped.__get__(self, cls), *args, **kwargs) 
    return wrapper 

def wrap_methods(cls): 
    for name in cls.WRAP_ATTRS: 
     setattr(cls, name, wrap_method(cls, name)) 
    return cls 

@wrap_methods 
class my_socket(parent_class): 
    WRAP_ATTRS = ['recv', 'recvfrom'] # ... + more method names 

    def _in(self, method, *args, **kwargs): 
     # do funky stuff 
+0

我想馬特想在'_in'中調用方法,也許在修改參數後。你可以通過綁定的方法作爲第一個參數:'self._in(wrap .__ get __(self,cls),* args,** kwargs)'。 – eryksun

+0

啊,是的 - 改變並納入。 – babbageclunk

+0

在讀完這些廢話後,我決定反對。有那麼多間接讓我噁心。我希望我們最終能夠獲得匿名功能。 –

0

威爾伯福斯提案工作,但僅使用OOP一個簡單的方法:你可以使用cog

def wrap_method(wrapped): 
    @functools.wraps(wrapped) 
    def wrapper(self, *args, **kwargs): 
     return self._in(wrapped.__get__(self, cls), *args, **kwargs) 
    return wrapper 

class Parent: 

    def _in(self, method, *args, **kwargs): 
     return method(*args, **kwargs) 


    @wrap_method 
    def recv(self, *args, **kwargs): 
     return # whatever 

    @wrap_method 
    def recv_into(self, *args, **kwargs): 
     return # whatever 

class MySocket(Parent): 

    def _in(self, method, *args, **kwargs): 
     # do funky stuff 
+0

不幸的是,我不能對父類進行更改。 –

+0

請求是以編程方式_add方法_ ... – gecco

+0

好吧,沒有危害我提供了一個元編程的替代方案。我投了威爾伯斯的答案,但我傾向於同意@Matt。該操作正試圖實現某種危險。他應該嘗試設計他的代碼,以便他可以不用這樣的技巧逃脫。 – Simon

-1

class MySocket(Parent): 
"""[[[cog 
import cog 
l = ['in','out'] 
for item in l: 
    cog.outl("def _{0}(self, method, *args, **kwargs):".format(item)) 

]]]""" 
#[[[end]]] 

這樣做很容易被更新,不觸及結束註釋之外的代碼的附加優勢,並在必要時你可以玩弄生成的代碼。

我已經成功地使用cog在另一個項目上生成樣板,與非生成的代碼混合在一起。它開始將一個輸入文件的指令讀入字典中。然後,對於樣板的每一部分,它都使用字典的那一部分來知道要寫什麼。

我在一個位置編輯指令文件,而不是樣板中的20個不同位置。

+1

恕我直言,代碼生成是永遠不需要在python中,因爲你總是可以找到一種方法來重構樣板 – Simon

+0

@Simon我使用的主要語言不是python。 :)取決於他想做什麼,代碼生成非常合理。這就是編譯器或解釋器所做的,它會根據您的指示輸出代碼。 –

0

我想擴大接受的答案。我想可能有很長的修飾器方法列表應用於很長的方法列表。

import functools 


def wrap_method(cls, name, wrapper_method_name): 
    # This unbound method will be pulled from the superclass. 
    wrapped = getattr(cls, name, wrapper_method_name) 

    @functools.wraps(wrapped) 
    def wrapper(self, *args, **kwargs): 
     wrapper_method = getattr(self, wrapper_method_name) 
     return wrapper_method(wrapped.__get__(self, cls), *args, **kwargs) 

    return wrapper 


def wrap_methods(cls): 
    for wrapper_method_name in cls.WRAPPER_METHOD_NAMES: 
     for name in cls.WRAPPED_METHODS: 
      setattr(cls, name, wrap_method(cls, name, wrapper_method_name)) 
    return cls 

這裏是一個包裝原

@wrap_methods 
class WrappedConnection(BaseConnection): 
    """ 
    This class adds some quality-of-life improvements to the BaseConnection class. 
    -WRAPPED_METHODS are wrapped by WRAPPER_METHOD_NAMES 
    -wrappers can be toggled on and off. 

    example: 
    connection = WrappedConnection(show_messages=True, log_errors=False, keep_authenticated=False) 

    default: 
    connection = WrappedConnection(show_messages=False, log_errors=True, keep_authenticated=True) 
    """ 
    WRAPPER_METHOD_NAMES = ['log_errors', 'keep_authenticated', 'show_messages'] 
    WRAPPED_METHODS = ['a_method', 'b_method', 'c_method', 'd_method'] 
    MESSAGE_OVERRIDE_MAP = {"a_method": "a_method_message_override_attribute", 
          "b_method": "b_method_message_override_attribute"} 

    def keep_authenticated(self, method, *args, **kwargs): 
     """ 
     If the session has expired, the session is re-authenticated. The incident is logged by the default logger. 
     This option can be turned off by setting keep_authenticated during initialization of a WrappedConnection object. 
     - connection = WrappedConnection(keep_authenticated=False) # why would you ever do this 


     :param method: (method) method to be wrapped 
     :param args: (args) passed args 
     :param kwargs: (kwargs) passed kwargs 
     :return: (method) method wrapped by @keep_authenticated 
     """ 
     response, expired_session = method(*args, **kwargs), None 
     if response["errors"] and self._keep_authenticated: 
      expired_session = list(filter(lambda x: 'expired session' in x, response["errors"])) 
     if expired_session: 
      self.__init__() 
      logging.info('Session has been re-authenticated.') 
      response = method(*args, **kwargs) 
     return response 

    def log_errors(self, method, *args, **kwargs): 
     """ 
     If there is an error the incident is logged. This option can be turned off by setting log_errors 
     during initialization of a WrappedConnection object. 
     - connection = WrappedConnection(log_errors=False) 

     :param method: (method) method to be wrapped 
     :param args: (args) passed args 
     :param kwargs: (kwargs) passed kwargs 
     :return: (method) method wrapped by @log_errors 
     """ 
     response = method(*args, **kwargs) 
     if response["errors"] and self._log_errors: 
      errors = response["errors"] 
      logging.error(errors) 
     return response 

    def show_messages(self, method, *args, **kwargs): 
     """ 
     Shows the xml that is sent during the request. This option can be turned on by setting show_messages during 
     initialization of a WrappedConnection object. 
     - connection = WrappedConnection(show_messages=True) 

     :param method: (method) method to be wrapped 
     :param args: (args) passed args 
     :param kwargs: (kwargs) passed kwargs 
     :return: (method) method wrapped by @show_messages 
     """ 
     response = method(*args, **kwargs) 
     if self._show_messages: 
      message_override_attr = WrappedConnection.MESSAGE_OVERRIDE_MAP.get(method.__name__) 
      if message_override_attr: 
       message_override = getattr(self, message_override_attr) 
       print(BeautifulSoup(message_override, "xml").prettify()) 
      else: 
       self._show_message(method.__name__, *args, **kwargs) 
     return response 

    def __init__(self, *args, keep_authenticated=True, log_errors=True, show_messages=False, **kwargs): 
     super(WrappedConnection, self).__init__(*args, **kwargs) 
     self._keep_authenticated = keep_authenticated 
     self._log_errors = log_errors 
     self._show_messages = show_messages