Skip to content

Commit 694f767

Browse files
authored
Avoid setattr/getattr with fixed format cache (#20826)
This is a small optimization, and something that bothered me for a while. I write bit packing in Python because we have mypyc and we should use it (it should write relatively efficient code for this). Implementation is really basic, I only allow at most 26 flags, which will hopefully be enough for few years. Btw `Var` has _21 flags_, I am quite sure some of those can be cleaned up.
1 parent c2b60a8 commit 694f767

File tree

1 file changed

+125
-16
lines changed

1 file changed

+125
-16
lines changed

mypy/nodes.py

Lines changed: 125 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ def write(self, data: WriteBuffer) -> None:
759759
write_tag(data, LITERAL_NONE)
760760
else:
761761
self.impl.write(data)
762-
write_flags(data, self, FUNCBASE_FLAGS)
762+
write_flags(data, [self.is_property, self.is_class, self.is_static, self.is_final])
763763
write_str_opt(data, self.deprecated)
764764
write_int_opt(data, self.setter_index)
765765
write_tag(data, END_TAG)
@@ -779,7 +779,7 @@ def read(cls, data: ReadBuffer) -> OverloadedFuncDef:
779779
# set line for empty overload items, as not set in __init__
780780
if len(res.items) > 0:
781781
res.set_line(res.impl.line)
782-
read_flags(data, res, FUNCBASE_FLAGS)
782+
res.is_property, res.is_class, res.is_static, res.is_final = read_flags(data, num_flags=4)
783783
res.deprecated = read_str_opt(data)
784784
res.setter_index = read_int_opt(data)
785785
# NOTE: res.info will be set in the fixup phase.
@@ -1067,7 +1067,25 @@ def write(self, data: WriteBuffer) -> None:
10671067
write_str(data, self._name)
10681068
mypy.types.write_type_opt(data, self.type)
10691069
write_str(data, self._fullname)
1070-
write_flags(data, self, FUNCDEF_FLAGS)
1070+
write_flags(
1071+
data,
1072+
[
1073+
self.is_property,
1074+
self.is_class,
1075+
self.is_static,
1076+
self.is_final,
1077+
self.is_overload,
1078+
self.is_generator,
1079+
self.is_coroutine,
1080+
self.is_async_generator,
1081+
self.is_awaitable_coroutine,
1082+
self.is_decorated,
1083+
self.is_conditional,
1084+
self.is_trivial_body,
1085+
self.is_trivial_self,
1086+
self.is_mypy_only,
1087+
],
1088+
)
10711089
write_str_opt_list(data, self.arg_names)
10721090
write_int_list(data, [int(ak.value) for ak in self.arg_kinds])
10731091
write_int(data, self.abstract_status)
@@ -1088,7 +1106,22 @@ def read(cls, data: ReadBuffer) -> FuncDef:
10881106
typ = mypy.types.read_function_like(data, tag)
10891107
ret = FuncDef(name, [], Block([]), typ)
10901108
ret._fullname = read_str(data)
1091-
read_flags(data, ret, FUNCDEF_FLAGS)
1109+
(
1110+
ret.is_property,
1111+
ret.is_class,
1112+
ret.is_static,
1113+
ret.is_final,
1114+
ret.is_overload,
1115+
ret.is_generator,
1116+
ret.is_coroutine,
1117+
ret.is_async_generator,
1118+
ret.is_awaitable_coroutine,
1119+
ret.is_decorated,
1120+
ret.is_conditional,
1121+
ret.is_trivial_body,
1122+
ret.is_trivial_self,
1123+
ret.is_mypy_only,
1124+
) = read_flags(data, num_flags=14)
10921125
# NOTE: ret.info is set in the fixup phase.
10931126
ret.arg_names = read_str_opt_list(data)
10941127
ret.arg_kinds = [ARG_KINDS[ak] for ak in read_int_list(data)]
@@ -1378,7 +1411,32 @@ def write(self, data: WriteBuffer) -> None:
13781411
mypy.types.write_type_opt(data, self.type)
13791412
mypy.types.write_type_opt(data, self.setter_type)
13801413
write_str(data, self._fullname)
1381-
write_flags(data, self, VAR_FLAGS)
1414+
write_flags(
1415+
data,
1416+
[
1417+
self.is_self,
1418+
self.is_cls,
1419+
self.is_initialized_in_class,
1420+
self.is_staticmethod,
1421+
self.is_classmethod,
1422+
self.is_property,
1423+
self.is_settable_property,
1424+
self.is_suppressed_import,
1425+
self.is_classvar,
1426+
self.is_abstract_var,
1427+
self.is_final,
1428+
self.is_index_var,
1429+
self.final_unset_in_class,
1430+
self.final_set_in_init,
1431+
self.explicit_self_type,
1432+
self.is_ready,
1433+
self.is_inferred,
1434+
self.invalid_partial_type,
1435+
self.from_module_getattr,
1436+
self.has_explicit_value,
1437+
self.allow_incompatible_override,
1438+
],
1439+
)
13821440
write_literal(data, self.final_value)
13831441
write_tag(data, END_TAG)
13841442

@@ -1393,9 +1451,30 @@ def read(cls, data: ReadBuffer) -> Var:
13931451
assert tag == mypy.types.CALLABLE_TYPE
13941452
setter_type = mypy.types.CallableType.read(data)
13951453
v.setter_type = setter_type
1396-
v.is_ready = False # Override True default set in __init__
13971454
v._fullname = read_str(data)
1398-
read_flags(data, v, VAR_FLAGS)
1455+
(
1456+
v.is_self,
1457+
v.is_cls,
1458+
v.is_initialized_in_class,
1459+
v.is_staticmethod,
1460+
v.is_classmethod,
1461+
v.is_property,
1462+
v.is_settable_property,
1463+
v.is_suppressed_import,
1464+
v.is_classvar,
1465+
v.is_abstract_var,
1466+
v.is_final,
1467+
v.is_index_var,
1468+
v.final_unset_in_class,
1469+
v.final_set_in_init,
1470+
v.explicit_self_type,
1471+
v.is_ready,
1472+
v.is_inferred,
1473+
v.invalid_partial_type,
1474+
v.from_module_getattr,
1475+
v.has_explicit_value,
1476+
v.allow_incompatible_override,
1477+
) = read_flags(data, num_flags=21)
13991478
tag = read_tag(data)
14001479
if tag == LITERAL_COMPLEX:
14011480
v.final_value = complex(read_float_bare(data), read_float_bare(data))
@@ -4035,7 +4114,22 @@ def write(self, data: WriteBuffer) -> None:
40354114
mypy.types.write_type_opt(data, self.metaclass_type)
40364115
mypy.types.write_type_opt(data, self.tuple_type)
40374116
mypy.types.write_type_opt(data, self.typeddict_type)
4038-
write_flags(data, self, TypeInfo.FLAGS)
4117+
write_flags(
4118+
data,
4119+
[
4120+
self.is_abstract,
4121+
self.is_enum,
4122+
self.fallback_to_any,
4123+
self.meta_fallback_to_any,
4124+
self.is_named_tuple,
4125+
self.is_newtype,
4126+
self.is_protocol,
4127+
self.runtime_protocol,
4128+
self.is_final,
4129+
self.is_disjoint_base,
4130+
self.is_intersection,
4131+
],
4132+
)
40394133
write_json(data, self.metadata)
40404134
if self.slots is None:
40414135
write_tag(data, LITERAL_NONE)
@@ -4095,7 +4189,19 @@ def read(cls, data: ReadBuffer) -> TypeInfo:
40954189
if (tag := read_tag(data)) != LITERAL_NONE:
40964190
assert tag == mypy.types.TYPED_DICT_TYPE
40974191
ti.typeddict_type = mypy.types.TypedDictType.read(data)
4098-
read_flags(data, ti, TypeInfo.FLAGS)
4192+
(
4193+
ti.is_abstract,
4194+
ti.is_enum,
4195+
ti.fallback_to_any,
4196+
ti.meta_fallback_to_any,
4197+
ti.is_named_tuple,
4198+
ti.is_newtype,
4199+
ti.is_protocol,
4200+
ti.runtime_protocol,
4201+
ti.is_final,
4202+
ti.is_disjoint_base,
4203+
ti.is_intersection,
4204+
) = read_flags(data, num_flags=11)
40994205
ti.metadata = read_json(data)
41004206
tag = read_tag(data)
41014207
if tag != LITERAL_NONE:
@@ -4882,15 +4988,18 @@ def set_flags(node: Node, flags: list[str]) -> None:
48824988
setattr(node, name, True)
48834989

48844990

4885-
def write_flags(data: WriteBuffer, node: SymbolNode, flags: list[str]) -> None:
4886-
for flag in flags:
4887-
write_bool(data, getattr(node, flag))
4991+
def write_flags(data: WriteBuffer, flags: list[bool]) -> None:
4992+
assert len(flags) <= 26, "This many flags not supported yet"
4993+
packed = 0
4994+
for i, flag in enumerate(flags):
4995+
if flag:
4996+
packed |= 1 << i
4997+
write_int(data, packed)
48884998

48894999

4890-
def read_flags(data: ReadBuffer, node: SymbolNode, flags: list[str]) -> None:
4891-
for flag in flags:
4892-
if read_bool(data):
4893-
setattr(node, flag, True)
5000+
def read_flags(data: ReadBuffer, num_flags: int) -> list[bool]:
5001+
packed = read_int(data)
5002+
return [(packed & (1 << i)) != 0 for i in range(num_flags)]
48945003

48955004

48965005
def get_member_expr_fullname(expr: MemberExpr) -> str | None:

0 commit comments

Comments
 (0)