How to determine the class of a descriptor? - python

In this example code, I would like to determine if x is an instance of TestProperty:
class TestProperty(object):
def __init__(self, name):
self._name = name
def __get__(self, instance, cls):
return getattr(instance, self._name)
def __set_(self, instance, value):
setattr(instance, self._name, value)
class Test(object):
x = TestProperty("x")
print isinstance(Test.x, TestProperty)
However, I get the following exception:
Traceback (most recent call last):
File "/home/zenoss/testproperties.py", line 14, in <module>
print isinstance(Test.x, TestProperty)
File "/home/zenoss/testproperties.py", line 6, in __get__
return getattr(instance, self._name)
AttributeError: 'NoneType' object has no attribute 'x'
Is there anyway to tell if an attribute is an instance of a class when it is a descriptor?

With the current __get__, Test.x causes the AttributeError because when the code accessing the descriptor using class, instance is passed None; (=> getattr(None, 'x') => None.x)
You should modify __get__ to handle such case:
>>> class TestProperty(object):
... def __init__(self, name):
... self._name = name
... def __get__(self, instance, cls):
... if instance is None: # To handle access through class, not instance
... return self # returns the TestProperty instance itself.
... return getattr(instance, self._name)
... def __set_(self, instance, value):
... setattr(instance, self._name, value)
...
>>> class Test(object):
... x = TestProperty("x")
...
>>> isinstance(Test.x, TestProperty)
True
BTW, as you may know, with x = TestProperty("x"), accessing x attribute through an instance will cause another exception, because it will call the __get__ (-> getattr(..) -> __get__ -> getattr(..) -> ...) recursively until stack overflow.

The best way to implement a property is with the #property decorator:
class TestProperty(object):
def __init__(self, name):
self._name = name
#property
def name(self):
"""Getter for '_name'."""
return self._name
#name.setter
def name(self, value):
"""Setter for '_name'."""
self._name = value
class Test(object):
x = TestProperty("x")
print(isinstance(Test.x, TestProperty))
It returns True when I run it.
See the documentation for #property at https://docs.python.org/3/library/functions.html#property.

Related

Can not make property and __getattr__ working together

I am working on a python class that has declared properties, and in which I want to add extra attributes at object instanciation (passed in the init method).
I want them to be read and written.
Finally, I don't want the user to be able to declare custom attributes; it should raise an Error.
class Person:
__slots__ = ["_name", "__dict__"]
def __init__(self, name, extra_arg):
self.__dict__[extra_arg] = None
self._name = name
#property
def name(self):
return self._name
#name.setter
def name(self, value):
self._name = value
def __getattr__(self, item):
if item in self.__dict__:
return self.__dict__[item]
raise AttributeError(item)
person = Person("gribouille", "hello")
person.custom_attribute = value # I want to prevent this
In this example, I can't manage to prevent new attributes to be declared.
When I override setattr method, it seems to collide with my property and I can't manage to retrieve my "name" attribute.
How about checking for existing attributes via hasattr and __slots__?
class Person:
__slots__ = ["_name", "__dict__"]
def __init__(self, name, extra_arg):
self.__dict__[extra_arg] = None
self._name = name
#property
def name(self):
return self._name
#name.setter
def name(self, value):
self._name = value
def __getattr__(self, item):
if item in self.__dict__:
return self.__dict__[item]
raise AttributeError(item)
def __setattr__(self, attr_name, attr_value):
if not (hasattr(self, attr_name) or attr_name in self.__slots__):
raise AttributeError(attr_name)
super().__setattr__(attr_name, attr_value)
person = Person("gribouille", "hello")
person.name = "test"
person.custom_attribute = None # Now: AttributeError: custom_attribute
person.custom_attribute = value # I want to prevent this
To achieve this your class should do NOT have __dict__ attribute, that is __slots__ must not contain __dict__. Consider following simple example
class C1:
__slots__ = ["__dict__"]
class C2:
__slots__ = ["x","y"]
c1 = C1()
c1.custom = "hello"
print(c1.custom) # hello
c2 = C2()
c2.x = 10
c2.y = 30
print(c2.x,c2.y) # 10 30
c2.z = 100 # cause AttributeError: 'C2' object has no attribute 'z'

Python descriptor for type checks and immutability

Read the Python Cookbook and saw descriptors, particularly the example for enforcing types when using class attributes. I am writing a few classes where that would be useful, but I would also like to enforce immutability. How to do it? Type checking descriptor adapted from the book:
class Descriptor(object):
def __init__(self, name=None, **kwargs):
self.name = name
for key, value in kwargs.items():
setattr(self, key, value)
def __set__(self, instance, value):
instance.__dict__[self.name] = value
# by default allows None
class Typed(Descriptor):
def __init__(self, expected_types=None, **kwargs):
self.expected_types = expected_types
super().__init__(**kwargs)
def __set__(self, instance, value):
if value is not None and not isinstance(value, self.expected_types):
raise TypeError('Expected: {}'.format(str(self.expected_types)))
super(Typed, self).__set__(instance, value)
class T(object):
v = Typed(int)
def __init__(self, v):
self.v = v
Attempt #1: add a self.is_set attribute to Typed
# by default allows None
class ImmutableTyped(Descriptor):
def __init__(self, expected_types=None, **kwargs):
self.expected_types = expected_types
self.is_set = False
super().__init__(**kwargs)
def __set__(self, instance, value):
if self.is_set:
raise ImmutableException(...)
if value is not None and not isinstance(value, self.expected_types):
raise TypeError('Expected: {}'.format(str(self.expected_types)))
self.is_set = True
super(Typed, self).__set__(instance, value)
Wrong, because when doing the following, ImmutableTyped is 'global' in the sense that it's a singleton throughout all instances of the class. When t2 is instantiated, is_set is already True from the previous object.
class T(object):
v = ImmutableTyped(int)
def __init__(self, v):
self.v = v
t1 = T()
t2 = T() # fail when instantiating
Attempt #2: Thought instance in __set__ refers to the class containing the attribute so tried to check if instance.__dict__[self.name] is still a Typed. That is also wrong.
Idea #3: Make Typed be used more similar to #property by accepting a 'fget' method returning the __dict__ of T instances. This would require the definition of a function in T similar to:
#Typed
def v(self):
return self.__dict__
which seems wrong.
How to implement immutability AND type checking as a descriptor?
Now this is my approach to the problem:
class ImmutableTyped:
def __set_name__(self, owner, name):
self.name = name
def __init__(self, *, immutable=False, types=None)
self.immutable == immutable is True
self.types = types if types else []
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if self.immutable is True:
raise TypeError('read-only attribute')
elif not any(isinstance(value, cls)
for cls in self.types):
raise TypeError('invalid argument type')
else:
instance.__dict__[self.name] = value
Side note: __set_name__ can be used to allow you to not specify the attribute name in initialisation. This means you can just do:
class Foo:
bar = ImmutableTyped()
and the instance of ImmutableTyped will automatically have the name attribute bar since I typed for that to occur in the __set_name__ method.
Could not succeed in making such a descriptor. Perhaps it's also unnecessarily complicated. The following method + property use suffices.
# this also allows None to go through
def check_type(data, expected_types):
if data is not None and not isinstance(data, expected_types):
raise TypeError('Expected: {}'.format(str(expected_types)))
return data
class A():
def __init__(self, value=None):
self._value = check_type(value, (str, bytes))
#property
def value(self):
return self._value
foo = A()
print(foo.value) # None
foo.value = 'bla' # AttributeError
bar = A('goosfraba')
print(bar.value) # goosfraba
bar.value = 'bla' # AttributeError
class ImmutableTyped(object):
def __set_name__(self, owner, name):
self.name = name
def __init__(self, *, types=None):
self.types = tuple(types or [])
self.instances = {}
return None
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
is_set = self.instances.setdefault(id(instance), False)
if is_set:
raise AttributeError("read-only attribute '%s'" % (self.name))
if self.types:
if not isinstance(value, self.types):
raise TypeError("invalid argument type '%s' for '%s'" % (type(value), self.name))
self.instances[id(instance)] = True
instance.__dict__[self.name] = value
return None
Examples:
class Something(object):
prop1 = ImmutableTyped(types=[int])
something = Something()
something.prop1 = "1"
Will give:
TypeError: invalid argument type '<class 'str'>' for 'prop1'
And:
something = Something()
something.prop1 = 1
something.prop1 = 2
Will give:
TypeError: read-only attribute 'prop1'

Python: test descriptors assigned correctly

Considering this example:
>>> class Bar(object):
...
... def __init__(self, name):
... self.name = name
... def __set__(self, instance, value):
... setattr(instance, self.name, value)
... def __get__(self, instance, owner):
... return getattr(instance, self.name, owner)
...
>>> class Foo(object):
... bat = Bar('bat')
...
>>> Foo.bat
<class 'Foo'>
>>> type(Foo.bat)
<class 'type'> # how would you get <class 'Bar'> ?
I want to write some pytests that assert the correct descriptor has been assigned to the correct attribute.
But I don't seem to be able to check the type of a descriptor once it has been assigned
I'm not sure what you're trying to do with your descriptor, but typically you want to pass back the descriptor itself when an instance is not passed:
class Bar(object):
def __init__(self, name):
self.name = name
def __set__(self, obj, value):
setattr(obj, self.name, value)
def __get__(self, obj, cls):
if obj is None:
return self
return getattr(obj, self.name)
class Foo(object):
bat = Bar('bat')
Foo.bat
# <__main__.Bar at 0x7f202accbf50>
You can override the usual lookup (which uses the very descriptor you're trying to see, whether or not you call type on the result) with vars(Foo)['bat'].

python nested attributes encapsulation

I have some question about encapsulation nested attributes in python. Let's assume few classes:
Here we have a main class (DataWrapper) that includes two more classes: InnerWrapper1 and InnerWrapper2. Both inner wrappers includes two attributes.
class DataWrapper(object):
#property
def inner_wrapper1(self):
return self.__inner_wrapper1
#inner_wrapper1.setter
def inner_wrapper1(self, value):
self.__inner_wrapper1 = value
#property
def inner_wrapper2(self):
return self.__inner_wrapper2
#inner_wrapper2.setter
def inner_wrapper2(self, value):
self.__inner_wrapper2 = value
class InnerWrapper1(object):
#property
def property1(self):
return self.__property1
#property1.setter
def property1(self, value):
self.__property1 = value
#property
def property2(self):
return self.__property2
#property2.setter
def property2(self, value):
self.__property2 = value
class InnerWrapper2(object):
#property
def property3(self):
return self.__property3
#property3.setter
def property3(self, value):
self.__property3 = value
#property
def property4(self):
return self.__property4
#property4.setter
def property4(self, value):
self.__property4 = value
Is it possible to override somehow getattr and setattr methods to make possible below encapsulation? What I want to achieve is to have an access to those nested attributes from the top class- DataWrapper.
data_wrapper = DataWrapper()
data_wrapper.property1 = "abc"
...
var = data_wrapper.property2
...
The first thing that came to my mind was to execute hasattr in getattr, but that gave a maximum recursion depth...
Here's a complete code:
class DataWrapper(object):
def __init__(self):
self.inner_wrapper1 = InnerWrapper1()
self.inner_wrapper2 = InnerWrapper2()
#property
def inner_wrapper1(self):
return self.__inner_wrapper1
#inner_wrapper1.setter
def inner_wrapper1(self, value):
self.__inner_wrapper1 = value
#property
def inner_wrapper2(self):
return self.__inner_wrapper2
#inner_wrapper2.setter
def inner_wrapper2(self, value):
self.__inner_wrapper2 = value
def __setattr__(self, attribute, value):
#if attribute in {'innerwrapper1', 'innerwrapper2'}:
if attribute in ['inner_wrapper1', 'inner_wrapper2']:
return super(DataWrapper, self).__setattr__(attribute, value)
if hasattr(self.inner_wrapper1, attribute):
return setattr(self.inner_wrapper1, attribute, value)
elif hasattr(self.inner_wrapper2, attribute):
return setattr(self.inner_wrapper2, attribute, value)
def __getattr__(self, attribute):
try:
return getattr(self.inner_wrapper1, attribute)
except AttributeError: pass
try:
return getattr(self.inner_wrapper2, attribute)
except AttributeError: pass
class InnerWrapper1(object):
#property
def property1(self):
return self.__property1
#property1.setter
def property1(self, value):
self.__property1 = value
#property
def property2(self):
return self.__property2
#property2.setter
def property2(self, value):
self.__property2 = value
class InnerWrapper2(object):
#property
def property3(self):
return self.__property3
#property3.setter
def property3(self, value):
self.__property3 = value
#property
def property4(self):
return self.__property4
#property4.setter
def property4(self, value):
self.__property4 = value
def main():
data_wrapper = DataWrapper()
data_wrapper.property1 = "abc"
if __name__ == "__main__":
main()
You get an infinite recursion error because you forgot to take into account setting the inner_wrapper1 and inner_wrapper2 attributes in your __init__ method.
When you do this:
self.inner_wrapper1 = InnerWrapper()
Python will also use your __setattr__ method. This then tries to use self.inner_wrapper1 which doesn't yet exist so __getattr__ is called, which tries to use self.inner_wrapper1 which doesn't yet exist, and you enter into an infinite recursion loop.
In __setattr__ delegate attribute setting to the superclass:
def __setattr__(self, attribute, value):
if attribute in {'innerwrapper1', 'innerwrapper2'}:
return super(DataWrapper, self).__setattr__(attribute, value)
if hasattr(self.inner_wrapper1, attribute):
return setattr(self.inner_wrapper1, attribute, value)
elif hasattr(self.inner_wrapper2, attribute):
return setattr(self.inner_wrapper2, attribute, value)
If you used a single leading underscore for 'private' attributes (so _innerwrapper1 and _innerwrapper2) you could just test for that:
def __setattr__(self, attribute, value):
if attribute[0] == '_': # private attribute
return super(DataWrapper, self).__setattr__(attribute, value)
so you don't have to hardcode a whole set of names.
Since your updated full script uses __inner_wrapper1 and __inner_wrapper2 as the actual attribute names, and you are using properties, you'll have to adjust your __setattr__ test to look for those names. Because you are using double-underscore names you need to adjust for the name mangling of such attributes:
def __setattr__(self, attribute, value):
if attribute in {
'inner_wrapper1', 'inner_wrapper2',
'_DataWrapper__inner_wrapper1', '_DataWrapper__inner_wrapper2'}:
return super(DataWrapper, self).__setattr__(attribute, value)
Unless you are going to subclass DataWrapper and must protect your attributes from accidental overriding, I'd avoid using double-underscored names altogether, however. In Pythonic code, you don't worry about other code accessing attributes, there is no concept of truly private attributes.
Using properties is also overkill here; properties don't buy you encapsulation, in Python you'd only use those to simplify the API (replacing a method call with attribute access).
Note that the hasattr() tests for the InnerWrapper* property* attributes will fail because you don't have default values:
>>> inner = InnerWrapper1()
>>> hasattr(inner, 'property1')
False
hasattr() doesn't test for properties, it simply tries to access an attribute and if any exception is raised it returns False:
>>> inner = InnerWrapper1()
>>> hasattr(inner, 'property1')
False
>>> inner.property1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 43, in property1
AttributeError: 'InnerWrapper1' object has no attribute '_InnerWrapper1__property1'
>>> inner.property1 = 'foo'
>>> inner.property1
'foo'
>>> hasattr(inner, 'property1')
True
By removing all the #property objects you can simplify this greatly:
class DataWrapper(object):
def __init__(self):
self._inner_wrapper1 = InnerWrapper1()
self._inner_wrapper2 = InnerWrapper2()
def __setattr__(self, attribute, value):
if attribute[0] == '_':
return super(DataWrapper, self).__setattr__(attribute, value)
if hasattr(self._inner_wrapper1, attribute):
return setattr(self._inner_wrapper1, attribute, value)
elif hasattr(self._inner_wrapper2, attribute):
return setattr(self._inner_wrapper2, attribute, value)
def __getattr__(self, attribute):
try:
return getattr(self._inner_wrapper1, attribute)
except AttributeError: pass
return getattr(self._inner_wrapper2, attribute)
class InnerWrapper1(object):
property1 = None
property2 = None
class InnerWrapper2(object):
property3 = None
property4 = None

determine a class attribute is Property?

Is there a way to determine a class attribute is a Property (with __get__ and __set__ or not?
The method in
Determine if given class attribute is a property or not, Python object looks like only work for property decorator, which is not work in my case.
class Property(object):
_value = None
def __get__(self, instance, owner):
return self._value
def __set__(self, instance, value):
self._value = value * 2
class A(object):
b = Property()
>>> a = A()
>>> type(A.p)
<type 'NoneType'>
>>> type(a.p)
<type 'NoneType'>
Your descriptor returns None because it is also invoked for classes (the instance attribute is set to None when __get__ is called for that scenario).
You need to retrieve it without invoking the descriptor protocol, reaching into the class __dict__ attribute is the most direct path:
A.__dict__['p']
See the Python Descriptor HOWTO for more details on how and when descriptors are invoked.
Alternatively, do as the property object does and return self when instance is set to None (so when accessed on a class):
class Property(object):
_value = None
def __get__(self, instance, owner):
if instance is None:
return self
return self._value
def __set__(self, instance, value):
self._value = value * 2
Also see How does the #property decorator work?
Demo:
>>> class Property(object):
... def __get__(self, instance, owner):
... return self._value
... def __set__(self, instance, value):
... self._value = value * 2
...
>>> class A(object):
... b = Property()
...
>>> A.__dict__['b']
<__main__.Property object at 0x103097910>
>>> type(A.__dict__['b'])
<class '__main__.Property'>
>>> class Property(object):
... _value = None
... def __get__(self, instance, owner):
... if instance is None:
... return self
... return self._value
... def __set__(self, instance, value):
... self._value = value * 2
...
>>> class A(object):
... b = Property()
...
>>> A.b
<__main__.Property object at 0x10413d810>

Categories

Resources