Python assert on property set - python

Is it possible to make a property assert when it is changed (for the purpose of debugging)?
class MyClass(object):
def set_my_property(self, value):
self.my_property = value
# TODO: mark my_property so that if it gets set again, an assert
# is triggered
c = MyClass()
c.set_my_property(4)
# any of these lines would cause an assertion
c.set_my_property(8)
c.my_property = 123

EDIT:
Is this what you're looking for?
class MyClass(object):
def __init__(self):
self.trigger = False
self._my_property = 0
def set_my_property(self, value):
if self.trigger:
raise Exception("WHOOPS!")
self._my_property = value
# TODO: mark my_property so that if it gets set again, an assert
# is triggered
self.trigger = True
def get_my_property(self):
return self._my_property
my_property = property(get_my_property, set_my_property, None)
c = MyClass()
c.set_my_property(4)
# any of these lines would cause an assertion
c.set_my_property(8)
c.my_property = 123

Add a boolean to check if the value has been set before:
EDIT: But you want a property, so you'll need to create one:
class MyClass(object):
def __init__(self):
self.my_property_set = False
self._my_property = None
def set_my_property(self, value):
self._my_property = value
assert not self.my_property_set,"my_property already set"
self.my_property_set = True
def get_my_property(self):
return self._my_property
my_property = property(get_my_property, set_my_property, None)
c = MyClass()
c.set_my_property(4)
# any of these lines would cause an assertion
c.set_my_property(8)
c.my_property = 123

class Foo:
def __init__(self):
self._bar = None
#property
def bar(self): return self._bar
#bar.setter:
def bar(self, value):
assert value != some_constant # your assert condition
self._bar = value
#bar.deleter:
def bar(self, value):
assert value != some_constant # your assert condition
self._bar = None

Related

Property setter not working when attribute starts with "__"?

I am using Python 3.8.6 and this works fine
class A:
#property
def _a(self):
return getattr(self, '_a_', 0)
#_a.setter
def _a(self, value):
self._a_ = value
a = A()
print(a._a) # prints 0
a._a = 10
print(a._a) # prints 10 as expected
This doesn't work
class A:
#property
def _a(self):
return getattr(self, '__a', 0)
#_a.setter
def _a(self, value):
self.__a = value
a = A()
print(a._a) # prints 0
a._a = 10
print(a._a) # prints 0 again
That's mind blowing! the only difference between the first and second example is that the private attribute is __a instead of _a_
Any idea why? I wasn't able to figure it out
It's due to private name mangling, but it doesn't apply to the contents of string literals like the one you're passing to getattr().
Fortunately the fix is simple:
class A:
#property
def _a(self):
return getattr(self, '_A__a', 0)
#_a.setter
def _a(self, value):
self.__a = value
a = A()
print(a._a) # prints 0
a._a = 10
print(a._a) # prints 10 now

Best way to modify behaviour of a setter for an attribute of a class which in itself is an attribute of another class?

Sorry for the confusing title. My problem looks as following:
class A():
def __init__(self):
self.flags = Flags()
self.subclasses = [list of B objects]
class B():
def __init__(self):
self.flags = Flags()
class Flags():
def __init__(self):
self.value = False
I want to be able to modify the value of the flag in A, which in turn adjusts all the flag values of the list of B objects. In my original soultion I simply set the value of the flag as an attribute of class A and then overwrote the setter with #property and #value.setter, my question is if it is also possible to do it the way described above.
You can do what you want using a descriptor:
class Flag:
def __init__(self):
self.value = False
def __set__(self, obj, value):
self.value = value
if isinstance(obj, A):
for scl in obj.subclasses:
scl.flag = value
def __get__(self, obj, type=None):
return self.value
class B:
flag = Flag()
def __repr__(self):
return f"B: {self.flag}"
class A:
flag = Flag()
def __init__(self, bs):
self.subclasses = list(bs)
def __repr__(self):
return f"A: {self.flag}"
bs = [B() for _ in range(7)]
a = A(bs)
The __repr__ methods are just for demonstration:
>>> a
A: True
>>> a.subclasses
[B: False, B: False, B: False, B: False, B: False, B: False, B: False]
>>> a.flag = True
A: True
>>> a.subclasses
[B: True, B: True, B: True, B: True, B: True, B: True, B: True]
If you want to have a dynamic number of flags that all have this effect, you could use __getattr__ and __setattr__ on the descriptor:
class Flags:
def __init__(self):
self._storage = {}
self._obj = None
def __get__(self, instance, owner):
self._obj = instance
return self
def __setattr__(self, attr, value):
if attr.startswith("_"):
return super().__setattr__(attr, value)
self._storage[attr] = value
if hasattr(self._obj, "subclasses"):
for sub in self._obj.subclasses:
setattr(sub.flag, attr, value)
def __getattr__(self, attr):
return self._storage[attr]
class B:
flags = Flags()
class A:
flags = Flags()
def __init__(self, bs):
self.subclasses = list(bs)
Usage:
>>> b = B()
>>> a = A([b])
>>> a.flags.foo = "bar"
>>> b.flags.foo
"bar"

Python set attribute equal to another attribute

Is it possible to have a class attribute targeting another attribute from the same object and have a function to update the value of the target?
class MyObject(object):
def __init__(self):
self.var_1 = 1
self.var_2 = 2
self.var_3 = 3
self.current_var = self.var_1
def update_var(self, value):
self.current_var = ...
Expected outcome:
>>> x = MyObject()
>>> x.update_var(10)
>>> x.var_1
10
>>> x.current_var = x.var_2
>>> x.update_var(5)
>>> x.var_2
5
You can use the __dict__ of the object or as said by #bla setattr,
And Enum so you don't use string to specify the attribute:
from enum import Enum
class MyObject(object):
def __init__(self):
self.var_1 = 1
self.var_2 = 2
self.var_3 = 3
self.current_var = None
def update_var(self, value):
if self.current_var is None:
raise Exception('Current var is not set')
self.__dict__[self.current_var.name] = value
setattr(self, self.current_var.name, value) # Same result
m = MyObject()
attrs = vars(m)
attrs_enum = Enum("attrs_enum", attrs)
m.var_1 # 1
m.current_var = attrs_enum.var_1
m.update_var(10)
m.var_1 # 10
m.current_var = attrs_enum.var_2
m.var_2 # 2
m.update_var(20)
m.var_2 # 20
I don't like using a string to specify the attribute, but this is solution
I suggest making current_var a property that acts as a proxy for a given instance attribute. You can use set_current_var to update the proxy target.
Code
class MyObject(object):
current_var = 1
def __init__(self):
self.var_1 = 1
self.var_2 = 2
self.var_3 = 3
def set_current_var(self, name):
self._current_var = name
#property
def current_var(self):
return getattr(self, self._current_var)
#current_var.setter
def current_var(self, value):
setattr(self, self._current_var, value)
Example
x = MyObject()
print(x.var_1) # 1
x.set_current_var('var_1')
print(x.current_var) # 1
x.current_var = 4
print(x.var_1) # 4
You can create a wrapper class for your MyObject attribute values. That way, a reference will exist from the contents of current_var to the attribute bound in __dict__:
class _int:
def __init__(self, _val):
self.value = _val
def __repr__(self):
return str(self.value)
class MyObject(object):
def __init__(self):
self.var_1 = _int(1)
self.var_2 = _int(2)
self.var_3 = _int(3)
self.current_var = self.var_1
def update_var(self, value):
self.current_var.value = value
x = MyObject()
x.update_var(10)
print(x.var_1)
x.current_var = x.var_2
x.update_var(5)
print(x.var_2)
Output:
10
5

Assert that a PropertyMock was called on a specific instance

I have successfully mocked a property with PropertyMock, but I can't figure out how to check which instance of the class has had that property called. How can I assert that the property was called on one object, but not on another?
Here's an example where I want to assert that foo1.is_big was called, and foo2.is_big was not:
from mock import PropertyMock, patch
class Foo(object):
def __init__(self, size):
self.size = size
#property
def is_big(self):
return self.size > 5
f = Foo(3)
g = Foo(10)
assert not f.is_big
assert g.is_big
with patch('__main__.Foo.is_big', new_callable=PropertyMock) as mock_is_big:
mock_is_big.return_value = True
foo1 = Foo(4)
foo2 = Foo(9)
should_pass = False
if should_pass:
is_big = foo1.is_big
else:
is_big = foo2.is_big
assert is_big
# How can I make this pass when should_pass is True, and fail otherwise?
mock_is_big.assert_called_once_with()
print('Done.')
The current code will pass when either one of them is called.
Maybe there's a better way, but I got it to work by creating a subclass of PropertyMock that records the instance that it was called on as one of the parameters to the mock call.
from mock import PropertyMock, patch
class Foo(object):
def __init__(self, size):
self.size = size
#property
def is_big(self):
return self.size > 5
class PropertyInstanceMock(PropertyMock):
"""Like PropertyMock, but records the instance that was called."""
def __get__(self, obj, obj_type):
return self(obj)
def __set__(self, obj, val):
self(obj, val)
with patch("__main__.Foo.is_big", new_callable=PropertyInstanceMock) as mock_is_big:
mock_is_big.return_value = True
foo1 = Foo(4)
foo2 = Foo(9)
should_pass = False
if should_pass:
is_big = foo1.is_big
else:
is_big = foo2.is_big
assert is_big
# Now this passes when should_pass is True, and fails otherwise.
mock_is_big.assert_called_once_with(foo1)
print("Done.")

How to properly define class properties in Python

I've been looking for a way to define class properties in Python.
The expected behavior would be something intuitive like:
class A:
_access_count = 0
#classproperty
def value1(cls):
cls._access_count += 1
return 1
A.value1 # return 1
A().value1 # return 1
A._access_count # return 2
A.value1 = 2 # raise an AttributeError
I found related questions on SO, but none of them propose this exact feature.
This thread has a nice example of metaclass even though it doesn't really apply in that case. The accepted answer of this one propose a close solution but doesn't handle the setter mecanism.
Since it is ok to answer his own question I'll write what I've come up with so far.
class classproperty(property):
"""Class property works exactly like property."""
pass
def create_instance_property(cls_prop):
"""Create instance property from class property."""
fget, fset, fdel = None, None, None
if cls_prop.fget is not None :
fget = lambda self: cls_prop.fget(type(self))
if cls_prop.fset is not None :
fset = lambda self, value: cls_prop.fset(type(self), value)
if cls_prop.fdel is not None :
fdel = lambda self: cls_prop.fdel(type(self))
return property(fget, fset, fdel, cls_prop.__doc__)
def init_for_metaclass(cls, name, bases, dct):
"""__init__ method for a metaclass to handle class properties."""
super(type(cls), cls).__init__(name, bases, dct)
for key, prop in dct.items():
if isinstance(prop, classproperty):
setattr(cls, key, create_instance_property(prop))
setattr(type(cls), key, prop)
def handle_class_property(cls):
"""Class decorator to handle class properties."""
name = type(cls).__name__ + "_for_" + cls.__name__
metacls = type(name, (type(cls),), {"__init__": init_for_metaclass})
return metacls(cls.__name__, cls.__bases__, dict(cls.__dict__))
So far it works exactly as expected, even for inheritance cases:
#handle_class_property
class A(object):
_access_count = 0
#classproperty
def value1(cls):
print cls
cls._access_count += 1
return 1
class B(A):
_access_count = 0
#classproperty
def value2(cls):
cls._access_count += 1
return 2
#value2.setter
def value2(cls, value):
print(value)
a = A()
b = B()
assert (a.value1, A.value1) == (1,1)
assert (b.value1, B.value1) == (1,1)
assert (b.value2, B.value2) == (2,2)
assert B._access_count == 4
assert A._access_count == 2
B.value2 = 42 # This should print '42'
try: B.value1 = 42 # This should raise an exception
except AttributeError as e: print(repr(e))

Categories