Skip to content

Commit d63737e

Browse files
author
Tavian Barnes
authored
Merge pull request #7 from microsoft/mypy
mypy
2 parents b4a9163 + 78ba206 commit d63737e

File tree

10 files changed

+233
-113
lines changed

10 files changed

+233
-113
lines changed

azure-pipelines.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ steps:
2525
CFLAGS="-std=c++11" pip install pyicu
2626
pip install -e './python[test]'
2727
pip install -r ./docs/requirements.txt
28+
pip install pytest-azurepipelines
2829
displayName: 'Install dependencies'
2930

3031
- script: |
31-
pip install pytest-azurepipelines
32+
(cd ./python && mypy -p bistring)
3233
pytest
3334
make -C docs doctest
3435
displayName: 'pytest'

python/bistring/_alignment.py

Lines changed: 53 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
__all__ = ['Alignment']
77

88
import bisect
9-
from numbers import Real
10-
from typing import Callable, Iterable, List, Optional, Sequence, Tuple, TypeVar, cast, overload
9+
from typing import Any, Callable, Iterable, Iterator, List, Optional, Sequence, Tuple, TypeVar, Union, cast, overload
1110

12-
from ._typing import Bounds, Range
11+
from ._typing import AnyBounds, Bounds, Index, Range
1312

1413

1514
T = TypeVar('T')
1615
U = TypeVar('U')
16+
Real = Union[int, float]
1717
CostFn = Callable[[Optional[T], Optional[U]], Real]
1818

1919

@@ -108,15 +108,15 @@ def _create(cls, original: List[int], modified: List[int]) -> Alignment:
108108
result._modified = modified
109109
return result
110110

111-
def __str__(self):
111+
def __str__(self) -> str:
112112
i, j = self._original[0], self._original[-1]
113113
k, l = self._modified[0], self._modified[-1]
114114
if self._original == list(range(i, j + 1)) and self._modified == list(range(k, l + 1)):
115115
return f'[{i}:{j}{k}:{l}]'
116116
else:
117117
return '[' + ', '.join(f'{i}{j}' for i, j in self) + ']'
118118

119-
def __repr__(self):
119+
def __repr__(self) -> str:
120120
i, j = self._original[0], self._original[-1]
121121
if self._original == list(range(i, j + 1)) and self._modified == list(range(i, j + 1)):
122122
if i == 0:
@@ -126,17 +126,17 @@ def __repr__(self):
126126
else:
127127
return 'Alignment([' + ', '.join(map(repr, self)) + '])'
128128

129-
def __eq__(self, other):
129+
def __eq__(self, other: Any) -> bool:
130130
if isinstance(other, Alignment):
131131
return (self._original, self._modified) == (other._original, other._modified)
132132
else:
133133
return NotImplemented
134134

135135
@classmethod
136-
def _parse_args(cls, args: Tuple) -> Bounds:
136+
def _parse_bounds(cls, args: Tuple[AnyBounds, ...]) -> Bounds:
137137
l = len(args)
138138
if l == 0:
139-
return None, None
139+
raise TypeError('Not enough arguments')
140140
elif l == 1:
141141
arg = args[0]
142142
if isinstance(arg, range):
@@ -154,23 +154,27 @@ def _parse_args(cls, args: Tuple) -> Bounds:
154154
else:
155155
raise TypeError('Too many arguments')
156156

157+
@classmethod
158+
def _parse_optional_bounds(cls, args: Tuple[AnyBounds, ...]) -> Union[Bounds, Tuple[None, None]]:
159+
if len(args) == 0:
160+
return None, None
161+
else:
162+
return cls._parse_bounds(args)
163+
157164
@overload
158165
@classmethod
159-
def identity(cls, length: int) -> Alignment:
160-
...
166+
def identity(cls, __length: int) -> Alignment: ...
161167

162168
@overload
163169
@classmethod
164-
def identity(cls, start: int, stop: int) -> Alignment:
165-
...
170+
def identity(cls, __start: int, __stop: int) -> Alignment: ...
166171

167172
@overload
168173
@classmethod
169-
def identity(cls, bounds: Range) -> Alignment:
170-
...
174+
def identity(cls, __bounds: Range) -> Alignment: ...
171175

172176
@classmethod
173-
def identity(cls, *args):
177+
def identity(cls, *args: Union[int, range, slice, Bounds]) -> Alignment:
174178
"""
175179
Create an identity alignment, which maps all intervals to themselves. You can pass the size of the sequence:
176180
@@ -188,7 +192,7 @@ def identity(cls, *args):
188192
Alignment.identity(1, 5)
189193
"""
190194

191-
start, stop = cls._parse_args(args)
195+
start, stop = cls._parse_bounds(args)
192196
values = list(range(start, stop + 1))
193197
return cls._create(values, values)
194198

@@ -203,12 +207,12 @@ def _infer_costs(cls, original: Sequence[T], modified: Sequence[U], cost_fn: Cos
203207
https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm
204208
"""
205209

206-
row = [0]
210+
row: List[Real] = [0]
207211
for i, m in enumerate(modified):
208212
cost = row[i] + cost_fn(None, m)
209213
row.append(cost)
210214

211-
prev = [0] * len(row)
215+
prev: List[Real] = [0] * len(row)
212216

213217
for o in original:
214218
prev, row = row, prev
@@ -228,7 +232,7 @@ def _infer_matrix(cls, original: Sequence[T], modified: Sequence[U], cost_fn: Co
228232
The Needleman–Wunsch or Wagner–Fischer algorithm, using the entire matrix to compute the optimal alignment.
229233
"""
230234

231-
row = [(0, -1, -1)]
235+
row: List[Tuple[Real, int, int]] = [(0, -1, -1)]
232236
for j, m in enumerate(modified):
233237
cost = row[j][0] + cost_fn(None, m)
234238
row.append((cost, 0, j))
@@ -325,29 +329,31 @@ def infer(cls, original: Sequence[T], modified: Sequence[U], cost_fn: Optional[C
325329
"""
326330

327331
if cost_fn is None:
328-
cost_fn = lambda a, b: int(a != b)
332+
real_cost_fn: CostFn[T, U] = lambda a, b: int(a != b)
333+
else:
334+
real_cost_fn = cost_fn
329335

330336
if len(original) < len(modified):
331-
swap = True
332-
swapped_cost_fn = lambda a, b: cost_fn(b, a)
337+
swapped_cost_fn = lambda a, b: real_cost_fn(b, a)
333338
result = cls._infer_recursive(modified, original, swapped_cost_fn)
339+
return Alignment(result).inverse()
334340
else:
335-
swap = False
336-
result = cls._infer_recursive(original, modified, cost_fn)
341+
result = cls._infer_recursive(original, modified, real_cost_fn)
342+
return Alignment(result)
337343

338-
alignment = Alignment(result)
339-
if swap:
340-
return alignment.inverse()
341-
else:
342-
return alignment
343-
344-
def __iter__(self):
344+
def __iter__(self) -> Iterator[Tuple[int, int]]:
345345
return zip(self._original, self._modified)
346346

347-
def __len__(self):
347+
def __len__(self) -> int:
348348
return len(self._original)
349349

350-
def __getitem__(self, index):
350+
@overload
351+
def __getitem__(self, index: int) -> Bounds: ...
352+
353+
@overload
354+
def __getitem__(self, index: slice) -> Alignment: ...
355+
356+
def __getitem__(self, index: Index) -> Union[Bounds, Alignment]:
351357
"""
352358
Indexing an alignment returns the nth pair of aligned positions:
353359
@@ -398,15 +404,15 @@ def _search(self, source: List[int], start: int, stop: int) -> Bounds:
398404

399405
return first, last
400406

401-
def _bounds(self, source: List[int], target: List[int], args: Tuple) -> Bounds:
402-
start, stop = self._parse_args(args)
403-
if start is None:
407+
def _bounds(self, source: List[int], target: List[int], args: Tuple[AnyBounds, ...]) -> Bounds:
408+
start, stop = self._parse_optional_bounds(args)
409+
if start is None or stop is None:
404410
i, j = 0, -1
405411
else:
406412
i, j = self._search(source, start, stop)
407413
return (target[i], target[j])
408414

409-
def original_bounds(self, *args) -> Bounds:
415+
def original_bounds(self, *args: AnyBounds) -> Bounds:
410416
"""
411417
Maps a subrange of the modified sequence to the original sequence. Can be called with either two arguments:
412418
@@ -430,19 +436,19 @@ def original_bounds(self, *args) -> Bounds:
430436

431437
return self._bounds(self._modified, self._original, args)
432438

433-
def original_range(self, *args) -> range:
439+
def original_range(self, *args: AnyBounds) -> range:
434440
"""
435441
Like :meth:`original_bounds`, but returns a :class:`range`.
436442
"""
437443
return range(*self.original_bounds(*args))
438444

439-
def original_slice(self, *args) -> slice:
445+
def original_slice(self, *args: AnyBounds) -> slice:
440446
"""
441447
Like :meth:`original_bounds`, but returns a :class:`slice`.
442448
"""
443449
return slice(*self.original_bounds(*args))
444450

445-
def modified_bounds(self, *args) -> Bounds:
451+
def modified_bounds(self, *args: AnyBounds) -> Bounds:
446452
"""
447453
Maps a subrange of the original sequence to the modified sequence. Can be called with either two arguments:
448454
@@ -466,19 +472,19 @@ def modified_bounds(self, *args) -> Bounds:
466472

467473
return self._bounds(self._original, self._modified, args)
468474

469-
def modified_range(self, *args) -> range:
475+
def modified_range(self, *args: AnyBounds) -> range:
470476
"""
471477
Like :meth:`modified_bounds`, but returns a :class:`range`.
472478
"""
473479
return range(*self.modified_bounds(*args))
474480

475-
def modified_slice(self, *args) -> slice:
481+
def modified_slice(self, *args: AnyBounds) -> slice:
476482
"""
477483
Like :meth:`modified_bounds`, but returns a :class:`range`.
478484
"""
479485
return slice(*self.modified_bounds(*args))
480486

481-
def slice_by_original(self, *args) -> Alignment:
487+
def slice_by_original(self, *args: AnyBounds) -> Alignment:
482488
"""
483489
Slice this alignment by a span of the original sequence.
484490
@@ -490,14 +496,14 @@ def slice_by_original(self, *args) -> Alignment:
490496
The slice of this alignment that corresponds with the given span of the original sequence.
491497
"""
492498

493-
start, stop = self._parse_args(args)
499+
start, stop = self._parse_bounds(args)
494500
first, last = self._search(self._original, start, stop)
495501
original = self._original[first:last+1]
496502
original = [min(max(i, start), stop) for i in original]
497503
modified = self._modified[first:last+1]
498504
return self._create(original, modified)
499505

500-
def slice_by_modified(self, *args) -> Alignment:
506+
def slice_by_modified(self, *args: AnyBounds) -> Alignment:
501507
"""
502508
Slice this alignment by a span of the modified sequence.
503509
@@ -509,14 +515,14 @@ def slice_by_modified(self, *args) -> Alignment:
509515
The slice of this alignment that corresponds with the given span of the modified sequence.
510516
"""
511517

512-
start, stop = self._parse_args(args)
518+
start, stop = self._parse_bounds(args)
513519
first, last = self._search(self._modified, start, stop)
514520
original = self._original[first:last+1]
515521
modified = self._modified[first:last+1]
516522
modified = [min(max(i, start), stop) for i in modified]
517523
return self._create(original, modified)
518524

519-
def __add__(self, other):
525+
def __add__(self, other: Any) -> Alignment:
520526
"""
521527
Concatenate two alignments.
522528
"""

0 commit comments

Comments
 (0)