This may have been answered somewhere else, but I was wondering if there was any way to remove an attribute/method decorated with @property
in a subclass.
Example:
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
The above code does not run if you attempt to create an instance of class B
. AttributeError: can't set attribute
The base class uses a property because it needs to evaluate its ID on the fly, while my sub class is able to know its ID when it is created. The id
attribute is accessed OFTEN, and I am seeing a significant performance hit because I have to use a property to serve this attribute, instead of just accessing it directly. (From what I have read, properties increase time-to-access by 5x). My application is currently spending around 10% of runtime getting this property.
Is there any way I can short-circuit the property in a sub class?
I'm going to go through several possibilities here. Some of them do what you literally asked. Some of them don't, but they may be better options anyway.
First, your example base class changes the value of obj.id
on every access due to the passage of time. That's really bizarre and doesn't seem like a useful concept of "ID". If your real use case has a stable obj.id
return value, then you can cache it to avoid the expense of recomputation:
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
This may mitigate the expense of the property. If you need more mitigation, look for places where you access id
repeatedly, and instead, access it once and save it in a variable. Local variable lookup outperforms attribute access no matter how the attribute is implemented. (Of course, if you actually do have weird time-variant IDs, then this sort of refactoring may not be valid.)
Second, you can't override a property
with a "regular" attribute, but you can create your own version of property
that can be overridden this way. Your property
blocks attribute setting, and takes priority over "regular" attributes even if you force an entry into the instance __dict__
, because property
has a __set__
method (even if you don't write a setter). Writing your own descriptor without a __set__
would allow overriding. You could do it with a generic 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
...
Or with a role-specific descriptor class:
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
...
The role-specific descriptor performs better than the generic LowPriorityProperty
, but both perform worse than property
due to implementing more logic in Python instead of C.
Finally, you can't override a property
with a "regular" attribute, but you can override it with another descriptor, such as another property
, or such as the descriptors created for __slots__
. If you're really, really pressed for performance, __slots__
is probably more performant than any descriptor you could implement manually, but the interaction between __slots__
and the property is weird and obscure and you'll probably want to leave a comment explaining what you're doing.
class Foo(object):
@property
def id(self):
...
class Bar(Foo):
__slots__ = ('id',)
def __init__(self):
super(Bar, self).__init__()
self.id = whatever
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With