Skip to content

Commit 01a1bf6

Browse files
authored
Speed up the implementation of hasattr() checks (#14333)
This makes the implementation of hasattr() checks faster (introduced in #13544). In particular, since the `extra_attrs` attribute used for hasattr() checks is usually None, I micro-optimized the codepaths to avoid expensive operations whenever there are no hasattr() checks. Also avoid expensive operations on simple unions and order `isinstance` checks so that common types are checked first. I measured a 2% performance uplift in self-check.
1 parent c246a52 commit 01a1bf6

File tree

2 files changed

+29
-15
lines changed

2 files changed

+29
-15
lines changed

mypy/meet.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def meet_types(s: Type, t: Type) -> ProperType:
7676
# Code in checker.py should merge any extra_items where possible, so we
7777
# should have only compatible extra_items here. We check this before
7878
# the below subtype check, so that extra_attrs will not get erased.
79-
if is_same_type(s, t) and (s.extra_attrs or t.extra_attrs):
79+
if (s.extra_attrs or t.extra_attrs) and is_same_type(s, t):
8080
if s.extra_attrs and t.extra_attrs:
8181
if len(s.extra_attrs.attrs) > len(t.extra_attrs.attrs):
8282
# Return the one that has more precise information.

mypy/typeops.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
ENUM_REMOVED_PROPS,
3434
AnyType,
3535
CallableType,
36+
ExtraAttrs,
3637
FormalArgument,
3738
FunctionLike,
3839
Instance,
@@ -466,16 +467,27 @@ def make_simplified_union(
466467

467468
result = get_proper_type(UnionType.make_union(simplified_set, line, column))
468469

469-
# Step 4: At last, we erase any (inconsistent) extra attributes on instances.
470-
extra_attrs_set = set()
471-
for item in items:
472-
instance = try_getting_instance_fallback(item)
473-
if instance and instance.extra_attrs:
474-
extra_attrs_set.add(instance.extra_attrs)
475-
476-
fallback = try_getting_instance_fallback(result)
477-
if len(extra_attrs_set) > 1 and fallback:
478-
fallback.extra_attrs = None
470+
nitems = len(items)
471+
if nitems > 1 and (
472+
nitems > 2 or not (type(items[0]) is NoneType or type(items[1]) is NoneType)
473+
):
474+
# Step 4: At last, we erase any (inconsistent) extra attributes on instances.
475+
476+
# Initialize with None instead of an empty set as a micro-optimization. The set
477+
# is needed very rarely, so we try to avoid constructing it.
478+
extra_attrs_set: set[ExtraAttrs] | None = None
479+
for item in items:
480+
instance = try_getting_instance_fallback(item)
481+
if instance and instance.extra_attrs:
482+
if extra_attrs_set is None:
483+
extra_attrs_set = {instance.extra_attrs}
484+
else:
485+
extra_attrs_set.add(instance.extra_attrs)
486+
487+
if extra_attrs_set is not None and len(extra_attrs_set) > 1:
488+
fallback = try_getting_instance_fallback(result)
489+
if fallback:
490+
fallback.extra_attrs = None
479491

480492
return result
481493

@@ -1006,13 +1018,15 @@ def try_getting_instance_fallback(typ: Type) -> Instance | None:
10061018
typ = get_proper_type(typ)
10071019
if isinstance(typ, Instance):
10081020
return typ
1009-
elif isinstance(typ, TupleType):
1010-
return typ.partial_fallback
1011-
elif isinstance(typ, TypedDictType):
1021+
elif isinstance(typ, LiteralType):
10121022
return typ.fallback
1023+
elif isinstance(typ, NoneType):
1024+
return None # Fast path for None, which is common
10131025
elif isinstance(typ, FunctionLike):
10141026
return typ.fallback
1015-
elif isinstance(typ, LiteralType):
1027+
elif isinstance(typ, TupleType):
1028+
return typ.partial_fallback
1029+
elif isinstance(typ, TypedDictType):
10161030
return typ.fallback
10171031
elif isinstance(typ, TypeVarType):
10181032
return try_getting_instance_fallback(typ.upper_bound)

0 commit comments

Comments
 (0)