Python - Adding attribute to descriptor - python

I've this descriptor:
# Generic descriptor
class Attribute(object):
def __init__(self, value):
self.value = value
def __get__(self, instance, value):
return self.value
def __set__(self, instance, value):
self.value = value
I would add an attribute 'modified' to check if an instance of descriptor is modified. Es.
# Generic descriptor
class Attribute(object):
def __init__(self, value):
self.value = value
self.modified = False
def __get__(self, instance, value):
return self.value
def __set__(self, instance, value):
self.value = value
self.modified = True
How I can do this ?

Note that in your __get__ and __set__ methods, you actually want to access instance, not self (self being the actual Attribute object).
Here is one way of doing it:
class Attribute(object):
def __init__(self, attr):
self.attr = attr
def __get__(self, instance, owner):
return getattr(instance, self.attr)
def __set__(self, instance, value):
setattr(instance, self.attr, value)
instance.modified = True
class A(object):
def __init__(self):
self._f = 0
self.modified = False
f = Attribute('_f')
a = A()
a.f
=> 0
a.modified
=> False
a.f = 33
a.modified
=> True
Of course, this snippet can be improved in many ways, depending on what you're trying to achieve.

Related

how to use instance attribute to define a class attribute?

I'm trying to use the name in my init for my class attribute, attr but it seems that's impossible.
here's the code:
class B:
def __init__(self, val):
self.val = val
def __get__(self, instance, owner):
return owner.valEditNew(self.val)
def __set__(self, instance, value):
return
class A:
def __init__(self, name = 'def_name'):
self.name = name
attr = B('G120')
def valEditNew(val):
val += ' #edited'
return val
a = A('JJ')
print(a.attr)
that's it if i use self.name or name or ... in place of G120>>>
builtins.NameError: name 'self' is not defined
if that's not possible, can you show me the way?
If you want to access attribute of the instance that contains the descriptor object, use instance parameter of __get__ / __set__:
class B:
def __get__(self, instance, owner):
return instance.valEditNew(instance.name) # <---
def __set__(self, instance, value):
return
class A:
attr = B()
def __init__(self, name='def_name'):
self.name = name
def valEditNew(self, val):
val += ' #edited'
return val
a = A('JJ')
print(a.attr)
# prints => JJ #edited

understanding a python descriptors example (TypedProperty)

Here is a slightly modified version of some code found in a python book:
class TypedProperty(object):
def __init__(self,name,type,default=None):
self.name = "_" + name
self.type = type
self.default = default if default else type()
def __get__(self,instance,cls):
return getattr(instance,self.name,self.default)
def __set__(self,instance,value):
if not isinstance(value,self.type):
raise TypeError("Must be a %s" % self.type)
setattr(instance,self.name,value)
class Foo(object):
name = TypedProperty("name",str)
num = TypedProperty("num",int,42)
f = Foo()
f.name = 'blah'
My question: why are we creating attributes in f? In the code above, TypedProperty is written such that f.name = 'blah' creates the attribute "_name" in the instance f.
Why not save the values as attributes of the class TypedProperty? Here is what I had in mind:
class TypedProperty2(object):
def __init__(self, val, typ):
if not isinstance(val, typ):
raise TypeError()
self.value = val
self.typ = typ
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, val):
if not isinstance(val, self.typ):
raise TypeError()
self.value = val
Is this an arbitrary design decision?
All instances of the class will share the same instance of the descriptor (e.g. TypedProperty). So, if you store the value on the TypedProperty, then all instances of Foo will have the same value for the name and num values. This is usually not desirable (or expected) for descriptors.
e.g. if you run the following script:
class TypedProperty2(object):
def __init__(self, val, typ):
if not isinstance(val, typ):
raise TypeError()
self.value = val
self.typ = typ
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, val):
if not isinstance(val, self.typ):
raise TypeError()
self.value = val
class Foo(object):
name = TypedProperty2("name", str)
f1 = Foo()
f1.name = 'blah'
f2 = Foo()
print(f2.name)
f2.name = 'bar'
print(f1.name)
You'll see the following output:
blah
bar
so we can see that initially f2 had f1's name and then, after changing the name of f2, f1 picked up f2's name.

Using __setattr__ + __slots__ in a python3 class

I am trying to be all fancy with sub element attribute access in a custom class hierarchy.
My fanciness works in that I can successfully use descriptors to do this.
I want to be even more fancy and make the class RefHolder (shown below in the testcase) use slots to save space.
When I try to use slots though, I get RuntimeError: maximum recursion depth exceeded
Note that I have already tried looking at existing solutions for this, the most closely matching I could find being this one:
https://stackoverflow.com/a/19566973/1671693
I have tried this in the testcase below but I am still get the runtimeerror.
Note that in the testcase, if the commented lines are used instead of the ones directly beneath them and __slots__ is removed from RefHolder,
The testcase passes.
Any suggestions?
Additionally, I am creating an object for every attribute access which seems expensive, are there any suggestions on a more efficient way of achieving the same behavior? Thanks!
import unittest
class RefHolder():
__slots__ = ['__obj', 'get_value']
def __init__(self, obj, get_value=False):
self.__dict__['__obj'] = obj
self.__dict__['get_value']=get_value
def get_sub(self, name):
#attr = self.__dict__['__obj'].find_by_name(name)
attr = self.__dict__['__obj'].__get__(self, RefHolder).find_by_name(name)
if attr is None:
raise AttributeError("Can't find field {}".format(name))
return attr
def __getattr__(self, name):
attr = self.get_sub(name)
#if self.__dict__['get_value']:
if self.__dict__['get_value'].__get__(self, RefHolder):
return attr.Value
else:
return attr
def __setattr__(self, name, value):
attr = self.get_sub(name)
#if self.__dict__['get_value']:
if self.__dict__['get_value'].__get__(self, RefHolder):
attr.Value = value
else:
raise AttributeError("{} is read only in this context".format(name))
class ContainerAccess():
__slots__ = ['get_value']
def __init__(self, get_value=False):
self.get_value = get_value
def __get__(self, obj, objtype=None):
if obj is None:
return self
return RefHolder(obj, self.get_value)
def __set__(self, obj, value):
raise AttributeError("Read Only attribute".format(value))
class PropVal():
def __init__(self, val):
self.Value = val
#property
def Value(self):
return self._value
#Value.setter
def Value(self, value):
self._value = value
class T():
get = ContainerAccess()
getv = ContainerAccess(get_value=True)
def __init__(self):
self.store = {}
self._value = 0
def find_by_name(self, name):
return self.store.get(name)
class T2(T):
pass
class TestDesc(unittest.TestCase):
def test_it(self):
t = T()
t2 = T2()
t.store['my_val'] = PropVal(5)
t.store['my_val2'] = PropVal(6)
t2.store['my_val'] = PropVal(1)
self.assertEqual(t.get.my_val.Value, 5)
self.assertEqual(t.get.my_val2.Value, 6)
self.assertEqual(t2.get.my_val.Value, 1)
t.get.my_val.Value = 6
self.assertEqual(t.get.my_val.Value, 6)
with self.assertRaises(AttributeError):
t.get.blah.Value = 6
#self.assertEqual(t.get.my_other_val.Value, None)
self.assertEqual(t.getv.my_val, 6)
t.getv.my_val = 7
self.assertEqual(t.getv.my_val, 7)
with self.assertRaises(AttributeError):
t.get.my_val = 7

How can I make __init__ take a new value or an existing instance?

I have a pattern that looks similar to the following:
class Foobar(object): # instances of this class will be referenced by others
def __init__(self, value):
self.value = value
class Foo(object):
def __init__(self, value, foobar)
self.value = value
if isinstance(foobar, Foobar):
self.foobar = foobar
else:
self.foobar = Foobar(foobar)
class Bar(object):
def __init__(self, value, foobar)
self.value = value
if isinstance(foobar, Foobar):
self.foobar = foobar
else:
self.foobar = Foobar(foobar)
This allows Foo and Bar to take either a new value (to create a Foobar) or an existing instance of Foobar as their foobar argument.
I would like to get rid of this redundant code:
# ...
if isinstance(foobar, Foobar):
self.foobar = foobar
else:
self.foobar = Foobar(foobar)
I considered the following, but it doesn't work due to infinite recursion in Foobar.__new__():
class Foobar(object):
def __new__(cls, value):
if isinstance(value, cls):
return value
else:
return Foobar(value)
def __init__(self, value):
self.value = value
class Foo(object):
def __init__(self, value, foobar)
self.value = value
self.foobar = Foobar(foobar)
class Bar(object):
def __init__(self, value, foobar)
self.value = value
self.foobar = Foobar(foobar)
What is the best way to allow classes to create new instances or use existing instances depending on the values passed to __init__?
You can get rid of the recursion by calling the base class __new__():
class Foobar(object):
def __new__(cls, value):
if isinstance(value, cls):
return value
else:
return object.__new__(cls, value)
def __init__(self, value):
self.value = value
Note that the first parameter to __new__() is a class, not self.
That said, I'm not convinced that this is a useful pattern. In general, I'd recommend to accept instances in the constructor and leave the object construction to the calling code. While magic that does the Right Thing often seems convenient, it usually causes more problems down the road than it is worth.
Another option would be to factor out the duplicated code with a mixin class...
class Foobar(object):
def __init__(self, value):
self.value = value
class FoobarMixin(object):
def __init__(self, **kwargs):
foobar = kwargs['foobar']
if isinstance(foobar, Foobar):
self.foobar = foobar
else:
self.foobar = Foobar(foobar)
class Foo(FoobarMixin):
def __init__(self, value, **kwargs):
super(Foo, self).__init__(**kwargs)
self.value = value
print self.value, self.foobar
class Bar(FoobarMixin):
def __init__(self, value, **kwargs):
super(Bar, self).__init__(**kwargs)
self.value = value
print self.value, self.foobar
foo = Foo('foo', foobar='foobar')
bar = Bar('bar', foobar=Foobar('foobar'))
...which prints...
foo <__main__.Foobar object at 0x7fa0fedf6050>
bar <__main__.Foobar object at 0x7fa0fedeaf10>

Accessing descriptor instance

When one defines a descriptor value retrieval etc. is overriden, making the instance of the descriptor effectively unaccessible.
I.e. one can't write instance_with_descriptor_attr.descriptor_attr.some_method_on_descriptor()... won't work. My question is basically how one can still access the descriptor's instance anway...
As noted by eryksun, Martijn's solution works for properties but not all descriptors:
class Desc(object):
def __init__(self, val=None):
self.val = val
def __get__(self, obj, cls):
return self.val
def __set__(self, obj, val):
self.val = val
class Test(object):
x = Desc(5)
>>> o = Test()
>>> print o.x
5
>>> print Test.x
5
The reason it works for property descriptors can be seen in the example property descriptor implementation in the docs:
http://docs.python.org/2/howto/descriptor.html#properties
the key is the __get__ function:
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError, "unreadable attribute"
return self.fget(obj)
If obj is None it returns self, which is the instance of the descriptor itself. obj is the instance of the class accessing the descriptor. When you access the attribute from a class instance, obj is that instance, when you access it from a class object, then obj is None.
Changing the previous descriptor to:
class Desc(object):
def __init__(self, val=None):
self.val = val
def __get__(self, obj, cls):
if obj is None:
return self
return self.val
def __set__(self, obj, val):
self.val = val
class Test(object):
x = Desc(5)
yields (class must be re-defined if you're using a python shell)
o = Test()
>>> print o.x
5
>>> print Test.x
<__main__.Desc object at 0x23205d0>
You need to go up to the class itself:
type(instance_with_descriptor_attr).descriptor_attr
Demonstration:
>>> class Foo():
... #property
... def bar(self): return 'bar'
...
>>> foo = Foo()
>>> foo.bar
'bar'
>>> type(foo).bar
<property object at 0x109f24310>
if the __get__ method of the descriptor dont have a "return self" statement then the descriptor can only be accessed by the __dict__ attribute of the class:
class descriptor:
def __get__(self, instance, owner=None):
return 1
class A:
d = descriptor()
a = A()
a.d # --> 1
A.d # --> 1
A.__dict__['d'] # --> <__main__.descriptor object at ...>

Categories