Added type annotations to all Python functions and methods, and almost all global variables.

This commit is contained in:
Project Nayuki 2020-04-24 18:07:59 +00:00
parent 49f9ea0890
commit 55dd3c881e
4 changed files with 62 additions and 61 deletions

View File

@ -25,10 +25,10 @@
#
import itertools, random, subprocess, sys, time
from typing import Optional, TypeVar
from typing import List, Optional, TypeVar
CHILD_PROGRAMS = [
CHILD_PROGRAMS: List[List[str]] = [
["python3", "-B", "../python/qrcodegen-worker.py"], # Python program
["java", "-cp", "../java/src/main/java", "-ea:io.nayuki.qrcodegen...", "io/nayuki/qrcodegen/QrCodeGeneratorWorker"], # Java program
["node", "../typescript-javascript/qrcodegen-worker.js"], # TypeScript program
@ -38,9 +38,9 @@ CHILD_PROGRAMS = [
]
subprocs = []
subprocs: List[subprocess.Popen] = []
def main():
def main() -> None:
# Launch workers
global subprocs
try:
@ -67,7 +67,7 @@ def main():
print()
def do_trial():
def do_trial() -> None:
mode = random.randrange(4)
if mode == 0: # Numeric
length = round((2 * 7089) ** random.random())
@ -115,15 +115,15 @@ def do_trial():
read_verify()
def write_all(val):
def write_all(val: int) -> None:
for proc in subprocs:
print(val, file=proc.stdin)
def flush_all():
def flush_all() -> None:
for proc in subprocs:
not_none(proc.stdin).flush()
def read_verify():
def read_verify() -> int:
val = not_none(subprocs[0].stdout).readline().rstrip("\r\n")
for proc in subprocs[1 : ]:
if not_none(proc.stdout).readline().rstrip("\r\n") != val:

View File

@ -27,7 +27,7 @@
from qrcodegen import QrCode, QrSegment
def main():
def main() -> None:
"""The main application program."""
do_basic_demo()
do_variety_demo()
@ -38,7 +38,7 @@ def main():
# ---- Demo suite ----
def do_basic_demo():
def do_basic_demo() -> None:
"""Creates a single QR Code, then prints it to the console."""
text = "Hello, world!" # User-supplied Unicode text
errcorlvl = QrCode.Ecc.LOW # Error correction level
@ -49,7 +49,7 @@ def do_basic_demo():
print(qr.to_svg_str(4))
def do_variety_demo():
def do_variety_demo() -> None:
"""Creates a variety of QR Codes that exercise different features of the library, and prints each one to the console."""
# Numeric mode encoding (3.33 bits per digit)
@ -76,7 +76,7 @@ def do_variety_demo():
print_qr(qr)
def do_segment_demo():
def do_segment_demo() -> None:
"""Creates QR Codes with manually specified segments for better compactness."""
# Illustration "silver"
@ -146,7 +146,7 @@ def do_segment_demo():
print_qr(qr)
def do_mask_demo():
def do_mask_demo() -> None:
"""Creates QR Codes with the same size and contents but different mask patterns."""
# Project Nayuki URL
@ -170,7 +170,7 @@ def do_mask_demo():
# ---- Utilities ----
def print_qr(qrcode):
def print_qr(qrcode: QrCode) -> None:
"""Prints the given QrCode object to the console."""
border = 4
for y in range(-border, qrcode.get_size() + border):

View File

@ -30,11 +30,11 @@ import sys
import qrcodegen
def read_int():
def read_int() -> int:
return int(input())
def main():
def main() -> None:
while True:
# Read data or exit

View File

@ -22,6 +22,7 @@
#
import collections, itertools, re
from typing import List, Optional, Tuple
"""
@ -79,7 +80,7 @@ class QrCode:
# ---- Static factory functions (high level) ----
@staticmethod
def encode_text(text, ecl):
def encode_text(text: str, ecl: QrCode.Ecc) -> QrCode:
"""Returns a QR Code representing the given Unicode text string at the given error correction level.
As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer
Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible
@ -90,7 +91,7 @@ class QrCode:
@staticmethod
def encode_binary(data, ecl):
def encode_binary(data: bytes, ecl: QrCode.Ecc) -> QrCode:
"""Returns a QR Code representing the given binary data at the given error correction level.
This function always encodes using the binary segment mode, not any text mode. The maximum number of
bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.
@ -103,7 +104,7 @@ class QrCode:
# ---- Static factory functions (mid level) ----
@staticmethod
def encode_segments(segs, ecl, minversion=1, maxversion=40, mask=-1, boostecl=True):
def encode_segments(segs: List[QrSegment], ecl: QrCode.Ecc, minversion: int = 1, maxversion: int = 40, mask: int = -1, boostecl: bool = True) -> QrCode:
"""Returns a QR Code representing the given segments with the given encoding parameters.
The smallest possible QR Code version within the given range is automatically
chosen for the output. Iff boostecl is true, then the ECC level of the result
@ -168,7 +169,7 @@ class QrCode:
# ---- Constructor (low level) ----
def __init__(self, version, errcorlvl, datacodewords, mask):
def __init__(self, version: int, errcorlvl: QrCode.Ecc, datacodewords: List[int], mask: int) -> None:
"""Creates a new QR Code with the given version number,
error correction level, data codeword bytes, and mask number.
This is a low-level API that most users should not use directly.
@ -230,23 +231,23 @@ class QrCode:
# ---- Accessor methods ----
def get_version(self):
def get_version(self) -> int:
"""Returns this QR Code's version number, in the range [1, 40]."""
return self._version
def get_size(self):
def get_size(self) -> int:
"""Returns this QR Code's size, in the range [21, 177]."""
return self._size
def get_error_correction_level(self):
def get_error_correction_level(self) -> QrCode.Ecc:
"""Returns this QR Code's error correction level."""
return self._errcorlvl
def get_mask(self):
def get_mask(self) -> int:
"""Returns this QR Code's mask, in the range [0, 7]."""
return self._mask
def get_module(self, x, y):
def get_module(self, x, y) -> bool:
"""Returns the color of the module (pixel) at the given coordinates, which is False
for white or True for black. The top left corner has the coordinates (x=0, y=0).
If the given coordinates are out of bounds, then False (white) is returned."""
@ -255,7 +256,7 @@ class QrCode:
# ---- Public instance methods ----
def to_svg_str(self, border):
def to_svg_str(self, border: int) -> str:
"""Returns a string of SVG code for an image depicting this QR Code, with the given number
of border modules. The string always uses Unix newlines (\n), regardless of the platform."""
if border < 0:
@ -276,7 +277,7 @@ class QrCode:
# ---- Private helper methods for constructor: Drawing function modules ----
def _draw_function_patterns(self):
def _draw_function_patterns(self) -> None:
"""Reads this object's version field, and draws and marks all function modules."""
# Draw horizontal and vertical timing patterns
for i in range(self._size):
@ -302,7 +303,7 @@ class QrCode:
self._draw_version()
def _draw_format_bits(self, mask):
def _draw_format_bits(self, mask) -> None:
"""Draws two copies of the format bits (with its own error correction code)
based on the given mask and this object's error correction level field."""
# Calculate error correction code and pack bits
@ -330,7 +331,7 @@ class QrCode:
self._set_function_module(8, self._size - 8, True) # Always black
def _draw_version(self):
def _draw_version(self) -> None:
"""Draws two copies of the version bits (with its own error correction code),
based on this object's version field, iff 7 <= version <= 40."""
if self._version < 7:
@ -352,7 +353,7 @@ class QrCode:
self._set_function_module(b, a, bit)
def _draw_finder_pattern(self, x, y):
def _draw_finder_pattern(self, x, y) -> None:
"""Draws a 9*9 finder pattern including the border separator,
with the center module at (x, y). Modules can be out of bounds."""
for dy in range(-4, 5):
@ -363,7 +364,7 @@ class QrCode:
self._set_function_module(xx, yy, max(abs(dx), abs(dy)) not in (2, 4))
def _draw_alignment_pattern(self, x, y):
def _draw_alignment_pattern(self, x, y) -> None:
"""Draws a 5*5 alignment pattern, with the center module
at (x, y). All modules must be in bounds."""
for dy in range(-2, 3):
@ -371,7 +372,7 @@ class QrCode:
self._set_function_module(x + dx, y + dy, max(abs(dx), abs(dy)) != 1)
def _set_function_module(self, x, y, isblack):
def _set_function_module(self, x: int, y: int, isblack: bool) -> None:
"""Sets the color of a module and marks it as a function module.
Only used by the constructor. Coordinates must be in bounds."""
assert type(isblack) is bool
@ -381,7 +382,7 @@ class QrCode:
# ---- Private helper methods for constructor: Codewords and masking ----
def _add_ecc_and_interleave(self, data):
def _add_ecc_and_interleave(self, data: List[int]) -> List[int]:
"""Returns a new byte string representing the given data with the appropriate error correction
codewords appended to it, based on this object's version and error correction level."""
version = self._version
@ -418,7 +419,7 @@ class QrCode:
return result
def _draw_codewords(self, data):
def _draw_codewords(self, data: List[int]) -> None:
"""Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
data area of this QR Code. Function modules need to be marked off before this is called."""
assert len(data) == QrCode._get_num_raw_data_modules(self._version) // 8
@ -441,7 +442,7 @@ class QrCode:
assert i == len(data) * 8
def _apply_mask(self, mask):
def _apply_mask(self, mask: int) -> None:
"""XORs the codeword modules in this QR Code with the given mask pattern.
The function modules must be marked and the codeword bits must be drawn
before masking. Due to the arithmetic of XOR, calling applyMask() with
@ -455,7 +456,7 @@ class QrCode:
self._modules[y][x] ^= (masker(x, y) == 0) and (not self._isfunction[y][x])
def _get_penalty_score(self):
def _get_penalty_score(self) -> int:
"""Calculates and returns the penalty score based on state of this QR Code's current modules.
This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score."""
result = 0
@ -518,7 +519,7 @@ class QrCode:
# ---- Private helper functions ----
def _get_alignment_pattern_positions(self):
def _get_alignment_pattern_positions(self) -> List[int]:
"""Returns an ascending list of positions of alignment patterns for this version number.
Each position is in the range [0,177), and are used on both the x and y axes.
This could be implemented as lookup table of 40 variable-length lists of integers."""
@ -534,7 +535,7 @@ class QrCode:
@staticmethod
def _get_num_raw_data_modules(ver):
def _get_num_raw_data_modules(ver) -> int:
"""Returns the number of data bits that can be stored in a QR Code of the given version number, after
all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table."""
@ -551,7 +552,7 @@ class QrCode:
@staticmethod
def _get_num_data_codewords(ver, ecl):
def _get_num_data_codewords(ver, ecl: QrCode.Ecc) -> int:
"""Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
QR Code of the given version number and error correction level, with remainder bits discarded.
This stateless pure function could be implemented as a (40*4)-cell lookup table."""
@ -561,7 +562,7 @@ class QrCode:
@staticmethod
def _reed_solomon_compute_divisor(degree):
def _reed_solomon_compute_divisor(degree: int) -> List[int]:
"""Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be
implemented as a lookup table over all possible parameter values, instead of as an algorithm."""
if not (1 <= degree <= 255):
@ -585,7 +586,7 @@ class QrCode:
@staticmethod
def _reed_solomon_compute_remainder(data, divisor):
def _reed_solomon_compute_remainder(data: List[int], divisor: List[int]) -> List[int]:
"""Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials."""
result = [0] * len(divisor)
for b in data: # Polynomial division
@ -597,7 +598,7 @@ class QrCode:
@staticmethod
def _reed_solomon_multiply(x, y):
def _reed_solomon_multiply(x: int, y: int) -> int:
"""Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result
are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8."""
if x >> 8 != 0 or y >> 8 != 0:
@ -611,7 +612,7 @@ class QrCode:
return z
def _finder_penalty_count_patterns(self, runhistory):
def _finder_penalty_count_patterns(self, runhistory: collections.deque) -> int:
"""Can only be called immediately after a white run is added, and
returns either 0, 1, or 2. A helper function for _get_penalty_score()."""
n = runhistory[1]
@ -621,7 +622,7 @@ class QrCode:
+ (1 if (core and runhistory[6] >= n * 4 and runhistory[0] >= n) else 0)
def _finder_penalty_terminate_and_count(self, currentruncolor, currentrunlength, runhistory):
def _finder_penalty_terminate_and_count(self, currentruncolor: bool, currentrunlength: int, runhistory: collections.deque) -> int:
"""Must be called at the end of a line (row or column) of modules. A helper function for _get_penalty_score()."""
if currentruncolor: # Terminate black run
self._finder_penalty_add_history(currentrunlength, runhistory)
@ -631,7 +632,7 @@ class QrCode:
return self._finder_penalty_count_patterns(runhistory)
def _finder_penalty_add_history(self, currentrunlength, runhistory):
def _finder_penalty_add_history(self, currentrunlength: int, runhistory: collections.deque) -> None:
if runhistory[0] == 0:
currentrunlength += self._size # Add white border to initial run
runhistory.appendleft(currentrunlength)
@ -681,7 +682,7 @@ class QrCode:
class Ecc:
"""The error correction level in a QR Code symbol. Immutable."""
# Private constructor
def __init__(self, i, fb):
def __init__(self, i: int, fb: int) -> None:
self.ordinal = i # (Public) In the range 0 to 3 (unsigned 2-bit integer)
self.formatbits = fb # (Package-private) In the range 0 to 3 (unsigned 2-bit integer)
@ -715,7 +716,7 @@ class QrSegment:
# ---- Static factory functions (mid level) ----
@staticmethod
def make_bytes(data):
def make_bytes(data) -> QrSegment:
"""Returns a segment representing the given binary data encoded in byte mode.
All input byte lists are acceptable. Any text string can be converted to
UTF-8 bytes (s.encode("UTF-8")) and encoded as a byte mode segment."""
@ -728,7 +729,7 @@ class QrSegment:
@staticmethod
def make_numeric(digits):
def make_numeric(digits: str) -> QrSegment:
"""Returns a segment representing the given string of decimal digits encoded in numeric mode."""
if QrSegment.NUMERIC_REGEX.match(digits) is None:
raise ValueError("String contains non-numeric characters")
@ -742,7 +743,7 @@ class QrSegment:
@staticmethod
def make_alphanumeric(text):
def make_alphanumeric(text: str) -> QrSegment:
"""Returns a segment representing the given text string encoded in alphanumeric mode.
The characters allowed are: 0 to 9, A to Z (uppercase only), space,
dollar, percent, asterisk, plus, hyphen, period, slash, colon."""
@ -759,7 +760,7 @@ class QrSegment:
@staticmethod
def make_segments(text):
def make_segments(text: str) -> List[QrSegment]:
"""Returns a new mutable list of zero or more segments to represent the given Unicode text string.
The result may use various segment modes and switch modes to optimize the length of the bit stream."""
if not isinstance(text, str):
@ -777,7 +778,7 @@ class QrSegment:
@staticmethod
def make_eci(assignval):
def make_eci(assignval: int) -> QrSegment:
"""Returns a segment representing an Extended Channel Interpretation
(ECI) designator with the given assignment value."""
bb = _BitBuffer()
@ -798,7 +799,7 @@ class QrSegment:
# ---- Constructor (low level) ----
def __init__(self, mode, numch, bitdata):
def __init__(self, mode: QrSegment.Mode, numch: int, bitdata: List[int]) -> None:
"""Creates a new QR Code segment with the given attributes and data.
The character count (numch) must agree with the mode and the bit buffer length,
but the constraint isn't checked. The given bit buffer is cloned and stored."""
@ -822,22 +823,22 @@ class QrSegment:
# ---- Accessor methods ----
def get_mode(self):
def get_mode(self) -> QrSegment.Mode:
"""Returns the mode field of this segment."""
return self._mode
def get_num_chars(self):
def get_num_chars(self) -> int:
"""Returns the character count field of this segment."""
return self._numchars
def get_data(self):
def get_data(self) -> List[int]:
"""Returns a new copy of the data bits of this segment."""
return list(self._bitdata) # Make defensive copy
# Package-private function
@staticmethod
def get_total_bits(segs, version):
def get_total_bits(segs, version: int) -> Optional[int]:
"""Calculates the number of bits needed to encode the given segments at
the given version. Returns a non-negative number if successful. Otherwise
returns None if a segment has too many characters to fit its length field."""
@ -874,17 +875,17 @@ class QrSegment:
"""Describes how a segment's data bits are interpreted. Immutable."""
# Private constructor
def __init__(self, modebits, charcounts):
def __init__(self, modebits: int, charcounts: Tuple[int,int,int]):
self._modebits = modebits # The mode indicator bits, which is a uint4 value (range 0 to 15)
self._charcounts = charcounts # Number of character count bits for three different version ranges
# Package-private method
def get_mode_bits(self):
def get_mode_bits(self) -> int:
"""Returns an unsigned 4-bit integer value (range 0 to 15) representing the mode indicator bits for this mode object."""
return self._modebits
# Package-private method
def num_char_count_bits(self, ver):
def num_char_count_bits(self, ver: int) -> int:
"""Returns the bit width of the character count field for a segment in this mode
in a QR Code at the given version number. The result is in the range [0, 16]."""
return self._charcounts[(ver + 7) // 17]
@ -910,7 +911,7 @@ class QrSegment:
class _BitBuffer(list):
"""An appendable sequence of bits (0s and 1s). Mainly used by QrSegment."""
def append_bits(self, val, n):
def append_bits(self, val: int, n: int) -> None:
"""Appends the given number of low-order bits of the given
value to this buffer. Requires n >= 0 and 0 <= val < 2^n."""
if n < 0 or val >> n != 0:
@ -918,7 +919,7 @@ class _BitBuffer(list):
self.extend(((val >> i) & 1) for i in reversed(range(n)))
def _get_bit(x, i):
def _get_bit(x: int, i: int) -> bool:
"""Returns true iff the i'th bit of x is set to 1."""
return (x >> i) & 1 != 0