Misspelled fields in a #dataclass Python class - python

How to make it raise an exception on setting a misspelled fields in a #dataclass-decorated Python class?
I want a practical way to do this. Do I need to write my own decorator instead?
#dataclass
class C(object):
x: int = 1
obj = C()
obj.y = 2 # should raise an exception

One straightforward way (which works with any class) is to define __slots__:
In [1]: from dataclasses import dataclass
In [2]: #dataclass
...: class Foo:
...: __slots__ = 'bar','baz'
...: bar: int
...: baz: int
...:
In [3]: foo = Foo(42, 88)
In [4]: foo.biz = 10
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-d52b60444257> in <module>()
----> 1 foo.biz = 10
AttributeError: 'Foo' object has no attribute 'biz'
The purpose of slots is to serve as a small optimization. It allows the instances of the class to use a symbol table instead of a dict as the namespace of the class. It increases the speed of attribute access slightly, and can significantly improve the per-instance memory usage (because the instance doesn't carry around a dict underneath the hood), however, it disallows dynamic attribute setting.
This is actually my favorite feature of __slots__.
Note, you must take care when using inheritance with slots, at least, if you want subclasses to retain the slots behavior.

One way to do so is to mark the dataclass as frozen:
>>> from dataclasses import dataclass
>>> #dataclass(frozen=True)
... class C:
... x: int = 1
...
>>> c = C()
>>> c.y = 1
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<string>", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'y'
But note that this makes all existing attributes, like x in this case, read-only as well.

You can provide your own __setattr__() method that only allows assignment to known fields.
Example:
#dataclass
class C:
x: int = 1
def __setattr__(self, k, v):
if k not in self.__annotations__:
raise AttributeError(f'{self.__class__.__name__} dataclass has no field {k}')
super().__setattr__(k, v)
In then fails/works like this:
[ins] In [3]: obj = C()
...: obj.y = 2
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-3-7a568eb098b1> in <module>
1 obj = C()
----> 2 obj.y = 2
<ipython-input-2-d30972f86fbb> in __setattr__(self, k, v)
5 def __setattr__(self, k, v):
6 if k not in self.__annotations__:
----> 7 raise AttributeError(f'{self.__class__.__name__} dataclass has no field {k}')
8 super().__setattr__(k, v)
9
AttributeError: C dataclass has no field y
[ins] In [4]: obj.x = 23
[ins] In [5]: obj.x
Out[5]: 23

Related

How to annotate an optional class variable

In Python 3 how do you add type annotations for a class variable that may or may not exist. This is distinct from a class variable that may be None. For example:
from typing import Optional
class Foo:
a: Optional[int]
# b: ???
def __init__(self, has_a: bool, has_b: bool):
if has_a:
self.a = 1
else
self.a = None
if has_b:
self.b = 2
Then
>>> x = Foo(False, False)
>>> repr(x.a)
'None'
>>> repr(x.b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'b'
What do I write in ??? to indicate to type checkers that b may be an int or it may not exist at all?

Expose object properties dynamically

In [1]: class Foo():
...: pass
...:
In [2]: class Qux():
...: def __init__(self):
...: item = Foo()
...:
In [3]: a = Foo()
In [4]: setattr(a, 'superpower', 'strength')
In [5]: a.superpower
Out[5]: 'strength'
In [6]: b = Qux()
In [7]: b.item = a
In [8]: b.superpower
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-8-cf0e287006f1> in <module>()
----> 1 b.superpower
AttributeError: Qux instance has no attribute 'superpower'
What I would like is to define some way of calling any attribute on Qux and have it return getattr(Qux.item, <attributename>). In other words, to have b.superpower work without explicitly defining:
#property
def superpower(self):
return getattr(self.item, 'superpower')
I don't want to lose access to any properties defined on Qux itself as well, but rather to expose properties defined on Foo if they are not also on Qux.
Define a __getattr__:
class Qux(Foo):
def __init__(self):
self.item = Foo()
def __getattr__(self, attr):
return getattr(self.item, attr)
__getattr__ gets called whenever someone tries to look up an attribute of the object, but fails through normal means.
It has an evil twin called __getattribute__, which always gets called and must be used with extreme caution.
You do that by defining __getattr__, not with a property. For any attribute that cannot be found with the standard protocol, Python will call the __getattr__ method of a class.
Moreover, to store the item, you have to assign it to self.item, otherwise it is thrown at the end of Qux.__init__.
Finally, inheriting from Foo seems unecessary in that case.
class Foo:
def __init__(self, superpower):
self.superpower = superpower
class Qux:
def __init__(self, foo_item):
self.item = foo_item
def __getattr__(self, name):
return getattr(self.item, name)
Example
f = Foo('strenght')
q = Qux(f)
print(q.superpower) # 'strenght'
Inheritance
Although, it seems you half-tried to implement this with inheritance. If your intent was to extend Qux behaviour with Foo, then inheritance would be the way to go.
class Foo:
def __init__(self, superpower):
self.superpower = superpower
class Qux(Foo):
def __getattr__(self, name):
return getattr(self.item, name)
Example
q = Qux('strenght')
print(q.superpower) # 'strenght'

Way to mask functions on Python Object

I have a class that inherit from OrderedDict, but I don't know if this is the right way to accomplish what I need.
I would like the class to have the duel method of the javascript '.' notation like obj.<property> and I would also like the users to be able to access the class properties like obj['myproperty'] but I was to hide all the key() and get() functions. The inheritance model is providing good functionality, but it cluttering up the object with additional methods that are not really needed.
Is it possible to get the dictionary behavior without all the other functions coming along?
For this discussion, let's assume my class is this:
from six.moves.urllib import request
import json
class MyClass(OrderedDict):
def __init__(self, url):
super(MyClass, self).__init__(url=url)
self._url = url
self.init()
def init(self):
# call the url and load the json
req = request.Request(self._url)
res = json.loads(request.urlopen(req).read())
for k,v in res.items():
setattr(self, k, v)
self.update(res)
self.__dict__.update(res)
if __name__ == "__main__":
url = "https://sampleserver5.arcgisonline.com/ArcGIS/rest/services?f=json"
props = MyClass(url=url)
props.currentVersion
Is there another way to approach this dilemma?
Thanks
If all you want is x['a'] to work the same way as x.a without any other functionality of dictionaries, then don't inherit from dict or OrderedDict, instead just forward key/indice operations (__getitem__, __setitem__ and __delitem__) to attribute operations:
class MyClass(object):
def __getitem__(self,key):
try: #change the error to a KeyError if the attribute doesn't exist
return getattr(self,key)
except AttributeError:
pass
raise KeyError(key)
def __setitem__(self,key,value):
setattr(self,key,value)
def __delitem__(self,key):
delattr(self,key)
As an added bonus, because these special methods don't check the instance variables for the method name it doesn't break if you use the same names:
x = MyClass()
x['__getitem__'] = 1
print(x.__getitem__) #still works
print(x["__getattr__"]) #still works
The only time it will break is when trying to use __dict__ since that is where the instance variables are actually stored:
>>> x = MyClass()
>>> x.a = 4
>>> x.__dict__ = 1 #stops you right away
Traceback (most recent call last):
File "<pyshell#36>", line 1, in <module>
x.__dict__ = 1
TypeError: __dict__ must be set to a dictionary, not a 'int'
>>> x.__dict__ = {} #this is legal but removes all the previously stored values!
>>> x.a
Traceback (most recent call last):
File "<pyshell#38>", line 1, in <module>
x.a
AttributeError: 'MyClass' object has no attribute 'a'
In addition you can still use the normal dictionary methods by using vars():
x = MyClass()
x.a = 4
x['b'] = 6
for k,v in vars(x).items():
print((k,v))
#output
('b', 6)
('a', 4)
>>> vars(x)
{'b': 6, 'a': 4}

Inheriting static dict via dict.update()

Subclasses in my application model inherit static attributes (stored as dictionaries) from the superclass, with each subclass using update() to add fields to it's own static field. But this didn't work how I expected it to. Here's the simple version:
In [19]: class A(object):
s = {1:1}
In [20]: class B(A):
s = A.s.update({2:2})
In [21]: class C(B):
s = B.s.update({3:3})
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-21-4eea794593c8> in <module>()
----> 1 class C(B):
2 s = B.s.update({3:3})
3
<ipython-input-21-4eea794593c8> in C()
1 class C(B):
----> 2 s = B.s.update({3:3})
3
AttributeError: 'NoneType' object has no attribute 'update
This DID work, however, when I was concatenating fields to a static list in each subclass. What am I missing?
update doesn't return a dict; it just modifies the receiver in place. For example, after s = A.s.update({2:2}), A.s is modified, and s is None. You can instead write something like
s = dict(B.s, **{3: 3})
to achieve what you want. Note that in python 3, this won't work since keyword arguments are enforced to be strings. You can write a helper function:
def merge(d1, d2):
d = dict(d1)
d.update(d2)
return d
And use s = merge(B.s, {3: 3}) instead.

Remove attribute from subclass in Python

Is there any way to remove an attribute from a subclass that is present in the parent?
In the following example
class A(object):
foo = 1
bar = 2
class B(A):
pass
# <desired code here>
b = B()
assert hasattr(b, 'bar') == False
Is there any code we can write to make the assertion pass?
class A(object):
foo = 1
bar = 2
class B(A):
#property
def bar(self):
raise AttributeError
>>> b = B()
>>> b.bar
Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
b.bar
File "<pyshell#15>", line 4, in bar
raise AttributeError
AttributeError
This works for me whe I don't want a specific attribute ('bar' in this case) to be listed in dir(A).
class A(object):
foo = 1
bar = 2
class B(A):
def ___init__(self):
self.delete()
def delete(self):
delattr(self, 'bar')
Basically, create a method (delete) in the subclass B that deletes that attribute and put that in the constructor.
Yes, using the magic of descriptors. See my blog post about it. Short version:
class nosubclasses(object):
def __init__(self, f, cls):
self.f = f
self.cls = cls
def __get__(self, obj, type=None):
if type == self.cls:
if hasattr(self.f, '__get__'):
return self.f.__get__(obj, type)
return self.f
raise AttributeError
Example:
In [2]: class MyClass(object):
...: x = 1
...:
In [3]: MyClass.x = nosubclasses(MyClass.x, MyClass)
In [4]: class MySubclass(MyClass):
...: pass
...:
In [5]: MyClass.x
Out[5]: 1
In [6]: MyClass().x
Out[6]: 1
In [80]: MySubclass.x
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-80-2b2f456dd101> in <module>()
----> 1 MySubclass.x
<ipython-input-51-7fe1b5063367> in __get__(self, obj, type)
8 return self.f.__get__(obj, type)
9 return self.f
---> 10 raise AttributeError
AttributeError:
In [81]: MySubclass().x
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-81-93764eeb9948> in <module>()
----> 1 MySubclass().x
<ipython-input-51-7fe1b5063367> in __get__(self, obj, type)
8 return self.f.__get__(obj, type)
9 return self.f
---> 10 raise AttributeError
AttributeError:
But as the commenter #delnan pointed out, this violates the Liskov substitutability principle. The motivation in my blog post was warranted, because the attribute did not describe the object itself. But in general, this breaks the whole point of being able to subclass in the first place, which is really the whole point of having classes at all.
By the way, the difference between my answer and #jamylak's is that in #jamylak's answer, attributes are removed on a per-subclass basis. If you made a class C(A), it would still have the bar attribute. In my answer, the class itself (well, actually the attribute), disallows subclasses from having the attribute, so that in one fell swoop, all subclasses don't have it.

Categories

Resources