Удалить свойство (getter / setter) из атрибута в подклассе в Python - PullRequest
0 голосов
/ 09 января 2019

Возможно, на это ответили где-то еще, но мне было интересно, есть ли способ удалить атрибут / метод, украшенный @property в подклассе.

Пример:

from datetime import datetime

class A():
    def __init__(self, num):
        self._num = num

    @property
    def id(self):
        return self._num * datetime.now().timestamp()

class B(A):
    def __init__(self, id, num):
        super().__init__(num)
        self.id = id

Приведенный выше код не запускается, если вы пытаетесь создать экземпляр класса B. AttributeError: can't set attribute

Базовый класс использует свойство, потому что ему нужно оценивать свой идентификатор на лету, в то время как мой подкласс может знать его идентификатор при его создании. К атрибуту id обращаются OFTEN, и я наблюдаю значительное снижение производительности, поскольку мне приходится использовать свойство для обслуживания этого атрибута, а не просто обращаться к нему напрямую. (Из того, что я прочитал, свойства увеличивают время доступа в 5 раз). Мое приложение в настоящее время тратит около 10% времени выполнения, чтобы получить это свойство.

Можно ли как-нибудь замкнуть свойство в подклассе?

Спасибо!

Ответы [ 2 ]

0 голосов
/ 09 января 2019

Я собираюсь пройти через несколько возможностей здесь. Некоторые из них делают то, что вы буквально просили. Некоторые из них этого не делают, но в любом случае они могут быть лучше.

Во-первых, ваш примерный базовый класс меняет значение obj.id при каждом доступе из-за времени. Это действительно странно и не похоже на полезную концепцию «ID». Если ваш реальный вариант использования имеет стабильное возвращаемое значение obj.id, то вы можете кэшировать его, чтобы избежать затрат на повторное вычисление:

def __init__(self):
    ...
    self._id = None
@property
def id(self):
    if self._id is not None:
        return self._id
    retval = self._id = expensive_computation()
    return retval

Это может уменьшить расходы на имущество. Если вам нужно больше мер по смягчению, ищите места, где вы неоднократно обращаетесь к id, и вместо этого обращайтесь к нему один раз и сохраняйте его в переменной. Поиск локальной переменной превосходит доступ к атрибуту независимо от того, как реализован атрибут. (Конечно, если у вас действительно есть странные временные идентификаторы, такой рефакторинг может быть недействительным.)

Во-вторых, вы не можете переопределить property с помощью «обычного» атрибута, но вы можете создать свою собственную версию property, которую можно переопределить таким образом. Ваш property блокирует настройку атрибутов и имеет приоритет над «обычными» атрибутами, даже если вы принудительно вводите запись в экземпляр __dict__, потому что property имеет метод __set__ (даже если вы не пишете установщик) , Написание собственного дескриптора без __set__ позволит переопределить. Вы можете сделать это с помощью общего LowPriorityProperty:

class LowPriorityProperty(object):
    """
    Like @property, but no __set__ or __delete__, and does not take priority
    over the instance __dict__.
    """
    def __init__(self, fget):
        self.fget = fget
    def __get__(self, instance, owner=None):
        if instance is None:
            return self
        return self.fget(instance)

class Foo(object):
    ...
    @LowPriorityProperty
    def id(self):
        ...

class Bar(Foo):
    def __init__(self):
        super(Bar, self).__init__()
        self.id = whatever
    ...

Или с классом дескриптора для конкретной роли:

class IDDescriptor(object):
    def __get__(self, instance, owner=None):
        if instance is None:
            return self
        # Remember, self is the descriptor. instance is the object you're
        # trying to compute the id attribute of.
        return whatever(instance)

class Foo(object):
    id = IDDescriptor()
    ...

class Bar(Foo):
    def __init__(self):
        super(Bar, self).__init__()
        self.id = whatever
        ...

Дескриптор для конкретной роли работает лучше, чем универсальный LowPriorityProperty, но оба работают хуже, чем property из-за реализации большего количества логики в Python вместо C.

Наконец, вы не можете переопределить property с помощью «обычного» атрибута, но вы можете переопределить его с помощью другого дескриптора, такого как другой property, или дескрипторов, созданных для __slots__. Если вы действительно очень заинтересованы в производительности, __slots__, вероятно, более производительный, чем любой дескриптор, который вы могли бы реализовать вручную, но взаимодействие между __slots__ и свойством странное и неясное, и вы, вероятно, захотите оставить комментарий объясняя, что ты делаешь.

class Foo(object):
    @property
    def id(self):
        ...

class Bar(Foo):
    __slots__ = ('id',)
    def __init__(self):
        super(Bar, self).__init__()
        self.id = whatever
    ...
0 голосов
/ 09 января 2019

добавить класс C как общего предка без идентификатора. наследуйте A и B от него и реализуйте там id по мере необходимости Python не заботится о том, что id не существует на C.

код / ​​атрибуты неидентификатора рефакторинга от A до C.

Пригодность зависит от того, контролирует ли OP иерархию классов и механизмы создания экземпляров.

Я также нашел обходной путь, чтобы заставить его работать как:

from datetime import datetime

class A():
    def __init__(self, num):
        self._num = num

    @property
    def id(self):
        return self._num * datetime.now().timestamp()

class B(A):

    #this fixes the problem
    id = None

    def __init__(self, id, num):
        super().__init__(num)
        self.id = id

b = B("id", 3)
print(vars(b))

Будет выведено:

{'_num': 3, 'id': 'id'}

Уловка id = None в классе B. По сути, механизм поиска атрибутов / методов Python остановится на первом классе с id в качестве атрибута в MRO. С id = None в классе B, поиск останавливается там, и он никогда не заходит так досадно, как @property на A.

Если я прокомментирую это обратно, согласно ОП:

    self.id = id
AttributeError: can't set attribute
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...