Существует ли эквивалент частичного метода для свойств?

#python #properties #python-descriptors

Вопрос:

У меня есть класс со многими параметрами, которые хранятся в словаре, как показано ниже (на практике это немного сложнее, но это дает хорошую идею). Я могу «автоматически генерировать» методы получения и установки, используя partialmethod :

 for a_name in ['x', 'y', 'z']:
  locals()["get_"   a_name] = partialmethod(_get_arg, 
                                            arg_name=a_name) 
  locals()["set_"   a_name] = partialmethod(_set_arg, 
                                            arg_name=a_name) 
 

В идеале я хотел бы использовать @property , но только если я смогу «автогенерировать» @property , @setter и @deleter . Возможно ли это? Второй фрагмент показывает свойства, добавленные «вручную»; Я рассматриваю возможность использования эквивалента partialmethod , чтобы избежать повторения и недостижимого кода.

 class C:
    def __init__(self):
        self.kwargs = {'x' : 0, 'y' : 3, 'z' : True}

    def __get_arg(self, arg_name):
        assert arg_name in self.kwargs
        return self.kwargs[arg_name]

    def __set_arg(self, arg_name, value):
        assert arg_name in self.kwargs
        self.kwargs[arg_name] = value

    def __del_arg(self, arg_name):
        assert arg_name in self.kwargs
        del self.kwargs[arg_name]

    @property
    def x(self):
        return self.__get_arg('x')

    @x.setter
    def x(self, value):
        self.__set_arg('x', value)

    @x.deleter
    def x(self):
        self.__del_arg('x')

    @property
    def y(self):
        return self.__get_arg('y')

    @y.setter
    def y(self, value):
        self.__set_arg('y', value)

    @y.deleter
    def y(self):
        self.__del_arg('y')

    @property
    def z(self):
        return self.__get_arg('z')

    @x.setter
    def z(self, value):
        self.__set_arg('z', value)

    @x.deleter
    def z(self):
        self.__del_arg('z')


c = C()
c.x = 'foo'  # setter called
foo = c.x    # getter called
del c.x      # deleter called
 

Ответ №1:

Вы можете написать свой собственный тип дескриптора и использовать __set_name__ метод, который будет вызван для него (механизмом создания класса), чтобы выяснить, на какое имя он был сохранен в классе:

 class MyProp:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, owner=None):
        if instance is None:
             return self
        return instance._get_arg(self.name)

    def __set__(self, instance, value):
        instance._set_arg(self.name, value)

    def __delete__(self, instance):
        instance._del_arg(self.name)
 

Вы бы использовали его таким образом:

  class C:
     # define __init__, _get_arg, etc.

     x = MyProp()
     y = MyProp()
     z = MyProp()
 

Обратите внимание, что, поскольку это код из другого класса, вызывающий _X_arg методы, вы, вероятно, не захотите искажать имена, поэтому я изменил __ префиксы только на один _ .

Комментарии:

1. Спасибо! будет ли myProp внутренним классом C? В x = MyProp() , было бы это больше похоже self.my_properties['x'] = C.MyProp(self, 'x') на и т. Д.?

2. Но мне все равно нужно создавать x = myProp(), y = myProp(), z = myProp() один за другим, я никак не могу создать их в цикле и по-прежнему использовать геттеры и сеттеры так, как я хочу, верно?

3. Я полагаю , вы могли бы определить MyProp внутри C , но в этом нет необходимости. И вы могли бы использовать цикл, чтобы установить их, я полагаю, но я думаю, что это было бы намного уродливее и сложнее для понимания, чем то, что я показал. Вы можете рассмотреть возможность использования декоратора класса для добавления дескрипторов, хотя вам нужно будет самостоятельно задать имя, так __set_name__ как оно вызывается только во время создания класса. Метакласс также может работать (и будет вызываться __set_name__ автоматически), но тогда менее очевидно, как вы указываете список нужных вам имен свойств.

4. @Bickknight Могу я спросить, в чем преимущество по __ set_name__ сравнению __init__ с тем же содержимым? Не нужно передавать name в качестве параметра? Извините за задержку с ответом, пришлось срочно заняться чем-то другим.

5. Да, тот факт, что это происходит автоматически, является главным преимуществом использования __set_name__ . Если бы вы написали __init__ метод, который принимает name в качестве аргумента, вам нужно было бы написать x = MyProp('x') , что немного избыточно. Это могло бы лучше работать с альтернативными подходами к созданию атрибутов, но в своем ответе я хотел выделить подход с дескрипторами.