4

I'm trying to combine dataclasses, properties and lru_caches for some computational science code:

from dataclasses import dataclass
from typing import Any
from functools import lru_cache
@dataclass
class F:
    a: Any = 1
    b: Any = 2
    c: Any = 3
    @property
    @lru_cache(1)
    def d(self):
        print('Computing d')
        return (self.a+self.b)**self.c
f=F()
print(f.d)
print(f.d)

I hoped to see

Computing d
27
27

but get

TypeError: unhashable type: 'F'

Is there a way to fix this?

Bananach
  • 2,016
  • 26
  • 51
  • Please see the edit to my answer and have a read through the documentation page cited to better understand why your problem occurs. – N Chauhan Aug 05 '19 at 09:20
  • Maybe [postprocessing on `init=false` fields](https://docs.python.org/3/library/dataclasses.html#post-init-processing) could help you here. – Arne Aug 06 '19 at 06:27
  • 1
    Instead of combining proeprty and lru_cache, cached_property will work: https://docs.python.org/3/library/functools.html#functools.cached_property – youknowone Nov 09 '19 at 14:17

1 Answers1

4

lru_cache is like memoization so it hashes arguments passed to the function and stores the result. Your class isn’t hashable. To make it hashable, add something like this

class F:
    ....
    def __hash__(self):
        return hash((self.a, self.b, self.c))

The reason for this is that these 3 attributes make each instance ‘unique’ - we don’t need to hash the methods as all instances have the same methods.

On most normal classes, its __dict__ is used for general hashing unless a __hash__ method is found. The dataclass documentation explains that it is possible for the dataclass to generate a hashing method but it depends on how you set up the dataclass as by default the object is assumed to be mutable (and mutable objects like lists can’t be hashed).

The dataclass documentation states that a hash method will be generated if the parameters eq and frozen are set to True when decorating with @dataclass(), but your application may not warrant this as frozen prohibits attribute assignment on instances.

https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass

joel
  • 6,359
  • 2
  • 30
  • 55
N Chauhan
  • 3,407
  • 2
  • 7
  • 21
  • I have never used dataclasses myself, but after a search on StackOverflow I was led to this: https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass. – N Chauhan Aug 05 '19 at 09:18
  • Thanks for the update. It made me realize that I cannot make my life easy in my real class and simply let `def __hash__(self): return 0`. Due to python's ''light-OO'' approach, the lru_cached methods of all instances are really the same method, called with different `self` arguments, so if I have two instances of `F` and use my fake hash, they'll give the same answer for `self.d` – Bananach Aug 05 '19 at 10:21
  • Exactly right. The method the instances ‘have’ is actually the same object held in the class itself so each instance must have a new hash to differentiate them. – N Chauhan Aug 05 '19 at 10:29