Initialize property on first use - python

I'm curious on what might be a better pattern for creating a property attribute that initialises its value on 1st use. Below is a class with several variations on a theme.
def some_initializer(s):
return f"Value: {s}"
class Foo(object):
""" Initialize on first use properties """
def __init__(self):
self._prop1 = None
#property
def prop1(self):
""" Existing private member attribute """
if not self._prop1:
self._prop1 = some_initializer("prop1")
return self._prop1
#property
def prop2(self):
""" Create private attribute on demand """
if not hasattr(self, "_prop2"):
self._prop2 = some_initializer("prop2")
return self._prop2
#property
def prop3(self):
""" Create private attribute on demand - shorter """
self._prop3 = getattr(self, "_prop3", some_initializer("prop3"))
return self._prop3
#property
def prop4(self):
""" Stash value in attribute with same name as property """
_prop4 = self.__dict__.get('_prop4')
if _prop4 is not None:
return _prop4
self._prop4 = _prop4 = some_initializer("prop4")
return _prop4
>> f = Foo()
>> print(f.prop1)
>> print(f.prop2)
>> print(f.prop3)
>> print(f.prop4)
Value: prop1
Value: prop2
Value: prop3
Value: prop4
In the past I've used variations prop1, prop2 and prop3. Recently I was introduced to the prop4 variation that I feel is quite confusing although perhaps technically correct. Any cons with these variations or maybe there are better ways?
Edit: Ideally, it would be nice to maintain compatibility with property setter and deleter decorators too.

i would just write a custom descriptor and use that instead:
class cached_property:
def __init__(self, f):
self.f = f
def __get__(self, instance, owner):
if not instance:
return self
res = instance.__dict__[self.f.__name__] = self.f(instance)
return res
usage:
class C:
#cached_property
def prop(self):
print('you will see me once')
return 4

You could use functools.lru_cache to memoize the property value:
from functools import lru_cache
class Foo(object):
#property
#lru_cache()
def prop(self):
print("called once")
return 42
foo = Foo()
print(foo.prop)
print(foo.prop)

I also thought of a descriptor but came up with this approach
from weakref import WeakKeyDictionary
def five():
return 5
def six():
return 6
def seven():
return 7
class FirstInit:
def __init__(self, initializer):
self.initializer = initializer
self.data = WeakKeyDictionary()
def __get__(self, instance, owner):
try:
value = self.data[instance]
except KeyError as e:
value = self.initializer()
self.data[instance] = value
return self.data[instance]
Usage:
class F:
a = FirstInit(five)
b = FirstInit(six)
c = FirstInit(seven)
def __init__(self,name):
self.name = f'{name}:{self.c}'
>>> f = F('foo')
>>> f.name
'foo:7'
>>> f.a, f.b
(5, 6)
>>> f.a = 'sixteen'
>>> f.a, f.b
('sixteen', 6)
>>> f.b += 13
>>> f.a, f.b
('sixteen', 19)
>>>
For an initializer that takes an argument:
d = {'P1':5, 'P2':6, 'P3':7}
def initializer(which):
return d[which]
class FirstInit:
def __init__(self, initializer, prop):
self.initializer = initializer
self.prop = prop
self.data = WeakKeyDictionary()
def __get__(self, instance, owner):
try:
value = self.data[instance]
except KeyError as e:
value = self.initializer(self.prop)
self.data[instance] = value
return self.data[instance]
class G:
a = FirstInit(initializer, 'P1')
b = FirstInit(initializer, 'P2')
c = FirstInit(initializer, 'P3')
def __init__(self,name):
self.name = f'{name}:{self.c}'
...
>>> g = G('foo')
>>> g.name
'foo:7'
>>> g.b += 16
>>> g.a,g.b
(5, 22)
>>> g.a = 'four'
>>> g.a,g.b
('four', 22)
>>>

Related

Python - Can same property logic be applied to multiple attributes?

Is there a way to apply the same property logic to a set of attributes in a class? For example, I want to apply the same #attr1.setter decorator to attr2, attr3, and attr4 without having to define the property for each attribute.
class Sample:
def __init__(self):
self.attr1 = None
self.attr2 = None
self.attr3 = None
self.attr4 = None
#property
def attr1(self):
return self.__attr1
#attr1.setter
def attr1(self, val):
if val < 0:
self.__attr1 = 0
else:
self.__attr1 = val
Just create your own descriptor for this:
class MyDescriptor:
def __set_name__(self, owner, name):
self.name = f'_{name}'
def __get__(self, instance, owner):
return getattr(instance, self.name)
def __set__(self, instance, val):
if val is None:
setattr(instance, self.name, None)
elif val < 0:
setattr(instance, self.name, 0)
else:
setattr(instance, self.name, val)
class Sample:
attr1 = MyDescriptor()
attr2 = MyDescriptor()
attr3 = MyDescriptor()
attr4 = MyDescriptor()
def __init__(self):
self.attr1 = None
self.attr2 = None
self.attr3 = None
self.attr4 = None
Now, in action:
In [3]: s = Sample()
In [4]: s.attr1 = -99
In [5]: s.attr1
Out[5]: 0
In [6]: s.attr2
In [7]: s.attr2 = 10
In [8]: s.attr2
Out[8]: 10
In [9]: s.attr2 = -1
In [10]: s.attr2
Out[10]: 0
See the Descriptor HOWTO and some more relevant documentation
Note, I incorporated the possibility of None in your setter logic (your code would have raised a TypeError on initialization of an instance, because the setter checks if None < 0). Also note, you probably don't want to be using double-underscore name-mangling (which doesn't mean private), so I used the conventional single-underscore to denote a variable not part of the public api. Using double-underscore name-mangling complicates things here.
You could override the __getattr__ and __setattr__ to behave the way you want them. This way you don't need to define any private variables nor initialize any of the member variables either.
class Sample:
def __getattr__(self, attr):
return self.__dict__.get(attr)
def __setattr__(self, attr, val):
if val is not None and val < 0:
self.__dict__[attr] = 0
else:
self.__dict__[attr] = val
s = Sample()
print(s.attr1) # None
s.attr1 = 10
print(s.attr1) # 10
s.attr1 = -10
print(s.attr1) # 0
s.attr1 = None
print(s.attr1) # None

How to avoid creating objects with same values?

I need to create a class whose instances can't have same values. If you create instance with value that have already been used you'll get old same instance.
I did it using special class method:
class A():
instances = []
def __init__(self, val):
self.val = val
#classmethod
def new(cls, val):
"""
Return instance with same value or create new.
"""
for ins in cls.instances:
if ins.val == val:
return ins
new_ins = A(val)
cls.instances.append(new_ins)
return new_ins
a1 = A.new("x")
a2 = A.new("x")
a3 = A.new("y")
print a1 # <__main__.A instance at 0x05B7FD00> S\ /M\
print a2 # <__main__.A instance at 0x05B7FD00> \A/ \E
print a3 # <__main__.A instance at 0x05B7FD28>
Is there a way to do it more elegant, without using .new method?
You could try functools.lru_cache.
For example:
from functools import lru_cache
class A:
#lru_cache()
def __new__(cls, arg):
return super().__new__(cls)
def __init__(self, arg):
self.n = arg
Sample usage:
>>> a1 = A('1')
>>> a2 = A('1')
>>> a1 is a2
True
>>> a1.n
'1'
>>> a2.n
'1'
Alternatively you could try building a custom caching class, as pointed out by Raymond Hettinger in this tweet: https://twitter.com/raymondh/status/977613745634471937.
This can be done by overriding the __new__ method, which is responsible for creating new instances of a class. Whenever you create a new instance you store it in a dict, and if the dict contains a matching instance then you return it instead of creating a new one:
class A:
instances = {}
def __new__(cls, val):
try:
return cls.instances[val]
except KeyError:
pass
obj = super().__new__(cls)
cls.instances[val] = obj
return obj
def __init__(self, val):
self.val = val
a = A(1)
b = A(2)
c = A(1)
print(a is b) # False
print(a is c) # True
One downside of this solution is that the __init__ method will be called regardless of whether the instance is a newly created one or one that's been stored in the dict. This can cause problems if your constructor has undesired side effects:
class A:
...
def __init__(self, val):
self.val = val
self.foo = 'foo'
a = A(1)
a.foo = 'bar'
b = A(1)
print(a.foo) # output: foo
Notice how a's foo attribute changed from "bar" to "foo" when b was created.
Another option is to use a metaclass and override its __call__ method:
class MemoMeta(type):
def __new__(mcs, name, bases, attrs):
cls = super().__new__(mcs, name, bases, attrs)
cls.instances = {}
return cls
def __call__(cls, val):
try:
return cls.instances[val]
except KeyError:
pass
obj = super().__call__(val)
cls.instances[val] = obj
return obj
class A(metaclass=MemoMeta):
def __init__(self, val):
self.val = val
self.foo = 'foo'
This bypasses the problem with __init__ being called on existing instances:
a = A(1)
a.foo = 'bar'
b = A(1)
print(a.foo) # output: bar
If you really want to make it more elegant, implement the duplicate check in __new__, so it will be performed when you call A(something).
Just do it in __new__:
def __new__(cls, val=None):
for i in cls.instances:
if val == i.val:
return i
return object.__new__(cls)

How to access a object's attrbute by using string corresponding to name if object is attribute of some other object

I have 3 classes as below:-
class C(object):
def __init__(self, v):
self.var = v
class B(object):
def __init__(self, c):
self.c = c
class A(object):
def __init__(self, b):
self.b = b
I have created instances as
c = C("required result")
b = B(c)
a = A(b)
>>> a.b.c.var
'required result'
Now I need to pass b.c.var as a string to some function and get the value of var similar to sample function as below -
`sample(a, 'b.c.var')` should return 'required result'`
What should be pythonic way to achieve this
This is my attempt :-
for attr in ('b', 'c', 'var'):
a = getattr(a, attr)
>>> print a
required result
You can use operator.attrgetter which takes a dotted name notation, eg:
from operator import attrgetter
attrgetter('b.c.var')(a)
# 'required result'
Then if you don't like that syntax, use it to make your sample function, eg:
def sample(obj, attribute):
getter = attrgetter(attribute)
return getter(obj)
From the documentation linked above, the operator.attrgetter uses the equivalent of the following code:
def attrgetter(*items):
if any(not isinstance(item, str) for item in items):
raise TypeError('attribute name must be a string')
if len(items) == 1:
attr = items[0]
def g(obj):
return resolve_attr(obj, attr)
else:
def g(obj):
return tuple(resolve_attr(obj, attr) for attr in items)
return g
def resolve_attr(obj, attr):
for name in attr.split("."):
obj = getattr(obj, name)
return obj
So in fact - your original code was just trying to do the equivalent of resolve_attr...
Here is the more accurate way, I suppose. (using try-except construction):
...
c = C("required result")
b = B(c)
a = A(b)
def sample(obj, path):
path_attrs = path.split('.') # splitting inner attributes path
inner_attr = None
for p in path_attrs:
try:
inner_attr = getattr(inner_attr if inner_attr else obj, p)
except AttributeError:
print('No %s field' % p)
print(inner_attr)
sample(a, 'b.c.var') # will output 'required result'

Filter attributes of a class based on the type

I would like to filter the attributes of an object of a class based on their types.
The answer will be something around inspect, “list comprehensions”, type(), __dict__ and dict() but I don't get it working.
class A():
def __init__(self, value):
self.x = value
def __str__(self):
return "value = {}\n".format(self.x)
class T():
def __init__(self):
self.a1 = A(1)
self.a2 = A(2)
self.b = 4
t = T()
And I would like to print only the attributes of the type A in the class T
class T():
def __init__(self):
self.a1 = A(1)
self.a2 = A(2)
self.b = 4
def __str__(self):
ret = ""
for i in [*magic*]:
ret += str(i)
return ret
Output should be something like:
value = 10
value = 15
You can use vars(self) to get a dictionary of the local attributes, then just test the values with isinstance():
def __str__(self):
ret = ""
for i in vars(self).values():
if isinstance(i, A):
ret += str(i)
return ret
vars() essentially returns self.__dict__ here but is cleaner.
Turning this into a list comprehension for one-liner appeal:
def __str__(self):
return ''.join([i for i in vars(self).values() if isinstance(i, A)])

Python: why can't descriptors be instance variables?

Say I define this descriptor:
class MyDescriptor(object):
def __get__(self, instance, owner):
return self._value
def __set__(self, instance, value):
self._value = value
def __delete__(self, instance):
del(self._value)
And I use it in this:
class MyClass1(object):
value = MyDescriptor()
>>> m1 = MyClass1()
>>> m1.value = 1
>>> m2 = MyClass1()
>>> m2.value = 2
>>> m1.value
2
So value is a class attribute and is shared by all instances.
Now if I define this:
class MyClass2(object)
value = 1
>>> y1 = MyClass2()
>>> y1.value=1
>>> y2 = MyClass2()
>>> y2.value=2
>>> y1.value
1
In this case value is an instance attribute and is not shared by the instances.
Why is it that when value is a descriptor it can only be a class attribute, but when value is a simple integer it becomes an instance attribute?
You're ignoring the instance parameter in your implementation of MyDescriptor. That is why it appears to be a class attribute. Perhaps you want something like this:
class MyDescriptor(object):
def __get__(self, instance, owner):
return instance._value
def __set__(self, instance, value):
instance._value = value
def __delete__(self, instance):
del(instance._value)
Will not work if you try the code below:
class MyClass1(object):
value = MyDescriptor()
value2 = MyDescriptor()
c = MyClass1()
c.value = 'hello'
c.value2 = 'world'
# where c.value also equals to "world"

Categories

Resources