Я застрял с этой проблемой в Python в течение некоторого времени: мне нужно создать список методов внутри класса, которые делают почти то же самое, но с разными переменными-членами экземпляра. Итак, моя первая попытка выглядит примерно так:

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

Как видите, я использую getattr и setattr для ссылки на атрибуты, которые мне нужно изменить.

Это работает, но на самом деле медленно, потому что частичное сохраняет только параметры, а при вызове функции передает их исходной функции. Кроме того, я не уверен в этом, но не getattr и setattr немного медленнее, чем использовать что-то вроде object.property

Итак, мне удалось получить вторую попытку:

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

На этот раз я использовал не партиалы, а закрытие. Основное преимущество заключается в том, что getattr выполняется только один раз при создании объекта экземпляра, а не при вызове методов, но я не могу найти способ избавиться от setattr. И как побочный эффект это не работает, как я ожидал. getattr получает значение свойства в начале, поэтому любые изменения этих свойств не будут видны возвращаемым функциям.

Так что теперь я застрял. Есть ли способ генерировать такой метод:

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

Дали имена свойств?

Спасибо.

0
dospro 3 Мар 2018 в 02:11

3 ответа

Лучший ответ
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 Мар 2018 в 00:10

Хорошо, после долгих экспериментов я нашел решение, хотя оно довольно нелегкое.

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']

Так как это можно оценить при инициализации, оно делает именно то, что мне нужно. Большой компромисс - элегантность, удобочитаемость, обработка ошибок и т. Д. Я думаю, что решение Пола Корнелиуса достаточно для этого случая использования. Хотя я могу рассмотреть использование шаблонов для создания кода Python

Спасибо за помощь.

0
dospro 3 Мар 2018 в 01:50

Как отметил dospro в своем комментарии, выполнение getattr только один раз является ошибкой. Ваше закрытие будет использовать устаревшие значения в последующих вызовах.

Что касается производительности, вы должны получить ее, используя атрибут __dict__ напрямую, а не setattr / getattr.

Чтобы понять, почему прямой доступ к __dict__ быстрее, чем к getattr / setattr, мы можем взглянуть на сгенерированный байт-код:

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 преобразуется в вызов функции, тогда как запись в __dict__ - это одна операция хранилища.

0
Maciej Kozik 3 Мар 2018 в 00:08