2011-11-29 16 views
11

Tengo cerca de 20 métodos para redirigir a un método de envoltura que tiene el método original, y el resto de los argumentos:programación generar métodos para una clase

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

¿Cómo se puede añadir más de programáticamente estos métodos? Esto es lo más lejos que llegue antes de que todo empieza a verse mal:

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

¿Puedo hacer esto mediante la asignación dentro de la definición de clase, o cualquier otra cosa que se siente más natural?

class my_socket(parent): 

    def makes_recv_methods(name): 
     # wraps call to name 

    def recv_meh = makes_recv_methods('recv_meh') 

yo prefiero usar __get__ y amigos cuando sea posible sobre funciones mágicas de types.

+0

¿No puedes refactorizar el código para hacer el 'stuff' cobarde en un' @ decorator' - O hay algo I don' ¿llegar aquí? – Kimvais

+0

@Kimvais: Sí, pero ¿cómo vincularé la función decorada con un nombre a la clase? Voy a terminar con '@decorator ('recv_into'): def recv_into (self): pass', ¿verdad? –

+0

¿Cuál es el punto de 'super' cuando no hay superclase? –

Respuesta

8

Lo hago ejecutando un código para generar los métodos de una lista después de definir la clase; puede poner esto en un decorador.

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

Creo que Matt quiere llamar al método en '_in', tal vez después de modificar los argumentos. En su lugar, podría pasar el método bound como primer argumento: 'self._in (wrapped .__ get __ (self, cls), * args, ** kwargs)'. – eryksun

+0

Ah, sí, cambiando para incorporar eso. – babbageclunk

+0

Después de leer esas tonterías he decidido no hacerlo. Hay tanta indirección que me da náuseas. Espero que eventualmente obtengamos funciones anónimas. –

0

propuesta wilberforce funciona, pero hay una manera más sencilla utilizando sólo POO:

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

Lamentablemente no puedo hacer cambios en la clase principal. –

+0

La solicitud fue _add métodos programmatically_ ... – gecco

+0

bien, no hay daño Proporciono una alternativa a la meta-programación. Voté la respuesta de wilberforce, pero tiendo a estar de acuerdo con @Matt. La operación está tratando de lograr algo peligroso. Debería tratar de diseñar su código para que pueda escapar sin trucos como este. – Simon

-1

Usted podría utilizar 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]]] 

Esto tiene la ventaja adicional de ser actualizado fácilmente, sin tocar el código fuera del comentario final, y se puede girar el código generado si es necesario.

He utilizado con éxito el cog para generar texto estándar en otro proyecto, mezclado con el código no generado. Comenzó leyendo un archivo de entrada de instrucciones en un diccionario. Luego, para cada sección de repetición, utilizó esa parte del diccionario para saber qué escribir.

Edito el archivo de instrucciones en un solo lugar, en lugar de veinte lugares diferentes en el texto estándar.

+1

En mi humilde opinión, la generación de código nunca es necesaria en Python, ya que siempre se puede encontrar una forma de refactorizar el estándar – Simon

+0

@Simon El idioma principal con el que cog cog no era python. :) Y dependiendo de lo que él quiera hacer, la generación de código tiene mucho sentido. Eso es lo que hace un compilador o intérprete, toma el código de tus instrucciones y salidas. –

0

Me gustaría ampliar la respuesta aceptada. Quería tener potencialmente una larga lista de métodos de decorador aplicados a una larga lista de métodos.

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 

Y aquí es la clase que envuelve el original

@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 
Cuestiones relacionadas