@@ -1510,35 +1510,47 @@ class C:
15101510 """
15111511 if not _is_dataclass_instance (obj ):
15121512 raise TypeError ("asdict() should be called on dataclass instances" )
1513- return _asdict_inner (obj , dict_factory )
1513+ return _asdict_inner (obj , dict_factory , set () )
15141514
15151515
1516- def _asdict_inner (obj , dict_factory ):
1516+ def _asdict_inner (obj , dict_factory , seen ):
15171517 obj_type = type (obj )
15181518 if obj_type in _ATOMIC_TYPES :
15191519 return obj
1520- elif hasattr (obj_type , _FIELDS ):
1520+ # Guard against circular references, which would otherwise recurse until
1521+ # a RecursionError (or a crash on release builds). gh-94345
1522+ if id (obj ) in seen :
1523+ raise ValueError ("Circular reference detected" )
1524+ seen .add (id (obj ))
1525+ try :
1526+ return _asdict_inner_recurse (obj , obj_type , dict_factory , seen )
1527+ finally :
1528+ seen .discard (id (obj ))
1529+
1530+
1531+ def _asdict_inner_recurse (obj , obj_type , dict_factory , seen ):
1532+ if hasattr (obj_type , _FIELDS ):
15211533 # dataclass instance: fast path for the common case
15221534 if dict_factory is dict :
15231535 return {
1524- f .name : _asdict_inner (getattr (obj , f .name ), dict )
1536+ f .name : _asdict_inner (getattr (obj , f .name ), dict , seen )
15251537 for f in fields (obj )
15261538 }
15271539 else :
15281540 return dict_factory ([
1529- (f .name , _asdict_inner (getattr (obj , f .name ), dict_factory ))
1541+ (f .name , _asdict_inner (getattr (obj , f .name ), dict_factory , seen ))
15301542 for f in fields (obj )
15311543 ])
15321544 # handle the builtin types first for speed; subclasses handled below
15331545 elif obj_type is list :
1534- return [_asdict_inner (v , dict_factory ) for v in obj ]
1546+ return [_asdict_inner (v , dict_factory , seen ) for v in obj ]
15351547 elif obj_type is dict :
15361548 return {
1537- _asdict_inner (k , dict_factory ): _asdict_inner (v , dict_factory )
1549+ _asdict_inner (k , dict_factory , seen ): _asdict_inner (v , dict_factory , seen )
15381550 for k , v in obj .items ()
15391551 }
15401552 elif obj_type is tuple :
1541- return tuple ([_asdict_inner (v , dict_factory ) for v in obj ])
1553+ return tuple ([_asdict_inner (v , dict_factory , seen ) for v in obj ])
15421554 elif issubclass (obj_type , tuple ):
15431555 if hasattr (obj , '_fields' ):
15441556 # obj is a namedtuple. Recurse into it, but the returned
@@ -1559,24 +1571,24 @@ def _asdict_inner(obj, dict_factory):
15591571 # dict. Note that if we returned dicts here instead of
15601572 # namedtuples, we could no longer call asdict() on a data
15611573 # structure where a namedtuple was used as a dict key.
1562- return obj_type (* [_asdict_inner (v , dict_factory ) for v in obj ])
1574+ return obj_type (* [_asdict_inner (v , dict_factory , seen ) for v in obj ])
15631575 else :
1564- return obj_type (_asdict_inner (v , dict_factory ) for v in obj )
1576+ return obj_type (_asdict_inner (v , dict_factory , seen ) for v in obj )
15651577 elif issubclass (obj_type , (dict , frozendict )):
15661578 if hasattr (obj_type , 'default_factory' ):
15671579 # obj is a defaultdict, which has a different constructor from
15681580 # dict as it requires the default_factory as its first arg.
15691581 result = obj_type (obj .default_factory )
15701582 for k , v in obj .items ():
1571- result [_asdict_inner (k , dict_factory )] = _asdict_inner (v , dict_factory )
1583+ result [_asdict_inner (k , dict_factory , seen )] = _asdict_inner (v , dict_factory , seen )
15721584 return result
1573- return obj_type ((_asdict_inner (k , dict_factory ),
1574- _asdict_inner (v , dict_factory ))
1585+ return obj_type ((_asdict_inner (k , dict_factory , seen ),
1586+ _asdict_inner (v , dict_factory , seen ))
15751587 for k , v in obj .items ())
15761588 elif issubclass (obj_type , list ):
15771589 # Assume we can create an object of this type by passing in a
15781590 # generator
1579- return obj_type (_asdict_inner (v , dict_factory ) for v in obj )
1591+ return obj_type (_asdict_inner (v , dict_factory , seen ) for v in obj )
15801592 else :
15811593 return copy .deepcopy (obj )
15821594
@@ -1603,15 +1615,27 @@ class C:
16031615
16041616 if not _is_dataclass_instance (obj ):
16051617 raise TypeError ("astuple() should be called on dataclass instances" )
1606- return _astuple_inner (obj , tuple_factory )
1618+ return _astuple_inner (obj , tuple_factory , set () )
16071619
16081620
1609- def _astuple_inner (obj , tuple_factory ):
1621+ def _astuple_inner (obj , tuple_factory , seen ):
16101622 if type (obj ) in _ATOMIC_TYPES :
16111623 return obj
1612- elif _is_dataclass_instance (obj ):
1624+ # Guard against circular references, which would otherwise recurse until
1625+ # a RecursionError (or a crash on release builds). gh-94345
1626+ if id (obj ) in seen :
1627+ raise ValueError ("Circular reference detected" )
1628+ seen .add (id (obj ))
1629+ try :
1630+ return _astuple_inner_recurse (obj , tuple_factory , seen )
1631+ finally :
1632+ seen .discard (id (obj ))
1633+
1634+
1635+ def _astuple_inner_recurse (obj , tuple_factory , seen ):
1636+ if _is_dataclass_instance (obj ):
16131637 return tuple_factory ([
1614- _astuple_inner (getattr (obj , f .name ), tuple_factory )
1638+ _astuple_inner (getattr (obj , f .name ), tuple_factory , seen )
16151639 for f in fields (obj )
16161640 ])
16171641 elif isinstance (obj , tuple ) and hasattr (obj , '_fields' ):
@@ -1621,22 +1645,22 @@ def _astuple_inner(obj, tuple_factory):
16211645 # treated (see below), but we just need to create them
16221646 # differently because a namedtuple's __init__ needs to be
16231647 # called differently (see bpo-34363).
1624- return type (obj )(* [_astuple_inner (v , tuple_factory ) for v in obj ])
1648+ return type (obj )(* [_astuple_inner (v , tuple_factory , seen ) for v in obj ])
16251649 elif isinstance (obj , (list , tuple )):
16261650 # Assume we can create an object of this type by passing in a
16271651 # generator (which is not true for namedtuples, handled
16281652 # above).
1629- return type (obj )(_astuple_inner (v , tuple_factory ) for v in obj )
1653+ return type (obj )(_astuple_inner (v , tuple_factory , seen ) for v in obj )
16301654 elif isinstance (obj , (dict , frozendict )):
16311655 obj_type = type (obj )
16321656 if hasattr (obj_type , 'default_factory' ):
16331657 # obj is a defaultdict, which has a different constructor from
16341658 # dict as it requires the default_factory as its first arg.
16351659 result = obj_type (getattr (obj , 'default_factory' ))
16361660 for k , v in obj .items ():
1637- result [_astuple_inner (k , tuple_factory )] = _astuple_inner (v , tuple_factory )
1661+ result [_astuple_inner (k , tuple_factory , seen )] = _astuple_inner (v , tuple_factory , seen )
16381662 return result
1639- return obj_type ((_astuple_inner (k , tuple_factory ), _astuple_inner (v , tuple_factory ))
1663+ return obj_type ((_astuple_inner (k , tuple_factory , seen ), _astuple_inner (v , tuple_factory , seen ))
16401664 for k , v in obj .items ())
16411665 else :
16421666 return copy .deepcopy (obj )
0 commit comments