2011-11-29 26 views
11

mam około 20 sposobów na przekierowanie do metody otoki, które ma oryginalny sposób, a reszta argumentów:Programowe generowanie metod klasy

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... 

Jak mogę dodać więcej tych metod go programowo? Jest to o ile mi się zanim wszystko zaczyna wyglądać źle:

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

Mogę to zrobić poprzez przypisanie w definicji klasy, czy coś innego, że czuje się bardziej naturalne?

class my_socket(parent): 

    def makes_recv_methods(name): 
     # wraps call to name 

    def recv_meh = makes_recv_methods('recv_meh') 

wolałbym używać __get__ i przyjaciół, kiedy to możliwe na magicznych funkcji z types.

+0

Czy nie byłaby kodu zrobić 'ostry stuff' w' @ decorator' - czy jest tam coś I don” T get tutaj? – Kimvais

+0

@Kimvais: Tak, ale w jaki sposób zwiążę dekorowaną funkcję z nazwą z powrotem do klasy? Skończę z '@decorator ('recv_into'): def recv_into (self): pass', prawda? –

+0

Jaki jest sens "super", gdy nie ma nadklasy? –

Odpowiedz

8

Zrobiłbym to, uruchamiając jakiś kod, aby wygenerować metody z listy po zdefiniowaniu klasy - można to umieścić w dekoratorze.

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

Myślę, że Matt chce wywołać metodę w '_in', może po zmodyfikowaniu argumentów. Zamiast tego możesz przekazać związaną metodę jako pierwszy argument: 'self._in (owinięty .__ get __ (self, cls), * args, ** kwargs)'. – eryksun

+0

Ach, tak - zmieniając się w to. – babbageclunk

+0

Po przeczytaniu tego nonsensu zdecydowałem się odrzucić. Jest tyle niedogodności, że wywołuję mdłości. Mam nadzieję, że w końcu otrzymamy anonimowe funkcje. –

0

Wilberforce propozycja działa, ale jest prostszy sposób przy użyciu tylko OOP:

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

Niestety nie mogę wprowadzać zmian w klasie nadrzędnej. –

+0

Prośba dotyczyła _add metod programowo_ ... – gecco

+0

cóż, nie szkodzi, stanowiąc alternatywę dla meta-programowania. Głosowałem za odpowiedzią wilberforce, ale zgadzam się z @Matt. Operacja próbuje osiągnąć coś niebezpiecznego. Powinien spróbować zaprojektować swój kod, aby mógł uciec bez takich sztuczek. – Simon

-1

Można użyć cog.

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

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

to ma inne zalety łatwo aktualizowane, nie dotykając kodu poza komentarza końcowego i można kręcić wygenerowany kod, jeśli to konieczne.

Z powodzeniem użyłem trybiki do generowania szablonu na innym projekcie, w połączeniu z nie wygenerowanym kodem. Zaczęło się od czytania pliku wejściowego instrukcji do słownika. Następnie dla każdej sekcji tablicy podstawowej użył tego fragmentu słownika, aby wiedzieć, co napisać.

Edytuję plik instrukcji w jednym miejscu, zamiast w dwudziestu różnych miejscach w płycie głównej.

+1

IMHO, generowanie kodu nigdy nie jest potrzebne w pythonie, ponieważ zawsze możesz znaleźć sposób na refaktorowy zestaw znaków – Simon

+0

@Simon Podstawowym językiem, w którym używałem cog, nie był python. :) A w zależności od tego, co chce zrobić, generowanie kodu ma sens. To właśnie robi kompilator lub interpreter, zabiera instrukcje i kody wyjściowe. –

0

Chciałbym rozwinąć akceptowaną odpowiedź. Chciałem mieć bardzo długą listę metod dekoratora zastosowanych do bardzo długiej listy metod.

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 

I tu jest klasa, która owija oryginalny

@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 
Powiązane problemy