Overriding Properties in Python

Overriding properties in python

I'm sure you've heard this before, but apply has been deprecated for eight years, since Python 2.3. Don't use it. Your use of locals() is also contrary to the Zen of Python -- explicit is better than implicit. If you really like the increased indentation, there is no need to create a throwaway object, just do

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

Which doesn't abuse locals, use apply, require creation of an extra object, or need a line afterwards with foo = foo() making it harder to see the end of the block. It works just as well for your old-fashioned way of using property -- just do foo = property(fget, fset) as normal.

If you want to override a property in an arbitrary subclass, you can use a recipe like this.

If the subclass knows where the property was defined, just do:

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

Override an attribute with a property in python class

instead of calling your method _initProperty call it __getattr__ so that it will be called every time the attribute is not found in the normal places it should be stored (the attribute dictionary, class dictionary etc.) then the first time the attribute is tried to be accessed it gets initialized.

Be sure to not set them in the Parent initialization, maybe only set them if they are not None:

class Parent:
def __init__(self,a1=None,a2=None):
if a1 is not None:
self.a1 = a1
if a2 is not None:
self.a2 = a2

As well to stay consistent with errors you will want to raise an AttributeError if the attribute doesn't exist instead of letting the KeyError go through, and maybe add a reference to the value in the regular attribute dict so that it doesn't need to run the __getattr__ every time:

_propertiesCache = {'a1':None,'a2':None}
def __getattr__(self, propertyName):
if propertyName not in self._propertiesCache:
raise AttributeError(propertyName)
value = self._propertiesCache[propertyName]
if value is None:
value = self._propertiesCache[propertyName]=expensiveFunction(self.b, propertyName)
setattr(self,propertyName,value)
return value

Any way you implement this you need to make sure:

The attribute is not set until the first time they are used (at which point __getattr__ gets used)

Override a property with super()

Try return super().velocity

@property
def velocity(self, flag=True):
if flag:
return super().velocity

Subclassing: Is it possible to override a property with a conventional attribute?

A property is a data descriptor which takes precedence over an instance attribute with the same name. You could define a non-data descriptor with a unique __get__() method: an instance attribute takes precedence over the non-data descriptor with the same name, see the docs. The problem here is that the non_data_property defined below is for computation purpose only (you can't define a setter or a deleter) but it seems to be the case in your example.

import math

class non_data_property:
def __init__(self, fget):
self.__doc__ = fget.__doc__
self.fget = fget

def __get__(self, obj, cls):
if obj is None:
return self
return self.fget(obj)

class Math_Set_Base:
@non_data_property
def size(self, *elements):
return len(self.elements)

class Concrete_Math_Set(Math_Set_Base):
def __init__(self, *elements):
self.elements = elements

class Square_Integers_Below(Math_Set_Base):
def __init__(self, cap):
self.size = int(math.sqrt(cap))

print(Concrete_Math_Set(1, 2, 3).size) # 3
print(Square_Integers_Below(1).size) # 1
print(Square_Integers_Below(4).size) # 2
print(Square_Integers_Below(9).size) # 3

However this assumes that you have access to the base class in order to make this changes.

Overriding an inherited property setter

You can refer to the existing property directly through the Node class, and use the property's setter method to create a new property from it:

class Theorem(Node):
@Node.importance.setter
def importance(self, new_importance):
# You can change the order of these two lines:
assert new_importance >= 3
Node.importance.fset(self, new_importance)

This will create a new property into Theorem class that uses the getter method from Node.importance but replaces the setter method with a different one.
That's how properties in general work: calling a property's setter returns a new property with a custom setter, which usually just replaces the old property.

You can learn more about how properties work by reading this answer (and the question too).

overriding a property that has no getter or setter in python

As mentionned in the comments, the code for your setter is incorrect.

Here is a working solution :

class Parent(object):

def __init__(self, val):
self.val = val

@property
def value(self):
return self.val

class Child(Parent):

@Parent.value.setter
def value(self, val):
self.val = val + " overridden"

parent = Parent("initial")
print(parent.value)

child = Child("initial")
print(child.value)
child.value = "new value"
print(child.value)

which produces

initial
initial
new value overridden

Python overriding of property does not work

You have to overload the property in B, too:

class A():
def get_val(self):
print(self)
raise NotImplementedError

val = property(get_val)

class B(A):
def __init__(self):
self.b_val = "foo\n"

def get_val(self):
print(self)
return self.b_val

val = property(get_val)

or since get_val is not really needed, use decorator syntax:

#!/usr/bin/env python

class A():
@property
def val(self):
print(self)
raise NotImplementedError

class B(A):
def __init__(self):
self.b_val = "foo\n"

@property
def val(self):
print(self)
return self.b_val

b = B()
print(b.val)

How to override the an inherent property when using its related method?

Your implementation is non-logical by default.
The function nums in class bar overrides the function nums in class foo - therefore when you call the self.nums in list-comprehension of gt function - the actual nums invoked is the overrided one in bar.

not sure what are your constraints, but just not override will do the work:

class foo:

@property
def nums(self):
return [1, 2, 3, 4, 5]

def gt(self, x):
return [num for num in self.nums if num > x]

class bar(foo):

@property
def nums2(self):
return super().gt(3)

f = foo()
b = bar()

print(f.nums)
print(b.nums2)

other option is to make the nums a member and return it from nums, like here:

class foo:
nums_member = [1, 2, 3, 4, 5]
@property
def nums(self):
return self.nums_member

def gt(self, x):
return [num for num in self.nums_member if num > x]

class bar(foo):

@property
def nums(self):
return super().gt(3)

f = foo()
b = bar()

print(f.nums)
print(b.nums)

Class method override with any type of property

You've got the variance backwards.

class App:
@abstractmethod
def main(self, params: object):
pass

This is a promise. It says "any class which implements App has a main which works on all objects. Not some of them. All of them.

Then you come along and write

class MyClass(App):
def main(self, params: Tuple[str, int, int]):
# Do something

So MyClass has a main which works on a specific type of parameter, not all of them. Your type checker correctly reports that this is not what you promised earlier. You could do the opposite thing: If App requires Tuple[str, int, int], then MyClass could implement a version for object, since that includes Tuple[str, int, int] and then some.

But for your case, you probably want generics.

from typing import TypeVar, Generic

_T_contra = TypeVar("_T_contra", contravariant=True)

class App(Generic[_T_contra]):
@abstractmethod
def main(self, params: _T_contra) -> None:
pass

class MyClass(App[Tuple[str, int, int]]):
def main(self, params: Tuple[str, int, int]):
# Do something

Now MyClass doesn't claim to work for all objects. It claims to work for this specific type, so it derives from a specific App.

For a discussion of why it's _T_contra (a contravariant type parameter), you can read my other answer where I go into more detail on variance annotations in Python.



Related Topics



Leave a reply



Submit