Skip to content

Commit 45d5d1e

Browse files
committed
performance tuning
1 parent 74d0c24 commit 45d5d1e

3 files changed

Lines changed: 51 additions & 27 deletions

File tree

python/code/typeguard/_checkers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,10 @@ def check_type_internal(
914914
:param memo: a memo object containing configuration and information necessary for
915915
looking up forward references
916916
"""
917+
ty = type(value)
918+
if ty == annotation or (value is None and ty is type(None)):
919+
# some early exits for better performance
920+
return
917921
annotation = resolve_alias_chains(annotation)
918922

919923
if isinstance(annotation, ForwardRef):

python/code/wypp/location.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ def __init__(self, f: Callable, kind: CallableKind):
217217
self.__lineno = f.__code__.co_firstlineno
218218
self.__name = f.__name__
219219
self.__ast = parsecache.getAST(self.file)
220+
self.__async = None
220221

221222
def __repr__(self):
222223
return f'StdCallableInfo({self.name}, {self.kind})'
@@ -282,8 +283,10 @@ def getParamSourceLocation(self, paramName: str) -> Optional[Loc]:
282283

283284
@property
284285
def isAsync(self) -> bool:
285-
node = self._findDef()
286-
return isinstance(node, ast.AsyncFunctionDef)
286+
if self.__async is None:
287+
node = self._findDef()
288+
self.__async = isinstance(node, ast.AsyncFunctionDef)
289+
return self.__async
287290

288291
def classFilename(cls) -> str | None:
289292
"""Best-effort path to the file that defined `cls`."""

python/code/wypp/typecheck.py

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ def isEmptySignature(sig: inspect.Signature) -> bool:
2424
return False
2525
return isEmptyAnnotation(sig.return_annotation)
2626

27-
def handleMatchesTyResult(res: MatchesTyResult, tyLoc: Optional[location.Loc]) -> bool:
27+
def handleMatchesTyResult(res: MatchesTyResult, getTyLoc: Callable[[], Optional[location.Loc]]) -> bool:
2828
match res:
2929
case MatchesTyFailure(exc, ty):
3030
if isDebug():
3131
debug(f'Exception occurred while calling matchesTy with type {ty}, re-raising')
3232
raise exc
3333
else:
34-
raise errors.WyppTypeError.invalidType(ty, tyLoc)
34+
raise errors.WyppTypeError.invalidType(ty, getTyLoc())
3535
case b:
3636
return b
3737

@@ -63,11 +63,17 @@ def checkSignature(sig: inspect.Signature, info: location.CallableInfo, cfg: Che
6363
locDecl = info.getParamSourceLocation(name)
6464
raise errors.WyppTypeError.partialAnnotationError(location.CallableName.mk(info), name, locDecl)
6565
if p.default is not inspect.Parameter.empty:
66-
locDecl = info.getParamSourceLocation(name)
66+
locDecl = lambda: info.getParamSourceLocation(name)
6767
if not handleMatchesTyResult(matchesTy(p.default, ty, cfg.ns), locDecl):
68-
raise errors.WyppTypeError.defaultError(location.CallableName.mk(info), name, locDecl, ty, p.default)
68+
raise errors.WyppTypeError.defaultError(location.CallableName.mk(info), name,
69+
locDecl(), ty, p.default)
70+
71+
_argCountCache = {}
6972

7073
def mandatoryArgCount(sig: inspect.Signature) -> int:
74+
x = _argCountCache.get(sig)
75+
if x is not None:
76+
return x
7177
required_kinds = {
7278
inspect.Parameter.POSITIONAL_ONLY,
7379
inspect.Parameter.POSITIONAL_OR_KEYWORD,
@@ -77,10 +83,12 @@ def mandatoryArgCount(sig: inspect.Signature) -> int:
7783
for p in sig.parameters.values():
7884
if p.kind in required_kinds and p.default is inspect._empty:
7985
res = res + 1
86+
_argCountCache[sig] = res
8087
return res
8188

8289
def checkArgument(p: inspect.Parameter, name: str, idx: Optional[int], a: Any,
83-
locArg: Optional[location.Loc], info: location.CallableInfo, cfg: CheckCfg):
90+
getLocArg: Callable[[], Optional[location.Loc]],
91+
info: location.CallableInfo, cfg: CheckCfg):
8492
t = p.annotation
8593
if not isEmptyAnnotation(t):
8694
if p.kind == inspect.Parameter.VAR_POSITIONAL:
@@ -110,29 +118,38 @@ def checkArgument(p: inspect.Parameter, name: str, idx: Optional[int], a: Any,
110118
else:
111119
raise ValueError(f'Invalid type for keyword argument: {t}')
112120
t = valT
113-
locDecl = info.getParamSourceLocation(name)
121+
locDecl = lambda: info.getParamSourceLocation(name)
114122
if not handleMatchesTyResult(matchesTy(a, t, cfg.ns), locDecl):
115123
cn = location.CallableName.mk(info)
116124
raise errors.WyppTypeError.argumentError(cn,
117125
name,
118126
idx,
119-
locDecl,
127+
locDecl(),
120128
t,
121129
a,
122-
locArg)
130+
getLocArg())
131+
123132
def checkArguments(sig: inspect.Signature, args: tuple, kwargs: dict,
124133
info: location.CallableInfo, cfg: CheckCfg) -> None:
125-
debug(f'Checking arguments when calling {info}')
134+
if isDebug():
135+
debug(f'Checking arguments when calling {info}')
126136
paramNames = list(sig.parameters)
127137
mandatory = mandatoryArgCount(sig)
128138
kind = getKind(cfg, paramNames)
129139
offset = 1 if kind == 'method' else 0
130-
fi = stacktrace.callerOutsideWypp()
131-
callLoc = None if not fi else location.Loc.fromFrameInfo(fi)
132140
cn = location.CallableName.mk(info)
141+
# stacktrace.callerOutsideWypp() is expensive, only access it lazily
142+
def getCallLoc() -> Optional[location.Loc]:
143+
fi = stacktrace.callerOutsideWypp()
144+
return None if not fi else location.Loc.fromFrameInfo(fi)
145+
def getLocArg(idxOrName: int | str) -> Callable[[], Optional[location.Loc]]:
146+
def f():
147+
fi = stacktrace.callerOutsideWypp()
148+
return None if fi is None else location.locationOfArgument(fi, i)
149+
return f
133150
def raiseArgMismatch():
134151
raise errors.WyppTypeError.argCountMismatch(cn,
135-
callLoc,
152+
getCallLoc(),
136153
len(paramNames) - offset,
137154
mandatory - offset,
138155
len(args) - offset)
@@ -152,46 +169,46 @@ def raiseArgMismatch():
152169
raiseArgMismatch()
153170
# Check positional args
154171
for i in range(len(args)):
155-
locArg = None if fi is None else location.locationOfArgument(fi, i)
156172
if i < len(positionalNames):
157173
name = positionalNames[i]
158174
p = sig.parameters[name]
159-
checkArgument(p, name, i - offset, args[i], locArg, info, cfg)
175+
checkArgument(p, name, i - offset, args[i], getLocArg(i), info, cfg)
160176
elif varPositionalParam is not None:
161-
checkArgument(varPositionalParam, varPositionalParam.name, i - offset, args[i], locArg, info, cfg)
177+
checkArgument(varPositionalParam, varPositionalParam.name, i - offset, args[i], getLocArg(i), info, cfg)
162178
else:
163179
raiseArgMismatch()
164180
# Check keyword args
165181
for name in kwargs:
166-
locArg = None if fi is None else location.locationOfArgument(fi, name)
167182
if name in sig.parameters and sig.parameters[name].kind not in (
168183
inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD
169184
):
170-
checkArgument(sig.parameters[name], name, None, kwargs[name], locArg, info, cfg)
185+
checkArgument(sig.parameters[name], name, None, kwargs[name], getLocArg(name), info, cfg)
171186
elif varKeywordParam is not None:
172-
checkArgument(varKeywordParam, name, None, kwargs[name], locArg, info, cfg)
187+
checkArgument(varKeywordParam, name, None, kwargs[name], getLocArg(name), info, cfg)
173188
else:
174-
raise errors.WyppTypeError.unknownKeywordArgument(cn, callLoc, name)
189+
raise errors.WyppTypeError.unknownKeywordArgument(cn, getCallLoc(), name)
175190

176-
def checkReturn(sig: inspect.Signature, returnFrame: Optional[inspect.FrameInfo],
191+
def checkReturn(sig: inspect.Signature, getReturnFrame: Callable[[], Optional[inspect.FrameInfo]],
177192
result: Any, info: location.CallableInfo, cfg: CheckCfg) -> None:
178193
if info.isAsync:
179194
return
180195
t = sig.return_annotation
181196
if isEmptyAnnotation(t):
182197
t = None
183-
debug(f'Checking return value when calling {info}, return type: {t}')
184-
locDecl = info.getResultTypeLocation()
198+
if isDebug():
199+
debug(f'Checking return value when calling {info}, return type: {t}')
200+
locDecl = lambda: info.getResultTypeLocation()
185201
if not handleMatchesTyResult(matchesTy(result, t, cfg.ns), locDecl):
186202
fi = stacktrace.callerOutsideWypp()
187203
if fi is not None:
188204
locRes = location.Loc.fromFrameInfo(fi)
189205
returnLoc = None
190206
extraFrames = []
207+
returnFrame = getReturnFrame()
191208
if returnFrame:
192209
returnLoc = location.Loc.fromFrameInfo(returnFrame)
193210
extraFrames = [returnFrame]
194-
raise errors.WyppTypeError.resultError(location.CallableName.mk(info), locDecl, t, returnLoc, result,
211+
raise errors.WyppTypeError.resultError(location.CallableName.mk(info), locDecl(), t, returnLoc, result,
195212
locRes, extraFrames)
196213

197214

@@ -232,9 +249,9 @@ def wrapped(*args, **kwargs) -> T:
232249
utils._call_with_frames_removed(checkArguments, sig, args, kwargs, info, checkCfg)
233250
returnTracker = stacktrace.getReturnTracker()
234251
result = utils._call_with_next_frame_removed(f, *args, **kwargs)
235-
retFrame = returnTracker.getReturnFrame(0)
252+
getRetFrame = lambda: returnTracker.getReturnFrame(0)
236253
utils._call_with_frames_removed(
237-
checkReturn, sig, retFrame, result, info, checkCfg
254+
checkReturn, sig, getRetFrame, result, info, checkCfg
238255
)
239256
return result
240257
return wrapped

0 commit comments

Comments
 (0)