Python Forum
Creating a set with dataclass and dict
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Creating a set with dataclass and dict
#1
Hi everyone.
I am trying to play with the new @dataclass and TypedDict from 3.7 and 3.8
But after searching and reading lots of documentation I cannot find a proper answer on this. I think the answer is simple but it's not clear to me.
Here the snippet:
from dataclasses import dataclass


@dataclass(frozen=True, eq=True, repr=False)
class Building:
    name: str
    property: dict


foo = Building(name="Test", property={"key": 10})
var = {foo}
and the error:
  File "<string>", line 3, in __hash__
TypeError: unhashable type: 'dict'
Ok the message is clear, but I don't understand why all nested element of a set need to be hashable. I don't find anything that tell me: a set take only hashable element that have itself hashable element

Obviously if I replace the property with a hashable object it works.

Thank you for your help

Hobbit
Reply
#2
I'm disappointed that I didn't find that information at https://docs.python.org/3/tutorial/datas....html#sets
They do mention it for dictionaries, but still not as clearly as I would have liked.

The easiest workaround for you is to use a frozenset instead of a set. If that doesn't solve your problem please describe the goal a bit more clearly.
Reply
#3
Ok, so I still think the official Python tutorial should have that info, but the reference docs do say:
https://docs.python.org/3/library/stdtyp...-frozenset Wrote:A set object is an unordered collection of distinct hashable objects.
Mutable data structures (such as sets) are not hashable, so they can't be in sets or used as dict keys.
Reply
#4
micseydel, thank you for your kind help.
But I am not sure I understood your answer.
You speak about using set in set or dict key. That's not what I meant.
My problem is the line 11 of my code. I have a dataclass that is Hashable (frozen and eq to True), but I cannot use it as an element of my set.

import collections
from dataclasses import dataclass


@dataclass(frozen=True, eq=True, repr=False)
class Building:
    name: str
    property: dict


foo = Building(name="Test", property={"key": 10})
print(isinstance(foo, collections.Hashable))
var = {foo}
Output:
F:/Perso/Python/board-game-solver/les-batisseurs/src/test.py:12: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.9 it will stop working print(isinstance(foo, collections.Hashable)) Traceback (most recent call last): File "F:/Perso/Python/board-game-solver/les-batisseurs/src/test.py", line 13, in <module> var = {foo} File "<string>", line 3, in __hash__ TypeError: unhashable type: 'dict' True Process finished with exit code 1
Thank you again !
Reply
#5
I think there is misunderstanding - frozen means that you cannot assign to fields, but in your case the property field is mutable type dict - i.e. you can change it without assignment (i.e. frozen argument is irrelevant in this case)

from dataclasses import dataclass
 
 # frozen dataclass with mutable field type
@dataclass(frozen=True, eq=True, repr=False)
class Building:
    name: str
    properties: dict

foo = Building(name="Test", properties={"key": 10})
foo.properties['key2'] = 20
print(foo.properties)


# dataclass, not frozen, assignment to field possible
@dataclass(eq=True, repr=False)
class Building:
    name: str
    count: int
 
foo = Building(name="Test", count=1)
foo.count = 2
print(foo.count)


# frozen dataclass, assignment to filed not possible
@dataclass(frozen=True, eq=True, repr=False)
class Building:
    name: str
    count: int
 
foo = Building(name="Test", count=1)
foo.count = 2
Output:
{'key': 10, 'key2': 20} 2 Traceback (most recent call last): File "***", line 32, in <module> foo.count = 2 File "<string>", line 3, in __setattr__ dataclasses.FrozenInstanceError: cannot assign to field 'count'

depending on what your ultimate goal is, you may use namedtuple
from dataclasses import dataclass
from collections import namedtuple

properties = namedtuple('Properties', 'key key2')

# frozen dataclass with namedtuple field type
@dataclass(frozen=True, eq=True, repr=False)
class Building:
    name: str
    properties: namedtuple

foo = Building(name="Test", properties=properties(key=10, key2=20))

print(foo.properties)
print(foo.properties.key2)
var = {foo}
print(var, type(var))
Output:
Properties(key=10, key2=20) 20 {<__main__.Building object at 0x7f0f27e70a50>} <class 'set'>
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply
#6
Thank you for the explanation !
You helped me a lot.
I agree NamedTuple is useful here. But what I don't understand is the following sentence from the doc:
A set object is an unordered collection of distinct hashable objects.
And my class Building is Hashable. So why it needs to me immutable in order to be used in a set ? I don't find a clear reference.

So here the result that I have done and that is working. I will explain my aim in an other post as it would be different that the title of this post. My question of this post was only about why I cannot use a hashable object in a set.

from dataclasses import dataclass

@dataclass(frozen=True, eq=True)
class BuildingProperty:
    stone: int
    wood: int
    architecture: int
    decoration: int
    victory_point: int
    money: int


@dataclass(frozen=True, eq=True)
class Building:
    name: str
    properties: BuildingProperty


foo = Building(name="Test", properties=BuildingProperty(stone=2, wood=1, architecture=0, decoration=1, victory_point=8, money=4))
var = {foo}
Reply
#7
(Jan-16-2020, 09:42 AM)hobbitdur Wrote: And my class Building is Hashable.
No, it's not hashable*, because one of the fields is dict, which is mutable and not hashable, thus the whole Building dataclass is not hashable*.
as it says in the docs:

Quote:hashable

An object is hashable if it has a hash value which never changes during its lifetime (it needs a __hash__() method), and can be compared to other objects (it needs an __eq__() or __cmp__() method). Hashable objects which compare equal must have the same hash value.

Hashability makes an object usable as a dictionary key and a set member, because these data structures use the hash value internally.

All of Python’s immutable built-in objects are hashable, while no mutable containers (such as lists or dictionaries) are. Objects which are instances of user-defined classes are hashable by default; they all compare unequal (except with themselves), and their hash value is derived from their id().

EDIT, based on the discussion below, to avoid confusion:
* hashable in the sense that when you pass object to hash() function it will not raise error, not that it is instance of collections.abc.Hashable and provide __hash__() method
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply
#8
EDIT: ninjad by buran.

I think there are some subtleties. Not all objects which are considered as immutable are hashable.

Python glossary: immutable:

Quote:An object with a fixed value. Immutable objects include numbers, strings and tuples. Such an object cannot be altered. A new object has to be created if a different value has to be stored. They play an important role in places where a constant hash value is needed, for example as a key in a dictionary.

Objects, values and types

Quote:The value of some objects can change. Objects whose value can change are said to be mutable; objects whose value is unchangeable once they are created are called immutable. (The value of an immutable container object that contains a reference to a mutable object can change when the latter’s value is changed; however the container is still considered immutable, because the collection of objects it contains cannot be changed. So, immutability is not strictly the same as having an unchangeable value, it is more subtle.) An object’s mutability is determined by its type; for instance, numbers, strings and tuples are immutable, while dictionaries and lists are mutable.
I'm not 'in'-sane. Indeed, I am so far 'out' of sane that you appear a tiny blip on the distant coast of sanity. Bucky Katt, Get Fuzzy

Da Bishop: There's a dead bishop on the landing. I don't know who keeps bringing them in here. ....but society is to blame.
Reply
#9
(Jan-16-2020, 09:49 AM)buran Wrote:
(Jan-16-2020, 09:42 AM)hobbitdur Wrote: And my class Building is Hashable.
No, it's not hashable, because one of the fields is dict, which is mutable and not hashable, thus the whole Building dataclass is not hashable.

Thank you.
Ok, then why the line print(isinstance(foo, collections.Hashable)) return True ?
Reply
#10
as stated in the docs collections.abc.Hashable are classes that provide __hash__() method. i.e. it doesn't mean it's guaranteed that when you call that method it will not raise error.
Not exactly the same, but it similar - if you look at data model docs for object.__hash__()
Quote:A class which defines its own __hash__() that explicitly raises a TypeError would be incorrectly identified as hashable by an isinstance(obj, collections.abc.Hashable) call.
Your Buildings has __hash__ method but when called it raises TypeError.

dataclasses.dataclass [may] provides __hash__() - check unsafe_hash attribute for details. In your case it will be forced to create __hash__ method because both frozen and eq are set True. However when you it is called when creating the set you get the TypeError. You will get same error if you try to call hash(some_dict).

Note that this is not unique to your case., e.g.

import collections.abc
foo = ([1, 2], 'a')
print(type(foo))
print(isinstance(foo, collections.abc.Hashable))
hash(foo)
Output:
<class 'tuple'> True Traceback (most recent call last): File "/home/buran/sandbox/pyforum.py", line 5, in <module> hash(foo) TypeError: unhashable type: 'list'
Bottom line - be careful with nested data structures.
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  append str to list in dataclass flash77 6 495 Mar-14-2024, 06:26 PM
Last Post: flash77
  Sort a dict in dict cherry_cherry 4 75,426 Apr-08-2020, 12:25 PM
Last Post: perfringo

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020