Skip to content

Commit

Permalink
fix obj state
Browse files Browse the repository at this point in the history
  • Loading branch information
albertz committed Oct 8, 2024
1 parent f13bca0 commit af55b52
Showing 1 changed file with 60 additions and 17 deletions.
77 changes: 60 additions & 17 deletions sisyphus/hash.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import enum
import hashlib
from inspect import isclass, isfunction
from inspect import isclass, isfunction, ismemberdescriptor


def md5(obj):
Expand Down Expand Up @@ -52,24 +52,18 @@ def get_object_state(obj):

if hasattr(obj, "__sis_state__"):
state = obj.__sis_state__()
elif getattr(obj, "__class__", None) and getattr(obj.__class__, "__dictoffset__", 0) > 0:
# This type has some native attribs, which are not in __dict__ (and neither in __slots__).
# `dir()` should be able to list those.
# One example is the native `_functools.partial`.
# https://github.com/rwth-i6/sisyphus/issues/207
return {k: getattr(obj, k) for k in dir(obj) if not k.startswith("__")}
elif hasattr(obj, "__getstate__"):
# Note: Since Python 3.11, there is a default object.__getstate__.
# However, this default object.__getstate__ is not correct for some native types, e.g. _functools.partial.
# https://github.com/rwth-i6/sisyphus/issues/207
elif hasattr(obj, "__getstate__") and obj.__class__.__getstate__ is not getattr(object, "__getstate__", None):
state = obj.__getstate__()
elif hasattr(obj, "__dict__") and not hasattr(obj, "__slots__"):
state = obj.__dict__
elif hasattr(obj, "__dict__") and hasattr(obj, "__slots__"):
state = obj.__dict__.copy()
state.update({k: getattr(obj, k) for k in obj.__slots__ if hasattr(obj, k)})
elif hasattr(obj, "__slots__"):
state = {k: getattr(obj, k) for k in obj.__slots__ if hasattr(obj, k)}
else:
assert args is not None, "Failed to get object state of: %s" % repr(obj)
state = None
state = _getmembers(obj)
print(" ***", state)
if not state and not hasattr(obj, "__dict__") and not hasattr(obj, "__slots__"):
# Keep compatibility with old behavior.
assert args is not None, "Failed to get object state of: %s" % repr(obj)
state = None

if isinstance(obj, enum.Enum):
assert isinstance(state, dict)
Expand Down Expand Up @@ -138,3 +132,52 @@ def _obj_type_qualname(obj) -> bytes:
# In Python >=3.11, keep hash same as in Python <=3.10, https://github.com/rwth-i6/sisyphus/issues/188
return b"EnumMeta"
return type(obj).__qualname__.encode()


def _getmembers(obj):
print("***", obj, type(obj), "dir:", dir(obj))
res = {}
if hasattr(obj, "__dict__"):
res.update(obj.__dict__)
if hasattr(obj, "__slots__"):
for key in obj.__slots__:
try:
res[key] = getattr(obj, key)
except AttributeError:
pass
# Note, there are cases where `__dict__` or `__slots__` don't contain all attributes,
# e.g. for some native types, e.g. _functools.partial.
# (https://github.com/rwth-i6/sisyphus/issues/207)
# `dir()` usually still lists those attributes.
# However, to keep the behavior as before, we only want to return the object attributes here,
# not the class attributes.
cls_dict = {}
for cls in reversed(type(obj).__mro__):
if getattr(cls, "__dict__", None):
cls_dict.update(cls.__dict__)
for key in dir(obj):
if key.startswith("__"):
continue
if key in res:
continue
# Get class attribute first, to maybe skip descriptors.
has_cls_attr = False
cls_value = None
if key in cls_dict:
has_cls_attr = True
cls_value = cls_dict[key]
if hasattr(cls_value, "__get__"): # descriptor
if not ismemberdescriptor(cls_value):
# descriptor are e.g. properties, bound methods, etc. We don't want to have those.
# but not member d
continue
print(" -", key, cls_value)
try:
value = getattr(obj, key)
except AttributeError:
# dir might not be reliable. just skip this
continue
if has_cls_attr and cls_value is value:
continue # this is a class attribute
res[key] = value
return res

0 comments on commit af55b52

Please sign in to comment.