He estado atrapado con este problema en python desde hace algún tiempo: necesito crear una lista de métodos dentro de una clase que haga casi lo mismo pero con diferentes variables miembro de instancia. Entonces mi primer intento es algo como esto:

from functools import partial


class Operations:
    def __init__(self):
        self.a = 10
        self.b = 20
        self.c = 30
        self.add_operation_1 = partial(self.generic_add_1, 'a', 'b')
        self.add_operation_2 = partial(self.generic_add_1, 'a', 'c')

    def generic_add_1(self, first, second):
        first_value = getattr(self, first)
        second_value = getattr(self, second)
        setattr(self, first, first_value + second_value)


instance = Operations()
instance.add_operation_1()
print(instance.a)
# Should print 30
instance.add_operation_2()
print(instance.a)
# Should print 60

Como puede ver, uso getattr y setattr para hacer referencia a los atributos que necesito cambiar.

Esto funciona, pero es realmente lento porque parcial solo mantiene los parámetros y cuando se llama a la función, los pasa a la función original. Además, no estoy seguro de esto, pero no son getattr y setattr un poco más lentos que usar algo como object.property

Entonces logré hacer un segundo intento:

class Operations:
    def __init__(self):
        self.a = 10
        self.b = 20
        self.c = 30
        self.add_operation_1 = self.generic_add('a', 'b')
        self.add_operation_2 = self.generic_add('a', 'c')

    def generic_add(self, first, second):
        first_value = getattr(self, first)
        second_value = getattr(self, second)

        def real_operation():
            setattr(self, first, first_value + second_value)

        return real_operation


instance = Operations()
instance.add_operation_1()
print(instance.a)

# Should print 30

instance.add_operation_2()
print(instance.a)
# Should print 60 but returns 40 instead!!!

Esta vez no utilicé parciales, sino un cierre. La principal ventaja es que getattr solo se ejecuta una vez cuando se crea el objeto de instancia y no cuando se invocan los métodos, pero no puedo encontrar una manera de deshacerme de setattr. Y como efecto secundario, esto no funciona como esperaba. getattr obtiene el valor de la propiedad al principio, por lo que las funciones devueltas no verán ningún cambio en esas propiedades.

Así que ahora estoy un poco atrapado. ¿Hay alguna manera de generar un método como este:

def expected_function(self):
    self.a = self.a + self.b

Dados los nombres de las propiedades?

Gracias.

0
dospro 3 mar. 2018 a las 02:11

3 respuestas

La mejor respuesta
def function_generate(v, s1, s2):
    def f():
        v[s1] += v[s2]
    return f

class Operations:
    def __init__(self):
        self.a = 10
        self.b = 20
        self.c = 30
        namespace = vars(self)
        self.add_operation_1 = function_generate(namespace, 'a', 'b')
        self.add_operation_2 = function_generate(namespace, 'a', 'c')


instance = Operations()
instance.add_operation_1()
print(instance.a)
# Should print 30
instance.add_operation_2()
print(instance.a)
# Should print 60
2
Paul Cornelius 3 mar. 2018 a las 00:10

Ok, después de experimentar mucho, encontré una solución, aunque es bastante poco elegante.

def generic_eval_add(self, first, second):
        my_locals = {
            'self': self
        }
        string_code = """def real_operation():
    self.{0} = self.{0} + self.{1}""".format(first, second)

        print(string_code)
        exec(string_code, my_locals)

        return my_locals['real_operation']

Como esto se puede evaluar en la inicialización, hace exactamente lo que necesitaba. Las grandes compensaciones son elegancia, legibilidad, manejo de errores, etc. Creo que la solución de Paul Cornelius es lo suficientemente buena para este caso de uso. Aunque puedo considerar la creación de plantillas jinja para generar código python

Gracias por tu ayuda.

0
dospro 3 mar. 2018 a las 01:50

Como señaló dospro en su comentario, la ejecución de getattr solo una vez es un error. Su cierre utilizará valores obsoletos en llamadas posteriores.

Sobre el rendimiento, debería ganar algo usando el atributo __dict__ directamente en lugar de usar setattr / getattr.

Para tener una idea de por qué acceder a __dict__ directamente es más rápido que getattr / setattr, podemos ver el bytecode generado:

self.__dict__['a'] = 1

0 LOAD_CONST               1 (1)
2 LOAD_FAST                0 (self)
4 LOAD_ATTR                0 (__dict__)
6 LOAD_CONST               2 ('a')
8 STORE_SUBSCR

setattr(self, 'a', 1)

0 LOAD_GLOBAL              0 (setattr)
2 LOAD_FAST                0 (self)
4 LOAD_CONST               1 ('a')
6 LOAD_CONST               2 (1)
8 CALL_FUNCTION            3
10 POP_TOP

Setattr se traduce en una llamada de función, mientras que escribir en __dict__ es una operación de almacenamiento.

0
Maciej Kozik 3 mar. 2018 a las 00:08