@@ -1332,14 +1332,47 @@ impl ExecutingFrame<'_> {
13321332 let mut values = Vec :: new ( ) ;
13331333 let mut all_match = true ;
13341334
1335- for key in keys {
1336- match subject. get_item ( key. as_object ( ) , vm) {
1337- Ok ( value) => values. push ( value) ,
1338- Err ( e) if e. fast_isinstance ( vm. ctx . exceptions . key_error ) => {
1339- all_match = false ;
1340- break ;
1335+ // We use the two argument form of map.get(key, default) for two reasons:
1336+ // - Atomically check for a key and get its value without error handling.
1337+ // - Don't cause key creation or resizing in dict subclasses like
1338+ // collections.defaultdict that define __missing__ (or similar).
1339+ // See CPython's _PyEval_MatchKeys
1340+
1341+ if let Some ( get_method) = vm
1342+ . get_method ( subject. to_owned ( ) , vm. ctx . intern_str ( "get" ) )
1343+ . transpose ( ) ?
1344+ {
1345+ // dummy = object()
1346+ // CPython: dummy = _PyObject_CallNoArgs((PyObject *)&PyBaseObject_Type);
1347+ let dummy = vm
1348+ . ctx
1349+ . new_base_object ( vm. ctx . types . object_type . to_owned ( ) , None ) ;
1350+
1351+ for key in keys {
1352+ // value = map.get(key, dummy)
1353+ match get_method. call ( ( key. as_object ( ) , dummy. clone ( ) ) , vm) {
1354+ Ok ( value) => {
1355+ // if value == dummy: key not in map!
1356+ if value. is ( & dummy) {
1357+ all_match = false ;
1358+ break ;
1359+ }
1360+ values. push ( value) ;
1361+ }
1362+ Err ( e) => return Err ( e) ,
1363+ }
1364+ }
1365+ } else {
1366+ // Fallback if .get() method is not available (shouldn't happen for mappings)
1367+ for key in keys {
1368+ match subject. get_item ( key. as_object ( ) , vm) {
1369+ Ok ( value) => values. push ( value) ,
1370+ Err ( e) if e. fast_isinstance ( vm. ctx . exceptions . key_error ) => {
1371+ all_match = false ;
1372+ break ;
1373+ }
1374+ Err ( e) => return Err ( e) ,
13411375 }
1342- Err ( e) => return Err ( e) ,
13431376 }
13441377 }
13451378
0 commit comments