@@ -1222,11 +1222,6 @@ def _get_slots(cls):
1222
1222
1223
1223
1224
1224
def _update_func_cell_for__class__ (f , oldcls , newcls ):
1225
- # Returns True if we update a cell, else False.
1226
- if f is None :
1227
- # f will be None in the case of a property where not all of
1228
- # fget, fset, and fdel are used. Nothing to do in that case.
1229
- return False
1230
1225
try :
1231
1226
idx = f .__code__ .co_freevars .index ("__class__" )
1232
1227
except ValueError :
@@ -1235,13 +1230,36 @@ def _update_func_cell_for__class__(f, oldcls, newcls):
1235
1230
# Fix the cell to point to the new class, if it's already pointing
1236
1231
# at the old class. I'm not convinced that the "is oldcls" test
1237
1232
# is needed, but other than performance can't hurt.
1238
- closure = f .__closure__ [idx ]
1239
- if closure .cell_contents is oldcls :
1240
- closure .cell_contents = newcls
1233
+ cell = f .__closure__ [idx ]
1234
+ if cell .cell_contents is oldcls :
1235
+ cell .cell_contents = newcls
1241
1236
return True
1242
1237
return False
1243
1238
1244
1239
1240
+ def _find_inner_functions (obj , _seen = None , _depth = 0 ):
1241
+ if _seen is None :
1242
+ _seen = set ()
1243
+ if id (obj ) in _seen :
1244
+ return None
1245
+ _seen .add (id (obj ))
1246
+
1247
+ _depth += 1
1248
+ if _depth > 2 :
1249
+ return None
1250
+
1251
+ obj = inspect .unwrap (obj )
1252
+
1253
+ for attr in dir (obj ):
1254
+ value = getattr (obj , attr , None )
1255
+ if value is None :
1256
+ continue
1257
+ if isinstance (obj , types .FunctionType ):
1258
+ yield obj
1259
+ return
1260
+ yield from _find_inner_functions (value , _seen , _depth )
1261
+
1262
+
1245
1263
def _create_slots (defined_fields , inherited_slots , field_names , weakref_slot ):
1246
1264
# The slots for our class. Remove slots from our base classes. Add
1247
1265
# '__weakref__' if weakref_slot was given, unless it is already present.
@@ -1317,7 +1335,10 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
1317
1335
# (the newly created one, which we're returning) and not the
1318
1336
# original class. We can break out of this loop as soon as we
1319
1337
# make an update, since all closures for a class will share a
1320
- # given cell.
1338
+ # given cell. First we try to find a pure function/properties,
1339
+ # and then fallback to inspecting custom descriptors.
1340
+
1341
+ custom_descriptors_to_check = []
1321
1342
for member in newcls .__dict__ .values ():
1322
1343
# If this is a wrapped function, unwrap it.
1323
1344
member = inspect .unwrap (member )
@@ -1326,10 +1347,27 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields):
1326
1347
if _update_func_cell_for__class__ (member , cls , newcls ):
1327
1348
break
1328
1349
elif isinstance (member , property ):
1329
- if (_update_func_cell_for__class__ (member .fget , cls , newcls )
1330
- or _update_func_cell_for__class__ (member .fset , cls , newcls )
1331
- or _update_func_cell_for__class__ (member .fdel , cls , newcls )):
1332
- break
1350
+ for f in member .fget , member .fset , member .fdel :
1351
+ if f is None :
1352
+ continue
1353
+ # unwrap once more in case function
1354
+ # was wrapped before it became property
1355
+ f = inspect .unwrap (f )
1356
+ if _update_func_cell_for__class__ (f , cls , newcls ):
1357
+ break
1358
+ elif hasattr (member , "__get__" ) and not inspect .ismemberdescriptor (
1359
+ member
1360
+ ):
1361
+ # we don't want to inspect custom descriptors just yet
1362
+ # there's still a chance we'll encounter a pure function
1363
+ # or a property
1364
+ custom_descriptors_to_check .append (member )
1365
+ else :
1366
+ # now let's ensure custom descriptors won't be left out
1367
+ for descriptor in custom_descriptors_to_check :
1368
+ for f in _find_inner_functions (descriptor ):
1369
+ if _update_func_cell_for__class__ (f , cls , newcls ):
1370
+ break
1333
1371
1334
1372
return newcls
1335
1373
0 commit comments