Переопределение свойств в Python - PullRequest
41 голосов
/ 11 августа 2011

Итак, я пытаюсь найти лучший (самый элегантный, с наименьшим количеством кода) способ, позволяющий переопределять определенные функции свойства (например, только получатель, только установщик и т. Д.) В Python. Я поклонник следующего способа создания свойств, поскольку все их методы инкапсулированы в одном и том же блоке кода с отступом (легче увидеть, где останавливаются функции, имеющие дело с одним свойством, и функции, связанные с следующее начало):

@apply
def foo():
    """A foobar"""
    def fget(self):
        return self._foo
    def fset(self, val):
        self._foo = val
    return property(**locals())

Однако, если я хочу наследовать от класса, который определяет свойства таким образом, а затем, скажем, переопределить функцию установки foo, это будет сложно. Я провел некоторый поиск, и большинство ответов, которые я нашел, состояло в том, чтобы определить отдельные функции в базовом классе (например, getFoo и setFoo), явно создать определение свойства из них (например, foo = property(lambda x: x.getFoo(), lambda x, y: x.setFoo(y), lambda x: x.delFoo())), а затем при необходимости переопределите getFoo, setFoo и delFoo.

Мне не нравится это решение, потому что оно означает, что я должен определить lambas для каждого отдельного свойства, а затем записать каждый вызов функции (когда раньше я мог только сделать property(**locals())). Я также не получаю инкапсуляцию, которая была у меня изначально.

В идеале, то, что я хотел бы сделать, было бы примерно так:

class A(object):
    def __init__(self):
        self.foo = 8
    @apply
    def foo():
        """A foobar"""
        def fget(self):
            return self._foo
        def fset(self, val):
            self._foo = val
        return property(**locals())

class ATimesTwo(A):
    @some_decorator
    def foo():
        def fset(self, val):
            self._foo = val * 2
        return something

И тогда результат будет выглядеть примерно так:

>>> a = A()
>>> a.foo
8
>>> b = ATimesTwo()
>>> b.foo
16

По сути, ATimesTwo наследует функцию получения от A, но переопределяет функцию установки. Кто-нибудь знает способ сделать это (таким образом, который похож на пример выше)? Какую функцию будет выглядеть some_decorator и что должна возвращать функция foo?

Ответы [ 2 ]

48 голосов
/ 11 августа 2011

Python docs на декораторе property предлагает следующую идиому:

class C(object):
    def __init__(self):
        self._x = None
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        self._x = value
    @x.deleter
    def x(self):
        del self._x

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

class C2(C):
    @C.x.getter
    def x(self):
        return self._x * -1

Это немного странно, потому что для переопределения нескольких методов требуется, чтобы вы сделали что-то вроде:

class C3(C):
    @C.x.getter
    def x(self):
        return self._x * -1
    # C3 now has an x property with a modified getter
    # so modify its setter rather than C.x's setter.
    @x.setter 
    def x(self, value):
        self._x = value * 2

Конечно, в тот момент, когда вы переопределяете getter, setter и delete, вы можете, вероятно,просто переопределите свойство для C3.

46 голосов
/ 11 августа 2011

Я уверен, что вы слышали это раньше, но apply устарела в течение восьми лет , начиная с Python 2.3. Не используйте это. Ваше использование locals() также противоречит дзен Python - явное лучше, чем неявное. Если вам действительно нравится увеличенный отступ, вам не нужно создавать одноразовый объект, просто сделайте

if True:
    @property
    def foo(self):
        return self._foo
    @foo.setter
    def foo(self, val):
        self._foo = val

который не использует locals, использует apply, требует создания дополнительного объекта или требует строки после foo = foo(), затрудняющей просмотр конца блока. Это работает так же хорошо для вашего старого способа использования property - просто сделайте foo = property(fget, fset) как обычно.

Если вы хотите переопределить свойство в произвольном подклассе, вы можете использовать рецепт, подобный этому .

Если подкласс знает, где было определено свойство, просто выполните:

class ATimesTwo(A):
    @A.foo.setter
    def foo(self, val):
        self._foo = val * 2
...