@@ -1219,11 +1219,6 @@ def _get_slots(cls):
1219
1219
1220
1220
1221
1221
def _update_func_cell_for__class__ (f , oldcls , newcls ):
1222
- # Returns True if we update a cell, else False.
1223
- if f is None :
1224
- # f will be None in the case of a property where not all of
1225
- # fget, fset, and fdel are used. Nothing to do in that case.
1226
- return False
1227
1222
try :
1228
1223
idx = f .__code__ .co_freevars .index ("__class__" )
1229
1224
except ValueError :
@@ -1232,13 +1227,36 @@ def _update_func_cell_for__class__(f, oldcls, newcls):
1232
1227
# Fix the cell to point to the new class, if it's already pointing
1233
1228
# at the old class. I'm not convinced that the "is oldcls" test
1234
1229
# is needed, but other than performance can't hurt.
1235
- closure = f .__closure__ [idx ]
1236
- if closure .cell_contents is oldcls :
1237
- closure .cell_contents = newcls
1230
+ cell = f .__closure__ [idx ]
1231
+ if cell .cell_contents is oldcls :
1232
+ cell .cell_contents = newcls
1238
1233
return True
1239
1234
return False
1240
1235
1241
1236
1237
+ def _find_inner_functions (obj , _seen = None , _depth = 0 ):
1238
+ if _seen is None :
1239
+ _seen = set ()
1240
+ if id (obj ) in _seen :
1241
+ return None
1242
+ _seen .add (id (obj ))
1243
+
1244
+ _depth += 1
1245
+ if _depth > 2 :
1246
+ return None
1247
+
1248
+ obj = inspect .unwrap (obj )
1249
+
1250
+ for attr in dir (obj ):
1251
+ value = getattr (obj , attr , None )
1252
+ if value is None :
1253
+ continue
1254
+ if isinstance (obj , types .FunctionType ):
1255
+ yield obj
1256
+ return
1257
+ yield from _find_inner_functions (value , _seen , _depth )
1258
+
1259
+
1242
1260
def _add_slots (cls , is_frozen , weakref_slot ):
1243
1261
# Need to create a new class, since we can't set __slots__ after a
1244
1262
# class has been created, and the @dataclass decorator is called
@@ -1297,7 +1315,10 @@ def _add_slots(cls, is_frozen, weakref_slot):
1297
1315
# (the newly created one, which we're returning) and not the
1298
1316
# original class. We can break out of this loop as soon as we
1299
1317
# make an update, since all closures for a class will share a
1300
- # given cell.
1318
+ # given cell. First we try to find a pure function/properties,
1319
+ # and then fallback to inspecting custom descriptors.
1320
+
1321
+ custom_descriptors_to_check = []
1301
1322
for member in newcls .__dict__ .values ():
1302
1323
# If this is a wrapped function, unwrap it.
1303
1324
member = inspect .unwrap (member )
@@ -1306,10 +1327,27 @@ def _add_slots(cls, is_frozen, weakref_slot):
1306
1327
if _update_func_cell_for__class__ (member , cls , newcls ):
1307
1328
break
1308
1329
elif isinstance (member , property ):
1309
- if (_update_func_cell_for__class__ (member .fget , cls , newcls )
1310
- or _update_func_cell_for__class__ (member .fset , cls , newcls )
1311
- or _update_func_cell_for__class__ (member .fdel , cls , newcls )):
1312
- break
1330
+ for f in member .fget , member .fset , member .fdel :
1331
+ if f is None :
1332
+ continue
1333
+ # unwrap once more in case function
1334
+ # was wrapped before it became property
1335
+ f = inspect .unwrap (f )
1336
+ if _update_func_cell_for__class__ (f , cls , newcls ):
1337
+ break
1338
+ elif hasattr (member , "__get__" ) and not inspect .ismemberdescriptor (
1339
+ member
1340
+ ):
1341
+ # we don't want to inspect custom descriptors just yet
1342
+ # there's still a chance we'll encounter a pure function
1343
+ # or a property
1344
+ custom_descriptors_to_check .append (member )
1345
+ else :
1346
+ # now let's ensure custom descriptors won't be left out
1347
+ for descriptor in custom_descriptors_to_check :
1348
+ for f in _find_inner_functions (descriptor ):
1349
+ if _update_func_cell_for__class__ (f , cls , newcls ):
1350
+ break
1313
1351
1314
1352
return newcls
1315
1353
0 commit comments