@@ -246,7 +246,12 @@ def _is_generator_func(func_impl):
246
246
return isgeneratorfunction (func_impl )
247
247
248
248
249
- class DefaultHolder :
249
+ class _SymbolRef :
250
+ """
251
+ A class used to protect signature default values and type hints when the local context would not be able
252
+ to evaluate them properly when the new function is created. In this case we store them under a known name,
253
+ we add that name to the locals(), and we use this symbol that has a repr() equal to the name.
254
+ """
250
255
__slots__ = 'varname'
251
256
252
257
def __init__ (self , varname ):
@@ -267,22 +272,16 @@ def get_signature_string(func_name, func_signature, evaldict):
267
272
# protect the parameters if needed
268
273
new_params = []
269
274
for p_name , p in func_signature .parameters .items ():
270
- if p .default is not Parameter .empty and not isinstance (p .default , (int , str , float , bool )):
271
- # check if the repr() of the default value is equal to itself.
272
- needs_protection = True
273
- try :
274
- deflt = eval (repr (p .default ))
275
- needs_protection = deflt != p .default
276
- except SyntaxError :
277
- pass
278
-
279
- # if we have any problem, we need to protect the default value
280
- if needs_protection :
281
- # store the object in the evaldict and insert name
282
- varname = "DEFAULT_%s" % p_name
283
- evaldict [varname ] = p .default
284
- p = Parameter (p .name , kind = p .kind , default = DefaultHolder (varname ), annotation = p .annotation )
275
+ # if default value can not be evaluated, protect it
276
+ default_needs_protection = _signature_symbol_needs_protection (p .default , evaldict )
277
+ new_default = _protect_signature_symbol (p .default , default_needs_protection , "DEFAULT_%s" % p_name , evaldict )
285
278
279
+ # if type hint can not be evaluated, protect it
280
+ annotation_needs_protection = _signature_symbol_needs_protection (p .annotation , evaldict )
281
+ new_annotation = _protect_signature_symbol (p .annotation , annotation_needs_protection , "HINT_%s" % p_name , evaldict )
282
+
283
+ # replace the parameter with the possibly new default and hint
284
+ p = Parameter (p .name , kind = p .kind , default = new_default , annotation = new_annotation )
286
285
new_params .append (p )
287
286
288
287
# copy signature object
@@ -292,6 +291,48 @@ def get_signature_string(func_name, func_signature, evaldict):
292
291
return "%s%s:" % (func_name , s )
293
292
294
293
294
+ def _signature_symbol_needs_protection (symbol , evaldict ):
295
+ """
296
+ Helper method for signature symbols (defaults, type hints) protection.
297
+
298
+ Returns True if the given symbol needs to be protected - that is, if its repr() can not be correctly evaluated with current evaldict.
299
+ :param symbol:
300
+ :return:
301
+ """
302
+ if symbol is not None and symbol is not Parameter .empty and not isinstance (symbol , (int , str , float , bool )):
303
+ # check if the repr() of the default value is equal to itself.
304
+ try :
305
+ deflt = eval (repr (symbol ), evaldict )
306
+ needs_protection = deflt != symbol
307
+ except SyntaxError :
308
+ needs_protection = True
309
+ else :
310
+ needs_protection = False
311
+
312
+ return needs_protection
313
+
314
+
315
+ def _protect_signature_symbol (val , needs_protection , varname , evaldict ):
316
+ """
317
+ Helper method for signature symbols (defaults, type hints) protection.
318
+
319
+ Returns either `val`, or a protection symbol. In that case the protection symbol
320
+ is created with name `varname` and inserted into `evaldict`
321
+
322
+ :param val:
323
+ :param needs_protection:
324
+ :param varname:
325
+ :param evaldict:
326
+ :return:
327
+ """
328
+ if needs_protection :
329
+ # store the object in the evaldict and insert name
330
+ evaldict [varname ] = val
331
+ return _SymbolRef (varname )
332
+ else :
333
+ return val
334
+
335
+
295
336
def get_signature_from_string (func_sig_str , evaldict ):
296
337
"""
297
338
Creates a `Signature` object from the given function signature string.
0 commit comments