Cython will not perform any magic here: it will just delegate the calls to PyDict_GetItemWithError - basically the same thing the Python interpreter will do (but probably slightly faster).
However, an unicode-object (I assume we are speaking about Python3-strings) caches its hash-value (in PyUnicodeObject.hash-member field), so this needs to be calculated only once - which makes sense because an unicode-object is immutable, that means the hash cannot change.
Here is the CPython code responsible for the hash calculation/caching:
#define _PyUnicode_HASH(op) \ (((PyASCIIObject *)(op))->hash) ... static Py_hash_t unicode_hash(PyObject *self) { ... // if hash already calculated, return cached value if (_PyUnicode_HASH(self) != -1) return _PyUnicode_HASH(self); ... // else caclculate hash, cache value, return it x = _Py_HashBytes(PyUnicode_DATA(self), PyUnicode_GET_LENGTH(self) * PyUnicode_KIND(self)); _PyUnicode_HASH(self) = x; return x; }
So as you can see, there is no need for Cython to avoid the hash-recalculation - this optimization is already done by CPython.
By using Cython here, one could win up to 10-30% because it would eliminate the interpreter for this part of the code (see for example this SO-post) - not really much, but better than nothing.