# 挖掘功能规范

> 原文： [https://www.fuzzingbook.org/html/DynamicInvariants.html](https://www.fuzzingbook.org/html/DynamicInvariants.html)

在测试程序时，不仅需要涵盖程序的几种行为，还需要对程序进行测试。 还需要*检查*结果是否符合预期。 在本章中，我们介绍一种技术，该技术使我们可以从一组给定的执行中*挖掘*功能规范，从而得出对该功能期望及其提供的内容的抽象和形式化的*描述*。

这些所谓的*动态不变量*对一组执行的函数自变量和变量产生前置条件和后置条件。 它们在多种情况下很有用：

*   动态不变量为[符号模糊](SymbolicFuzzer.html)提供重要信息，例如函数参数的类型和范围。
*   动态不变式为正式的程序证明和验证提供了前提条件。
*   动态不变量提供大量断言，可以检查函数行为是否已更改
*   动态不变量提供的检查作为 *oracles* 对于检查生成的测试的效果非常有用。

传统上，动态不变量取决于它们所源自的执行。 但是，当与综合测试生成器配合使用时，它们很快变得非常精确，如本章所示。

**前提条件**

*   您应该熟悉跟踪程序的执行情况，如关于的[一章中所述。](Coverage.html)
*   在本节的稍后部分，我们将访问Python程序的内部*抽象语法树*表示形式并对其进行转换，就像关于信息流的[一章中一样。](InformationFlow.html)

```py
import [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils)

```

```py
import [Coverage](Coverage.html)
import [Intro_Testing](Intro_Testing.html)

```

## 内容提要

要使用本章中提供的代码来[，请编写](Importing.html)

```py
>>> from [fuzzingbook.DynamicInvariants](DynamicInvariants.html) import <identifier>

```

然后利用以下功能。

本章提供了两个类，这些类可自动从函数和一组输入中提取规范：

*   *类型*的`TypeAnnotator`，以及
*   *之前的*和*后置条件*的`InvariantAnnotator`。

两者都是通过*观察*一个函数及其在`with`子句中的调用来工作的。 这是类型注释器的示例：

```py
>>> def sum2(a, b):
>>>     return a + b
>>> with TypeAnnotator() as type_annotator:
>>>     sum2(1, 2)
>>>     sum2(-4, -5)
>>>     sum2(0, 0)

```

`typed_functions()`方法将返回`sum2()`的表示形式，并用在执行过程中观察到的类型进行注释。

```py
>>> print(type_annotator.typed_functions())
def sum2(a: int, b: int) ->int:
    return a + b

```

不变注释器的工作方式类似：

```py
>>> with InvariantAnnotator() as inv_annotator:
>>>     sum2(1, 2)
>>>     sum2(-4, -5)
>>>     sum2(0, 0)

```

`functions_with_invariants()`方法将返回`sum2()`的表示形式，并用推断的前提条件和后置条件进行注释，这些条件对于观察值均成立。

```py
>>> print(inv_annotator.functions_with_invariants())
@precondition(lambda a, b: isinstance(a, int))
@precondition(lambda a, b: isinstance(b, int))
@postcondition(lambda return_value, a, b: a == return_value - b)
@postcondition(lambda return_value, a, b: b == return_value - a)
@postcondition(lambda return_value, a, b: isinstance(return_value, int))
@postcondition(lambda return_value, a, b: return_value == a + b)
@postcondition(lambda return_value, a, b: return_value == b + a)
def sum2(a, b):
    return a + b

```

这样的类型规范和不变量可以作为*预言*（用于检测与一组给定行程的偏差）以及所有*符号代码分析*都是有用的。 本章详细介绍了如何自定义检查的属性。

## 规范和断言

在实现功能或程序时，通常会违反*规范* –一组代码要满足的书面要求。 这样的规范可以用自然语言来表示。 但是，正式的规范允许计算机检查规范是否得到满足。

在测试的[简介中，我们已经看到*前置条件*和*后置条件*如何描述函数的功能。 考虑以下（简单）平方根函数：](Intro_Testing.html)

```py
def my_sqrt(x):
    assert x >= 0  # Precondition

    ...

    assert result * result == x  # Postcondition
    return result

```

断言`assert p`检查条件`p`； 如果不成立，则中止执行。 在这里，实际的身体还没有写。 我们将这些断言用作`my_sqrt()` *期望*以及*交付*的规范。

最上面的断言是*前提条件*，其中说明了函数自变量的要求。 最后的断言是*后置条件*，说明函数结果的属性（包括其与原始参数的关系）。 使用这些前置条件和后置条件作为规范，我们现在可以实现满足它们的平方根函数。 一旦实现，我们就可以在运行时检查断言，`my_sqrt()`是否按预期工作； [符号](SymbolicFuzzer.html)或[圆锥形](ConcolicFuzzer.html)测试生成器甚至会专门尝试查找断言没有*保持的输入。 （断言可以看作是中止执行的条件分支，任何试图覆盖所有代码分支的技术都将尝试使尽可能多的断言无效。）*

但是，并非一开始就用明确的规范来开发每段代码； 更不用说大多数代码都附带了正式的前置条件和后置条件。 （只是看一下本书中的章节。）很遗憾：正如肯·汤普森（Ken Thompson）所说的那样：“没有规范，就没有bug，只有惊喜”。 这也是测试的问题，因为测试当然需要一些规范来进行测试。 这就提出了一个有趣的问题：我们是否可以通过适当地描述其行为的“规范”对*改造*现有代码，从而使开发人员可以简单地*检查*而不必从头开始编写它们？ 这就是我们在本章中所做的。

## 为什么通用错误检查不够用

在进入*挖掘*规范之前，让我们首先讨论为什么*拥有*规范可能有用。 作为一个激励示例，请考虑从[简介到测试](Intro_Testing.html)的`my_sqrt()`的完整实现：

```py
import [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils)

```

```py
def my_sqrt(x):
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

```

`my_sqrt()`没有任何可检查类型或值的功能。 因此，在调用`my_sqrt()`时，调用者很容易出错：

```py
from [ExpectError](ExpectError.html) import ExpectError, ExpectTimeout

```

```py
with ExpectError():
    my_sqrt("foo")

```

```py
Traceback (most recent call last):
  File "<ipython-input-7-774676a5ccb8>", line 2, in <module>
    my_sqrt("foo")
  File "<ipython-input-5-47185ad159a1>", line 4, in my_sqrt
    guess = x / 2
TypeError: unsupported operand type(s) for /: 'str' and 'int' (expected)

```

```py
with ExpectError():
    x = my_sqrt(0.0)

```

```py
Traceback (most recent call last):
  File "<ipython-input-8-262c66114b1c>", line 2, in <module>
    x = my_sqrt(0.0)
  File "<ipython-input-5-47185ad159a1>", line 7, in my_sqrt
    guess = (approx + x / approx) / 2
ZeroDivisionError: float division by zero (expected)

```

至少，Python系统会在运行时捕获这些错误。 但是，以下调用仅使函数进入无限循环：

```py
with ExpectTimeout(1):
    x = my_sqrt(-1.0)

```

```py
Traceback (most recent call last):
  File "<ipython-input-9-b72078127dc0>", line 2, in <module>
    x = my_sqrt(-1.0)
  File "<ipython-input-5-47185ad159a1>", line 7, in my_sqrt
    guess = (approx + x / approx) / 2
  File "<ipython-input-5-47185ad159a1>", line 7, in my_sqrt
    guess = (approx + x / approx) / 2
  File "<string>", line 16, in check_time
TimeoutError (expected)

```

我们的目标是通过*注释*函数以防止出现类似于上述错误的信息，从而避免此类错误。 这个想法是提供期望特性的*规范*-然后可以在运行时或静态检查该规范。

\ todo {介绍*合同*的概念。}

## 指定和检查数据类型

对于我们的Python代码，我们需要的最重要的“规范”之一是*类型*。 Python是一种“动态”类型的语言，意味着所有数据类型都是在运行时确定的； 代码本身并未明确声明变量是整数，字符串，数组，字典还是其他。

作为Python代码的*编写者*，省略显式类型声明可以节省时间（并允许一些有趣的hack）。 不确定是否缺少类型有助于*阅读*和*了解人类的*代码。 对于试图分析代码的*计算机*，缺少显式类型是有害的。 例如，如果约束求解器看到`if x:`却不知道`x`应该是数字还是字符串，则将引入*模糊性*。 这种歧义性可能会在组合爆炸中使整个分析倍增，或者在分析中产生过于不准确的结果。

Python 3.6和更高版本允许数据类型作为*批注*来函数自变量（实际上是所有变量）并返回值。 例如，我们可以说`my_sqrt()`是一个接受浮点值并返回一个的函数：

```py
def my_sqrt_with_type_annotations(x: float) -> float:
    """Computes the square root of x, using the Newton-Raphson method"""
    return my_sqrt(x)

```

默认情况下，Python解释器会忽略此类注释。 因此，仍然可以使用字符串作为参数调用`my_sqrt_typed()`，并获得与上述完全相同的结果。 但是，可以使用特殊的*类型检查*模块，这些模块可以在运行时动态检查或通过分析代码静态 **来执行类型检查，而无需执行代码。**

### 运行时类型检查

Python `enforce`包提供了一个函数装饰器，该装饰器会自动插入在运行时执行的类型检查代码。 使用方法如下：

```py
import [enforce](https://docs.python.org/3/library/enforce.html)

```

```py
@enforce.runtime_validation
def my_sqrt_with_checked_type_annotations(x: float) -> float:
    """Computes the square root of x, using the Newton-Raphson method"""
    return my_sqrt(x)

```

现在，以与声明的类型不同的类型调用`my_sqrt_with_checked_type_annotations()`会引发异常：

```py
with ExpectError():
    my_sqrt_with_checked_type_annotations(True)

```

```py
Traceback (most recent call last):
  File "<ipython-input-13-68b73bd3f6ef>", line 2, in <module>
    my_sqrt_with_checked_type_annotations(True)
  File "/Users/zeller/Library/Python/3.6/site-packages/enforce/decorators.py", line 104, in universal
    _args, _kwargs, _ = enforcer.validate_inputs(parameters)
  File "/Users/zeller/Library/Python/3.6/site-packages/enforce/enforcers.py", line 86, in validate_inputs
    raise RuntimeTypeError(exception_text)
enforce.exceptions.RuntimeTypeError: 
  The following runtime type errors were encountered:
       Argument 'x' was not of type <class 'float'>. Actual type was bool. (expected)

```

请注意，“ untyped”变量不会捕获此错误，在该变量中，传递布尔值会愉快地返回$ \ sqrt {1} $作为结果。

```py
my_sqrt(True)

```

```py
1.0

```

在Python（和其他语言）中，布尔值`True`和`False`可以隐式转换为整数1和0; 但是，很难想到没有错误的对`sqrt()`的调用。

### 静态类型检查

类型注释也可以通过*静态检查*-也就是说，甚至无需运行代码。 让我们创建一个简单的Python文件，其中包含上述`my_sqrt_typed()`定义和错误的调用。

```py
import [inspect](https://docs.python.org/3/library/inspect.html)
import [tempfile](https://docs.python.org/3/library/tempfile.html)

```

```py
f = tempfile.NamedTemporaryFile(mode='w', suffix='.py')
f.name

```

```py
'/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmp0utpjwyq.py'

```

```py
f.write(inspect.getsource(my_sqrt))
f.write('\n')
f.write(inspect.getsource(my_sqrt_with_type_annotations))
f.write('\n')
f.write("print(my_sqrt_with_type_annotations('123'))\n")
f.flush()

```

这些是我们新创建的Python文件的内容：

```py
from [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils) import print_file

```

```py
print_file(f.name)

```

```py
def my_sqrt(x):
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

def my_sqrt_with_type_annotations(x: float) -> float:
    """Computes the square root of x, using the Newton-Raphson method"""
    return my_sqrt(x)

print(my_sqrt_with_type_annotations('123'))

```

[Mypy](http://mypy-lang.org) 是Python程序的类型检查器。 由于它静态地检查类型，因此类型在运行时不会产生开销； 此外，启用了运行时类型检查的静态检查比一系列冗长的测试要快。 让我们看看`mypy`在以上文件中产生的结果：

```py
import [subprocess](https://docs.python.org/3/library/subprocess.html)

```

```py
result = subprocess.run(["mypy", "--strict", f.name], universal_newlines=True, stdout=subprocess.PIPE)
del f  # Delete temporary file

```

```py
print(result.stdout)

```

```py
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmp0utpjwyq.py:1: error: Function is missing a type annotation
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmp0utpjwyq.py:12: warning: Returning Any from function declared to return "float"
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmp0utpjwyq.py:12: error: Call to untyped function "my_sqrt" in typed context
/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/tmp0utpjwyq.py:14: error: Argument 1 to "my_sqrt_with_type_annotations" has incompatible type "str"; expected "float"

```

我们看到`mypy`抱怨未类型化的函数定义，例如`my_sqrt()`； 最重要的是，它发现最后一行中对`my_sqrt_with_type_annotations()`的调用类型错误。

使用`mypy`，我们可以使用Python达到与静态类型语言相同的类型安全性-前提是我们作为程序员还可以生成必要的类型注释。 有没有简单的方法来获取这些？

## 挖掘类型规范

我们的第一个任务是从运行时观察到的*值*中挖掘类型注释（作为代码的一部分）。 这些类型注释将是*从实际函数执行中提取的*，*从（正常）运行中学习*的预期参数和返回类型应该是什么。 通过观察一系列这样的调用，我们可以推断`x`和返回值均为`float`类型：

```py
y = my_sqrt(25.0)
y

```

```py
5.0

```

```py
y = my_sqrt(2.0)
y

```

```py
1.414213562373095

```

我们如何从执行中挖掘类型？ 答案很简单：

1.  我们在执行过程中观察*一个函数*
2.  我们跟踪其参数的*类型*
3.  我们将这些类型作为*注释*包含在代码中。

为此，我们可以利用在关于覆盖率的章节中已经观察到的Python跟踪工具。 每次调用函数时，我们都会检索参数，它们的值和它们的类型。

### 跟踪呼叫

为了在运行时观察参数类型，我们定义了*跟踪器函数*，该函数跟踪`my_sqrt()`的执行情况，检查其参数并返回值。 `Tracker`类设置为跟踪`with`块中的函数，如下所示：

```py
with Tracker() as tracker:
    function_to_be_tracked(...)
info = tracker.collected_information()

```

就像关于的[一章中一样，我们在执行期间使用`sys.settrace()`函数跟踪单个函数。 当`with`块开始时，我们打开跟踪； 此时，将调用`__enter__()`方法。 当`with`块的执行结束时，将调用`__exit()__`。](Coverage.html)

```py
import [sys](https://docs.python.org/3/library/sys.html)

```

```py
class Tracker(object):
    def __init__(self, log=False):
        self._log = log
        self.reset()

    def reset(self):
        self._calls = {}
        self._stack = []

    def traceit(self):
        """Placeholder to be overloaded in subclasses"""
        pass

    # Start of `with` block
    def __enter__(self):
        self.original_trace_function = sys.gettrace()
        sys.settrace(self.traceit)
        return self

    # End of `with` block
    def __exit__(self, exc_type, exc_value, tb):
        sys.settrace(self.original_trace_function)

```

`traceit()`方法什么也没做； 这是在专门的子类中完成的。 `CallTracker`类实现了`traceit()`函数，该函数检查函数调用并返回：

```py
class CallTracker(Tracker):
    def traceit(self, frame, event, arg):
        """Tracking function: Record all calls and all args"""
        if event == "call":
            self.trace_call(frame, event, arg)
        elif event == "return":
            self.trace_return(frame, event, arg)

        return self.traceit

```

调用函数时，调用`trace_call()`。 它检索函数名称和当前参数，并将它们保存在堆栈中。

```py
class CallTracker(CallTracker):
    def trace_call(self, frame, event, arg):
        """Save current function name and args on the stack"""
        code = frame.f_code
        function_name = code.co_name
        arguments = get_arguments(frame)
        self._stack.append((function_name, arguments))

        if self._log:
            print(simple_call_string(function_name, arguments))

```

```py
def get_arguments(frame):
    """Return call arguments in the given frame"""
    # When called, all arguments are local variables
    arguments = [(var, frame.f_locals[var]) for var in frame.f_locals]
    arguments.reverse()  # Want same order as call
    return arguments

```

当函数返回时，将调用`trace_return()`。 现在，我们还有返回值。 我们使用参数记录整个调用并返回值（如果需要），并将其保存在调用列表中。

```py
class CallTracker(CallTracker):
    def trace_return(self, frame, event, arg):
        """Get return value and store complete call with arguments and return value"""
        code = frame.f_code
        function_name = code.co_name
        return_value = arg
        # TODO: Could call get_arguments() here to also retrieve _final_ values of argument variables

        called_function_name, called_arguments = self._stack.pop()
        assert function_name == called_function_name

        if self._log:
            print(simple_call_string(function_name, called_arguments), "returns", return_value)

        self.add_call(function_name, called_arguments, return_value)

```

`simple_call_string()`是用于记录的帮助程序，它以用户友好的方式打印出呼叫。

```py
def simple_call_string(function_name, argument_list, return_value=None):
    """Return function_name(arg[0], arg[1], ...) as a string"""
    call = function_name + "(" + \
        ", ".join([var + "=" + repr(value)
                   for (var, value) in argument_list]) + ")"

    if return_value is not None:
        call += " = " + repr(return_value)

    return call

```

`add_call()`将呼叫保存在列表中； 每个函数名称都有其自己的列表。

```py
class CallTracker(CallTracker):
    def add_call(self, function_name, arguments, return_value=None):
        """Add given call to list of calls"""
        if function_name not in self._calls:
            self._calls[function_name] = []
        self._calls[function_name].append((arguments, return_value))

```

使用`calls()`，我们可以检索给定功能或所有功能的调用列表。

```py
class CallTracker(CallTracker):
    def calls(self, function_name=None):
        """Return list of calls for function_name, 
 or a mapping function_name -> calls for all functions tracked"""
        if function_name is None:
            return self._calls

        return self._calls[function_name]

```

现在让我们使用它。 我们打开日志记录以跟踪各个调用及其返回值：

```py
with CallTracker(log=True) as tracker:
    y = my_sqrt(25)
    y = my_sqrt(2.0)

```

```py
my_sqrt(x=25)
my_sqrt(x=25) returns 5.0
my_sqrt(x=2.0)
my_sqrt(x=2.0) returns 1.414213562373095
__exit__(self=<__main__.CallTracker object at 0x114c3e898>, exc_type=None, exc_value=None, tb=None)

```

执行后，我们可以检索单个调用：

```py
calls = tracker.calls('my_sqrt')
calls

```

```py
[([('x', 25)], 5.0), ([('x', 2.0)], 1.414213562373095)]

```

每个呼叫都是一对（`argument_list`，`return_value`），其中`argument_list`是一对列表（`parameter_name`，`value`）。

```py
my_sqrt_argument_list, my_sqrt_return_value = calls[0]
simple_call_string('my_sqrt', my_sqrt_argument_list, my_sqrt_return_value)

```

```py
'my_sqrt(x=25) = 5.0'

```

如果函数未返回值，则`return_value`为`None`。

```py
def hello(name):
    print("Hello,", name)

```

```py
with CallTracker() as tracker:
    hello("world")

```

```py
Hello, world

```

```py
hello_calls = tracker.calls('hello')
hello_calls

```

```py
[([('name', 'world')], None)]

```

```py
hello_argument_list, hello_return_value = hello_calls[0]
simple_call_string('hello', hello_argument_list, hello_return_value)

```

```py
"hello(name='world')"

```

### 获取类型

尽管您可能已经读过或听过，Python实际上*是*一种类型的语言。 只是它是*动态类型的* –类型仅在运行时使用和检查（而不是在代码中声明，可以在编译时对*进行静态检查*）。 因此，我们可以在Python中检索所有值的类型：

```py
type(4)

```

```py
int

```

```py
type(2.0)

```

```py
float

```

```py
type([4])

```

```py
list

```

我们可以检索`my_sqrt()`的第一个参数的类型：

```py
parameter, value = my_sqrt_argument_list[0]
parameter, type(value)

```

```py
('x', int)

```

以及返回值的类型：

```py
type(my_sqrt_return_value)

```

```py
float

```

因此，我们看到（到目前为止），`my_sqrt()`是一个函数（采用整数）并返回浮点数。 我们可以将`my_sqrt()`声明为：

```py
def my_sqrt_annotated(x: int) -> float:
    return my_sqrt(x)

```

这是我们可以放置在静态类型检查器中的一种表示形式，它可以检查对`my_sqrt()`的调用是否实际上传递了数字。 动态类型检查器可以在运行时运行此类检查。 当然，任何[符号解释](SymbolicFuzzer.html)都会从附加注释中受益匪浅。

默认情况下，Python对此类注释不做任何事情。 但是，工具可以从函数和其他对象访问批注：

```py
my_sqrt_annotated.__annotations__

```

```py
{'x': int, 'return': float}

```

这是运行时检查器访问注释进行检查的方式。

### 访问功能结构

我们的计划是根据我们看到的类型自动注释功能。 为此，我们需要一些模块，这些模块允许我们将函数转换为树表示形式（称为*抽象语法树*或AST），然后返回； 我们已经在[曲线](ConcolicFuzzer.html)和[符号](SymbolicFuzzer.html)测试中的章节中看到了这些。

```py
import [ast](https://docs.python.org/3/library/ast.html)
import [inspect](https://docs.python.org/3/library/inspect.html)
import [astor](https://docs.python.org/3/library/astor.html)

```

我们可以使用`inspect.getsource()`获得Python函数的源代码。 （请注意，这不适用于其他笔记本中定义的功能。）

```py
my_sqrt_source = inspect.getsource(my_sqrt)
my_sqrt_source

```

```py
'def my_sqrt(x):\n    """Computes the square root of x, using the Newton-Raphson method"""\n    approx = None\n    guess = x / 2\n    while approx != guess:\n        approx = guess\n        guess = (approx + x / approx) / 2\n    return approx\n'

```

为了以令人愉悦的形式查看这些内容，我们的函数`print_content(s, suffix)`格式化并突出显示了字符串`s`，就像它是一个以`suffix`结尾的文件一样。 因此，我们可以像查看Python文件一样查看（并突出显示）源：

```py
from [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils) import print_content

```

```py
print_content(my_sqrt_source, '.py')

```

```py
def my_sqrt(x):
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

```

对此进行解析可以为我们提供抽象语法树（AST）-以树形式表示程序。

```py
my_sqrt_ast = ast.parse(my_sqrt_source)

```

这个AST是什么样的？ 辅助功能`astor.dump_tree()`（文本输出）和`showast.show_ast()`（带有 [showast](https://github.com/hchasestevens/show_ast) 的图形输出）使我们可以检查树的结构。 我们看到函数以带有名称和参数的`FunctionDef`开始，后跟一个主体，该主体是`Expr`类型（文档字符串），`Assign`类型（赋值），`While`类型的语句列表 环回自己的身体），最后`Return`。

```py
print(astor.dump_tree(my_sqrt_ast))

```

```py
Module(
    body=[
        FunctionDef(name='my_sqrt',
            args=arguments(args=[arg(arg='x', annotation=None)],
                vararg=None,
                kwonlyargs=[],
                kw_defaults=[],
                kwarg=None,
                defaults=[]),
            body=[
                Expr(value=Str(s='Computes the square root of x, using the Newton-Raphson method')),
                Assign(targets=[Name(id='approx')], value=NameConstant(value=None)),
                Assign(targets=[Name(id='guess')], value=BinOp(left=Name(id='x'), op=Div, right=Num(n=2))),
                While(
                    test=Compare(left=Name(id='approx'), ops=[NotEq], comparators=[Name(id='guess')]),
                    body=[Assign(targets=[Name(id='approx')], value=Name(id='guess')),
                        Assign(targets=[Name(id='guess')],
                            value=BinOp(
                                left=BinOp(left=Name(id='approx'),
                                    op=Add,
                                    right=BinOp(left=Name(id='x'), op=Div, right=Name(id='approx'))),
                                op=Div,
                                right=Num(n=2)))],
                    orelse=[]),
                Return(value=Name(id='approx'))],
            decorator_list=[],
            returns=None)])

```

文字太多了吗？ 这种图形表示可以使事情变得更简单。

```py
from [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils) import rich_output

```

```py
if rich_output():
    import [showast](https://docs.python.org/3/library/showast.html)
    showast.show_ast(my_sqrt_ast)

```

<svg height="548pt" viewBox="0.00 0.00 1705.00 548.00" width="1705pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 544)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="303.5" y="-519.3">FunctionDef</text></g> <g class="node" id="node2"><title>1</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="90" y="-446.3">"my_sqrt"</text></g> <g class="edge" id="edge1"><title>0--1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="162" y="-447.3">arguments</text></g> <g class="edge" id="edge2"><title>0--2</title></g> <g class="node" id="node6"><title>5</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="272.5" y="-447.3">Assign</text></g> <g class="edge" id="edge5"><title>0--5</title></g> <g class="node" id="node11"><title>10</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="367.5" y="-447.3">Assign</text></g> <g class="edge" id="edge10"><title>0--10</title></g> <g class="node" id="node22"><title>21</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="919.5" y="-447.3">While</text></g> <g class="edge" id="edge21"><title>0--21</title></g> <g class="node" id="node59"><title>58</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1333.5" y="-447.3">Return</text></g> <g class="edge" id="edge58"><title>0--58</title></g> <g class="node" id="node4"><title>3</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="48" y="-375.3">arg</text></g> <g class="edge" id="edge3"><title>2--3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="27" y="-302.3">"x"</text></g> <g class="edge" id="edge4"><title>3--4</title></g> <g class="node" id="node7"><title>6</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="168" y="-375.3">Name</text></g> <g class="edge" id="edge6"><title>5--6</title></g> <g class="node" id="node10"><title>9</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="289" y="-374.3">NameConstant</text></g> <g class="edge" id="edge9"><title>5--9</title></g> <g class="node" id="node8"><title>7</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="114" y="-302.3">"approx"</text></g> <g class="edge" id="edge7"><title>6--7</title></g> <g class="node" id="node9"><title>8</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="204" y="-302.3">Store</text></g> <g class="edge" id="edge8"><title>6--8</title></g> <g class="node" id="node12"><title>11</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="376" y="-375.3">Name</text></g> <g class="edge" id="edge11"><title>10--11</title></g> <g class="node" id="node15"><title>14</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="482.5" y="-375.3">BinOp</text></g> <g class="edge" id="edge14"><title>10--14</title></g> <g class="node" id="node13"><title>12</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="289" y="-302.3">"guess"</text></g> <g class="edge" id="edge12"><title>11--12</title></g> <g class="node" id="node14"><title>13</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="374" y="-302.3">Store</text></g> <g class="edge" id="edge13"><title>11--13</title></g> <g class="node" id="node16"><title>15</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="432" y="-303.3">Name</text></g> <g class="edge" id="edge15"><title>14--15</title></g> <g class="node" id="node19"><title>18</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="521" y="-302.3">Div</text></g> <g class="edge" id="edge18"><title>14--18</title></g> <g class="node" id="node20"><title>19</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="580" y="-303.3">Num</text></g> <g class="edge" id="edge19"><title>14--19</title></g> <g class="node" id="node17"><title>16</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="377" y="-230.3">"x"</text></g> <g class="edge" id="edge16"><title>15--16</title></g> <g class="node" id="node18"><title>17</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="449" y="-230.3">Load</text></g> <g class="edge" id="edge17"><title>15--17</title></g> <g class="node" id="node21"><title>20</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="521" y="-230.3">2</text></g> <g class="edge" id="edge20"><title>19--20</title></g> <g class="node" id="node23"><title>22</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="785.5" y="-375.3">Compare</text></g> <g class="edge" id="edge22"><title>21--22</title></g> <g class="node" id="node31"><title>30</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="967.5" y="-375.3">Assign</text></g> <g class="edge" id="edge30"><title>21--30</title></g> <g class="node" id="node38"><title>37</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1305.5" y="-375.3">Assign</text></g> <g class="edge" id="edge37"><title>21--37</title></g> <g class="node" id="node24"><title>23</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="678" y="-303.3">Name</text></g> <g class="edge" id="edge23"><title>22--23</title></g> <g class="node" id="node27"><title>26</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="778" y="-302.3">NotEq</text></g> <g class="edge" id="edge26"><title>22--26</title></g> <g class="node" id="node28"><title>27</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="836" y="-303.3">Name</text></g> <g class="edge" id="edge27"><title>22--27</title></g> <g class="node" id="node25"><title>24</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="608" y="-230.3">"approx"</text></g> <g class="edge" id="edge24"><title>23--24</title></g> <g class="node" id="node26"><title>25</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="695" y="-230.3">Load</text></g> <g class="edge" id="edge25"><title>23--25</title></g> <g class="node" id="node29"><title>28</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="778" y="-230.3">"guess"</text></g> <g class="edge" id="edge28"><title>27--28</title></g> <g class="node" id="node30"><title>29</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="861" y="-230.3">Load</text></g> <g class="edge" id="edge29"><title>27--29</title></g> <g class="node" id="node32"><title>31</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="976" y="-303.3">Name</text></g> <g class="edge" id="edge31"><title>30--31</title></g> <g class="node" id="node35"><title>34</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1106" y="-303.3">Name</text></g> <g class="edge" id="edge34"><title>30--34</title></g> <g class="node" id="node33"><title>32</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="948" y="-230.3">"approx"</text></g> <g class="edge" id="edge32"><title>31--32</title></g> <g class="node" id="node34"><title>33</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1038" y="-230.3">Store</text></g> <g class="edge" id="edge33"><title>31--33</title></g> <g class="node" id="node36"><title>35</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1123" y="-230.3">"guess"</text></g> <g class="edge" id="edge35"><title>34--35</title></g> <g class="node" id="node37"><title>36</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1206" y="-230.3">Load</text></g> <g class="edge" id="edge36"><title>34--36</title></g> <g class="node" id="node39"><title>38</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1314" y="-303.3">Name</text></g> <g class="edge" id="edge38"><title>37--38</title></g> <g class="node" id="node42"><title>41</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1429.5" y="-303.3">BinOp</text></g> <g class="edge" id="edge41"><title>37--41</title></g> <g class="node" id="node40"><title>39</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1289" y="-230.3">"guess"</text></g> <g class="edge" id="edge39"><title>38--39</title></g> <g class="node" id="node41"><title>40</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1374" y="-230.3">Store</text></g> <g class="edge" id="edge40"><title>38--40</title></g> <g class="node" id="node43"><title>42</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1429.5" y="-231.3">BinOp</text></g> <g class="edge" id="edge42"><title>41--42</title></g> <g class="node" id="node56"><title>55</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1526" y="-230.3">Div</text></g> <g class="edge" id="edge55"><title>41--55</title></g> <g class="node" id="node57"><title>56</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1586" y="-231.3">Num</text></g> <g class="edge" id="edge56"><title>41--56</title></g> <g class="node" id="node44"><title>43</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1361" y="-159.3">Name</text></g> <g class="edge" id="edge43"><title>42--43</title></g> <g class="node" id="node47"><title>46</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1450" y="-158.3">Add</text></g> <g class="edge" id="edge46"><title>42--46</title></g> <g class="node" id="node48"><title>47</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1503.5" y="-159.3">BinOp</text></g> <g class="edge" id="edge47"><title>42--47</title></g> <g class="node" id="node45"><title>44</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1293" y="-86.3">"approx"</text></g> <g class="edge" id="edge44"><title>43--44</title></g> <g class="node" id="node46"><title>45</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1380" y="-86.3">Load</text></g> <g class="edge" id="edge45"><title>43--45</title></g> <g class="node" id="node49"><title>48</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1436" y="-87.3">Name</text></g> <g class="edge" id="edge48"><title>47--48</title></g> <g class="node" id="node52"><title>51</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1525" y="-86.3">Div</text></g> <g class="edge" id="edge51"><title>47--51</title></g> <g class="node" id="node53"><title>52</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1580" y="-87.3">Name</text></g> <g class="edge" id="edge52"><title>47--52</title></g> <g class="node" id="node50"><title>49</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1396" y="-14.3">"x"</text></g> <g class="edge" id="edge49"><title>48--49</title></g> <g class="node" id="node51"><title>50</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1468" y="-14.3">Load</text></g> <g class="edge" id="edge50"><title>48--50</title></g> <g class="node" id="node54"><title>53</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1583" y="-14.3">"approx"</text></g> <g class="edge" id="edge53"><title>52--53</title></g> <g class="node" id="node55"><title>54</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1670" y="-14.3">Load</text></g> <g class="edge" id="edge54"><title>52--54</title></g> <g class="node" id="node58"><title>57</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1600" y="-158.3">2</text></g> <g class="edge" id="edge57"><title>56--57</title></g> <g class="node" id="node60"><title>59</title> <text fill="#004080" font-family="Courier,monospace" font-size="14.00" font-weight="bold" text-anchor="start" x="1524" y="-375.3">Name</text></g> <g class="edge" id="edge59"><title>58--59</title></g> <g class="node" id="node61"><title>60</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1541" y="-302.3">"approx"</text></g> <g class="edge" id="edge60"><title>59--60</title></g> <g class="node" id="node62"><title>61</title> <text fill="#008040" font-family="Courier,monospace" font-size="14.00" text-anchor="middle" x="1628" y="-302.3">Load</text></g> <g class="edge" id="edge61"><title>59--61</title></g></g></svg>

函数`astor.to_source()`将这种树转换回更熟悉的文本Python代码表示形式。 注释不见了，括号可能比以前多了，但是结果具有相同的语义：

```py
print_content(astor.to_source(my_sqrt_ast), '.py')

```

```py
def my_sqrt(x):
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

```

### 给定类型的函数注释

现在让我们对这些树进行转换，以添加类型注释。 我们从一个辅助函数`parse_type(name)`开始，该函数将类型名称解析为AST。

```py
def parse_type(name):
    class ValueVisitor(ast.NodeVisitor):
        def visit_Expr(self, node):
            self.value_node = node.value

    tree = ast.parse(name)
    name_visitor = ValueVisitor()
    name_visitor.visit(tree)
    return name_visitor.value_node

```

```py
print(astor.dump_tree(parse_type('int')))

```

```py
Name(id='int')

```

```py
print(astor.dump_tree(parse_type('[object]')))

```

```py
List(elts=[Name(id='object')])

```

现在，我们定义一个辅助函数，该辅助函数实际上将类型注释添加到函数AST中。 `TypeTransformer`类建立在Python标准库`ast.NodeTransformer`基础结构上。 它将被称为

```py
TypeTransformer({'x': 'int'}, 'float').visit(ast)

```

用`int`注释`my_sqrt()`：`x`的参数，并用`float`注释返回类型。 然后可以对返回的AST进行不分析，编译或分析。

```py
class TypeTransformer(ast.NodeTransformer):
    def __init__(self, argument_types, return_type=None):
        self.argument_types = argument_types
        self.return_type = return_type
        super().__init__()

```

`TypeTransformer`的核心是方法`visit_FunctionDef()`，对于AST中的每个函数定义都将调用该方法。 其参数`node`是要转换的函数定义的子树。 我们的实现访问各个参数，并在它们上调用`annotate_args()`； 它还在节点的`returns`属性中设置返回类型。

```py
class TypeTransformer(TypeTransformer):
    def visit_FunctionDef(self, node):
        """Add annotation to function"""
        # Set argument types
        new_args = []
        for arg in node.args.args:
            new_args.append(self.annotate_arg(arg))

        new_arguments = ast.arguments(
            new_args,
            node.args.vararg,
            node.args.kwonlyargs,
            node.args.kw_defaults,
            node.args.kwarg,
            node.args.defaults
        )

        # Set return type
        if self.return_type is not None:
            node.returns = parse_type(self.return_type)

        return ast.copy_location(ast.FunctionDef(node.name, new_arguments, 
                                                 node.body, node.decorator_list,
                                                 node.returns), node)

```

每个参数都有自己的注释，该注释取自最初传递给类的类型：

```py
class TypeTransformer(TypeTransformer):
    def annotate_arg(self, arg):
        """Add annotation to single function argument"""
        arg_name = arg.arg
        if arg_name in self.argument_types:
            arg.annotation = parse_type(self.argument_types[arg_name])
        return arg

```

这样行吗？ 让我们用`my_sqrt()`的AST注释参数和返回类型：

```py
new_ast = TypeTransformer({'x': 'int'}, 'float').visit(my_sqrt_ast)

```

当我们解析新的AST时，我们看到注释实际上存在：

```py
print_content(astor.to_source(new_ast), '.py')

```

```py
def my_sqrt(x: int) ->float:
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

```

同样，我们可以从上方注释`hello()`函数：

```py
hello_source = inspect.getsource(hello)

```

```py
hello_ast = ast.parse(hello_source)

```

```py
new_ast = TypeTransformer({'name': 'str'}, 'None').visit(hello_ast)

```

```py
print_content(astor.to_source(new_ast), '.py')

```

```py
def hello(name: str) ->None:
    print('Hello,', name)

```

### 使用挖掘类型注释函数

现在让我们用在运行时挖掘的类型注释函数。 我们从一个简单的函数`type_string()`开始，该函数确定给定值的适当类型（作为字符串）：

```py
def type_string(value):
    return type(value).__name__

```

```py
type_string(4)

```

```py
'int'

```

```py
type_string([])

```

```py
'list'

```

对于复合结构，`type_string()`不检查元素类型； 因此，`[3]`的类型只是`list`而不是`list[int]`。 目前，`list`会很好。

```py
type_string([3])

```

```py
'list'

```

`type_string()`将用于推断在运行时发现的参数值的类型，如`CallTracker.calls()`返回的那样：

```py
with CallTracker() as tracker:
    y = my_sqrt(25.0)
    y = my_sqrt(2.0)

```

```py
tracker.calls()

```

```py
{'my_sqrt': [([('x', 25.0)], 5.0), ([('x', 2.0)], 1.414213562373095)]}

```

函数`annotate_types()`接受这样的调用列表并注释列出的每个函数：

```py
def annotate_types(calls):
    annotated_functions = {}

    for function_name in calls:
        try:
            annotated_functions[function_name] = annotate_function_with_types(function_name, calls[function_name])
        except KeyError:
            continue

    return annotated_functions

```

对于每个函数，我们获取源代码及其AST，然后转到`annotate_function_ast_with_types()`中的实际注释：

```py
def annotate_function_with_types(function_name, function_calls):
    function = globals()[function_name]  # May raise KeyError for internal functions
    function_code = inspect.getsource(function)
    function_ast = ast.parse(function_code)
    return annotate_function_ast_with_types(function_ast, function_calls)

```

函数`annotate_function_ast_with_types()`使用看到的调用来调用`TypeTransformer`，并且对于每个调用，对参数进行迭代，确定其类型，并使用这些参数对AST进行注释。 当我们遇到类型冲突时，将使用通用类型`Any`，我们将在下面进行讨论。

```py
from [typing](https://docs.python.org/3/library/typing.html) import Any

```

```py
def annotate_function_ast_with_types(function_ast, function_calls):
    parameter_types = {}
    return_type = None

    for calls_seen in function_calls:
        args, return_value = calls_seen
        if return_value is not None:
            if return_type is not None and return_type != type_string(return_value):
                return_type = 'Any'
            else:
                return_type = type_string(return_value)

        for parameter, value in args:
            try:
                different_type = parameter_types[parameter] != type_string(value)
            except KeyError:
                different_type = False

            if different_type:
                parameter_types[parameter] = 'Any'
            else:
                parameter_types[parameter] = type_string(value)

    annotated_function_ast = TypeTransformer(parameter_types, return_type).visit(function_ast)
    return annotated_function_ast

```

这是`my_sqrt()`，上面用跟踪器记录的类型进行了注释。

```py
print_content(astor.to_source(annotate_types(tracker.calls())['my_sqrt']), '.py')

```

```py
def my_sqrt(x: float) ->float:
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

```

### 多合一注解

让我们将所有这些都放在一个单独的类`TypeAnnotator`中，该类首先跟踪函数的调用，然后允许访问带有类型注释的跟踪函数的AST（和源代码形式）。 方法`typed_functions()`以字符串形式返回带注释的函数； `typed_functions_ast()`返回其AST。

```py
class TypeTracker(CallTracker):
    pass

```

```py
class TypeAnnotator(TypeTracker):
    def typed_functions_ast(self, function_name=None):
        if function_name is None:
            return annotate_types(self.calls())

        return annotate_function_with_types(function_name, self.calls(function_name))

    def typed_functions(self, function_name=None):
        if function_name is None:
            functions = ''
            for f_name in self.calls():
                try:
                    f_text = astor.to_source(self.typed_functions_ast(f_name))
                except KeyError:
                    f_text = ''
                functions += f_text
            return functions

        return astor.to_source(self.typed_functions_ast(function_name))

```

这是使用`TypeAnnotator`的方法。 我们首先跟踪一系列呼叫：

```py
with TypeAnnotator() as annotator:
    y = my_sqrt(25.0)
    y = my_sqrt(2.0)

```

跟踪之后，我们可以立即获取跟踪功能的带注释的版本：

```py
print_content(annotator.typed_functions(), '.py')

```

```py
def my_sqrt(x: float) ->float:
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

```

这也适用于多种功能。 可以根据执行期间看到的类型，为Python文件实现自动类型注释器。

```py
with TypeAnnotator() as annotator:
    hello('type annotations')
    y = my_sqrt(1.0)

```

```py
Hello, type annotations

```

```py
print_content(annotator.typed_functions(), '.py')

```

```py
def hello(name: str):
    print('Hello,', name)
def my_sqrt(x: float) ->float:
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

```

现在可以将上述内容发送到类型检查器，该类型检查器将检测呼叫者和被呼叫者之间的任何类型不一致。 同样，上面的类型注释也极大地有利于符号代码分析（如[符号模糊化](SymbolicFuzzer.html)中的章节），因为它们有效地限制了参数和变量可以采用的值集。

### 多种类型

现在让我们解决魔术`Any`类型在`annotate_function_ast_with_types()`中的作用。 如果我们为同一个参数看到多种类型，则将其类型设置为`Any`。 对于`my_sqrt()`，这很有意义，因为其参数可以是整数也可以是浮点数：

```py
with CallTracker() as tracker:
    y = my_sqrt(25.0)
    y = my_sqrt(4)

```

```py
print_content(astor.to_source(annotate_types(tracker.calls())['my_sqrt']), '.py')

```

```py
def my_sqrt(x: Any) ->float:
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

```

可以使用浮点数作为参数来调用以下函数`sum3()`，导致参数获得`float`类型：

```py
def sum3(a, b, c):
    return a + b + c

```

```py
with TypeAnnotator() as annotator:
    y = sum3(1.0, 2.0, 3.0)
y

```

```py
6.0

```

```py
print_content(annotator.typed_functions(), '.py')

```

```py
def sum3(a: float, b: float, c: float) ->float:
    return a + b + c

```

但是，如果我们使用整数调用`sum3()`，则参数将获得`int`类型：

```py
with TypeAnnotator() as annotator:
    y = sum3(1, 2, 3)
y

```

```py
6

```

```py
print_content(annotator.typed_functions(), '.py')

```

```py
def sum3(a: int, b: int, c: int) ->int:
    return a + b + c

```

我们还可以使用字符串调用`sum3()`，为参数指定`str`类型：

```py
with TypeAnnotator() as annotator:
    y = sum3("one", "two", "three")
y

```

```py
'onetwothree'

```

```py
print_content(annotator.typed_functions(), '.py')

```

```py
def sum3(a: str, b: str, c: str) ->str:
    return a + b + c

```

如果我们有多个调用，但类型不同，则`TypeAnnotator()`将为参数和返回值分配`Any`类型：

```py
with TypeAnnotator() as annotator:
    y = sum3(1, 2, 3)
    y = sum3("one", "two", "three")

```

```py
typed_sum3_def = annotator.typed_functions('sum3')

```

```py
print_content(typed_sum3_def, '.py')

```

```py
def sum3(a: Any, b: Any, c: Any) ->Any:
    return a + b + c

```

类型`Any`明确表明对象可以具有任何类型。 它不会在运行时或静态地进行类型检查。 在某种程度上，这削弱了类型检查的能力。 但是它还保留了许多Python程序员喜欢的类型灵活性。 除了`Any`之外，`typing`模块还支持几种其他方式来定义模糊类型。 我们将牢记这一点，以便以后进行练习。

## 指定和检查不变量

除了基本数据类型。 我们可以从参数中检查其他几个属性。 例如，我们可以确定一个参数可以是负数，零还是正数。 或者一个论点应小于第二个论点； 或结果应为两个参数的总和–无法以（Python）类型表示的属性。

这样的属性称为*不变量*，因为它们在函数的所有调用中均有效。 具体来说，不变量出现在*之前*-和*后置条件*-始终在函数的开头和结尾处保持的条件。 （还有*数据*和*对象*不变量表示数据或对象状态的始终保持属性，但在本书中我们不予考虑。）

### 带前置条件和后置条件的函数注释

指定前置条件和后置条件的经典方法是通过*断言*，我们已在[关于测试](Intro_Testing.html)的章节中介绍了该断言。 前提条件检查函数的参数是否满足期望的属性； 后置条件的结果相同。 我们可以使用断言来表达和检查这两种方法，如下所示：

```py
def my_sqrt_with_invariants(x):
    assert x >= 0  # Precondition

    ...

    assert result * result == x  # Postcondition
    return result

```

但是，更好的方法是在语法上将不变式与当前函数分开。 使用适当的修饰符，我们可以指定前置条件和后置条件，如下所示：

```py
@precondition lambda x: x >= 0
@postcondition lambda return_value, x: return_value * return_value == x
def my_sqrt_with_invariants(x):
    # normal code without assertions
    ...

```

装饰器`@precondition`和`@postcondition`将分别在装饰的函数之前和之后运行给定的函数（指定为匿名`lambda`函数）。 如果函数返回`False`，则违反条件。 `@precondition`将函数参数作为参数； `@postcondition`另外将返回值作为第一个参数。

事实证明，实现这样的装饰器并不难。 我们的实现基于StackOverflow 的[代码段：](https://stackoverflow.com/questions/12151182/python-precondition-postcondition-for-member-function-how)

```py
import [functools](https://docs.python.org/3/library/functools.html)

```

```py
def condition(precondition=None, postcondition=None):
    def decorator(func):
        @functools.wraps(func) # preserves name, docstring, etc
        def wrapper(*args, **kwargs):
            if precondition is not None:
               assert precondition(*args, **kwargs), "Precondition violated"

            retval = func(*args, **kwargs) # call original function or method
            if postcondition is not None:
               assert postcondition(retval, *args, **kwargs), "Postcondition violated"

            return retval
        return wrapper
    return decorator

def precondition(check):
    return condition(precondition=check)

def postcondition(check):
    return condition(postcondition=check)

```

有了这些，我们现在可以开始装饰`my_sqrt()`了：

```py
@precondition(lambda x: x > 0)
def my_sqrt_with_precondition(x):
    return my_sqrt(x)

```

这引起了违反先决条件的论点：

```py
with ExpectError():
    my_sqrt_with_precondition(-1.0)

```

```py
Traceback (most recent call last):
  File "<ipython-input-102-c02dc99b6c54>", line 2, in <module>
    my_sqrt_with_precondition(-1.0)
  File "<ipython-input-100-39ada1fd0b7e>", line 6, in wrapper
    assert precondition(*args, **kwargs), "Precondition violated"
AssertionError: Precondition violated (expected)

```

同样，我们可以提供一个后置条件：

```py
EPSILON = 1e-5

```

```py
@postcondition(lambda ret, x: ret * ret - x < EPSILON)
def my_sqrt_with_postcondition(x):
    return my_sqrt(x)

```

```py
y = my_sqrt_with_postcondition(2.0)
y

```

```py
1.414213562373095

```

如果我们有一个错误的$ \ sqrt {x} $实现，则会很快被发现：

```py
@postcondition(lambda ret, x: ret * ret - x < EPSILON)
def buggy_my_sqrt_with_postcondition(x):
    return my_sqrt(x) + 0.1

```

```py
with ExpectError():
    y = buggy_my_sqrt_with_postcondition(2.0)

```

```py
Traceback (most recent call last):
  File "<ipython-input-107-38a36260c5b6>", line 2, in <module>
    y = buggy_my_sqrt_with_postcondition(2.0)
  File "<ipython-input-100-39ada1fd0b7e>", line 10, in wrapper
    assert postcondition(retval, *args, **kwargs), "Postcondition violated"
AssertionError: Postcondition violated (expected)

```

虽然检查前置条件和后置条件是捕获错误的好方法，但是指定它们可能很麻烦。 让我们尝试看看是否可以（再次）*挖掘*其中的一些。

## 挖掘不变量

为了*挖掘*不变量，我们可以使用与以前相同的跟踪功能； 但是，我们现在检查值是否满足特定的*属性*，而不是保存单个变量的值。 例如，如果看到的`x`的所有值都满足条件`x > 0`，则使`x > 0`成为函数的不变式。 但是，如果我们看到`x`的正值，零值和负值，那么`x`的属性就不用说了。

因此，总体思路是：

1.  根据一组预定义的属性检查观察到的所有变量值； 和
2.  仅保留那些对于所有观察到的运行均适用的属性。

### 定义属性

属性到底是什么意思？ 这是价值属性的一小部分集合，经常在不变式中使用。 所有这些属性都将通过将*元变量* `X`，`Y`和`Z`（实际上是任何大写标识符）替换为功能参数名称来进行评估：

```py
INVARIANT_PROPERTIES = [
    "X < 0",
    "X <= 0",
    "X > 0",
    "X >= 0",
    "X == 0",
    "X != 0",
]

```

当`my_sqrt(x)`称为`my_sqrt(5.0)`时，我们看到`x = 5.0`成立。 然后将检查以上所有属性的`x`。 仅属性`X > 0`，`X >= 0`和`X != 0`适用于所看到的呼叫； 因此`x > 0`，`x >= 0`和`x != 0`将成为`my_sqrt(x)`的潜在前提。

我们可以检查更多的属性，例如两个参数之间的关系：

```py
INVARIANT_PROPERTIES += [
    "X == Y",
    "X > Y",
    "X < Y",
    "X >= Y",
    "X <= Y",
]

```

也可以使用属性检查类型。 对于任何功能参数`X`，其中只有一个将保持：

```py
INVARIANT_PROPERTIES += [
    "isinstance(X, bool)",
    "isinstance(X, int)",
    "isinstance(X, float)",
    "isinstance(X, list)",
    "isinstance(X, dict)",
]

```

我们可以检查算术属性：

```py
INVARIANT_PROPERTIES += [
    "X == Y + Z",
    "X == Y * Z",
    "X == Y - Z",
    "X == Y / Z",
]

```

这是三个值之间的关系，这是Python的特殊功能：

```py
INVARIANT_PROPERTIES += [
    "X < Y < Z",
    "X <= Y <= Z",
    "X > Y > Z",
    "X >= Y >= Z",
]

```

最后，我们还可以检查列表或字符串属性。 同样，这只是一个很小的选择。

```py
INVARIANT_PROPERTIES += [
    "X == len(Y)",
    "X == sum(Y)",
    "X.startswith(Y)",
]

```

### 提取元变量

在开始实际挖掘之前，让我们首先介绍一些*辅助函数*。 `metavars()`从属性中提取一组元变量（`X`，`Y`，`Z`等）。 为此，我们将属性解析为Python表达式，然后访问标识符。

```py
def metavars(prop):
    metavar_list = []

    class ArgVisitor(ast.NodeVisitor):
        def visit_Name(self, node):
            if node.id.isupper():
                metavar_list.append(node.id)

    ArgVisitor().visit(ast.parse(prop))
    return metavar_list

```

```py
assert metavars("X < 0") == ['X']

```

```py
assert metavars("X.startswith(Y)") == ['X', 'Y']

```

```py
assert metavars("isinstance(X, str)") == ['X']

```

### 实例化属性

为了产生不变的属性，我们需要能够使用变量名将其*实例化。 例如，将`X`实例化为`a`的`X > 0`实例化为`a > 0`。 为此，函数`instantiate_prop()`具有属性和变量名的集合，并使用该集合中的相应变量名从左到右实例化元变量。*

```py
def instantiate_prop_ast(prop, var_names):
    class NameTransformer(ast.NodeTransformer):
        def visit_Name(self, node):
            if node.id not in mapping:
                return node
            return ast.Name(id=mapping[node.id], ctx=ast.Load())

    meta_variables = metavars(prop)
    assert len(meta_variables) == len(var_names)

    mapping = {}
    for i in range(0, len(meta_variables)):
        mapping[meta_variables[i]] = var_names[i]

    prop_ast = ast.parse(prop, mode='eval')
    new_ast = NameTransformer().visit(prop_ast)

    return new_ast

```

```py
def instantiate_prop(prop, var_names):
    prop_ast = instantiate_prop_ast(prop, var_names)
    prop_text = astor.to_source(prop_ast).strip()
    while prop_text.startswith('(') and prop_text.endswith(')'):
        prop_text = prop_text[1:-1]
    return prop_text

```

```py
assert instantiate_prop("X > Y", ['a', 'b']) == 'a > b'

```

```py
assert instantiate_prop("X.startswith(Y)", ['x', 'y']) == 'x.startswith(y)'

```

### 评估属性

要实际*评估*属性，我们不需要实例化它们。 相反，我们只需要使用`lambda`将它们转换为布尔函数：

```py
def prop_function_text(prop):
    return "lambda " + ", ".join(metavars(prop)) + ": " + prop

def prop_function(prop):
    return eval(prop_function_text(prop))

```

这是一个简单的示例：

```py
prop_function_text("X > Y")

```

```py
'lambda X, Y: X > Y'

```

```py
p = prop_function("X > Y")
p(100, 1)

```

```py
True

```

```py
p(1, 100)

```

```py
False

```

### 检查不变量

为了从执行中提取不变量，我们需要检查所有可能的参数实例化。 如果要检查的函数具有两个参数`a`和`b`，我们将属性`X < Y`实例化为`a < b`和`b < a`并分别检查它们。

要获得所有组合，我们使用Python `permutations()`函数：

```py
import [itertools](https://docs.python.org/3/library/itertools.html)

```

```py
for combination in itertools.permutations([1.0, 2.0, 3.0], 2):
    print(combination)

```

```py
(1.0, 2.0)
(1.0, 3.0)
(2.0, 1.0)
(2.0, 3.0)
(3.0, 1.0)
(3.0, 2.0)

```

函数`true_property_instantiations()`具有一个属性和一个元组列表（`var_name`，`value`）。 然后，它将使用给定的值生成该属性的所有实例，并返回评估为True的实例。

```py
def true_property_instantiations(prop, vars_and_values, log=False):
    instantiations = set()
    p = prop_function(prop)

    len_metavars = len(metavars(prop))
    for combination in itertools.permutations(vars_and_values, len_metavars):
        args = [value for var_name, value in combination]
        var_names = [var_name for var_name, value in combination]

        try:
            result = p(*args)
        except:
            result = None

        if log:
            print(prop, combination, result)
        if result:
            instantiations.add((prop, tuple(var_names)))

    return instantiations

```

这是一个例子。 如果`x == -1`和`y == 1`的属性`X < Y`对于`x < y`成立，但对`y < x`不成立：

```py
invs = true_property_instantiations("X < Y", [('x', -1), ('y', 1)], log=True)
invs

```

```py
X < Y (('x', -1), ('y', 1)) True
X < Y (('y', 1), ('x', -1)) False

```

```py
{('X < Y', ('x', 'y'))}

```

实例化检索缩写形式：

```py
for prop, var_names in invs:
    print(instantiate_prop(prop, var_names))

```

```py
x < y

```

同样，对于上述`x`和`y`的值，属性`X < 0`仅适用于`x`，而不适用于`y`：

```py
invs = true_property_instantiations("X < 0", [('x', -1), ('y', 1)], log=True)

```

```py
X < 0 (('x', -1),) True
X < 0 (('y', 1),) False

```

```py
for prop, var_names in invs:
    print(instantiate_prop(prop, var_names))

```

```py
x < 0

```

### 提取不变量

现在让我们对函数参数进行上述不变提取，并返回在函数执行期间观察到的值。 为此，我们将`CallTracker`类扩展为`InvariantTracker`类，该类自动为跟踪期间观察到的所有函数和所有调用计算不变量。

默认情况下，`InvariantTracker`使用上面定义的属性。 但是，可以指定一组备用属性。

```py
class InvariantTracker(CallTracker):
    def __init__(self, props=None, **kwargs):
        if props is None:
            props = INVARIANT_PROPERTIES

        self.props = props
        super().__init__(**kwargs)

```

`InvariantTracker`的关键方法是`invariants()`方法。 这将遍历观察到的调用并检查哪些属性成立。 仅保留属性的交集-即对所有调用均适用的属性集-并最终将其返回。 特殊变量`return_value`设置为保留返回值。

```py
RETURN_VALUE = 'return_value'

```

```py
class InvariantTracker(InvariantTracker):
    def invariants(self, function_name=None):
        if function_name is None:
            return {function_name: self.invariants(function_name) for function_name in self.calls()}

        invariants = None
        for variables, return_value in self.calls(function_name):
            vars_and_values = variables + [(RETURN_VALUE, return_value)]

            s = set()
            for prop in self.props:
                s |= true_property_instantiations(prop, vars_and_values, self._log)
            if invariants is None:
                invariants = s
            else:
                invariants &= s

        return invariants

```

这是如何使用`invariants()`的示例。 我们通过一小组电话来运行跟踪器。

```py
with InvariantTracker() as tracker:
    y = my_sqrt(25.0)
    y = my_sqrt(10.0)

tracker.calls()

```

```py
{'my_sqrt': [([('x', 25.0)], 5.0), ([('x', 10.0)], 3.162277660168379)]}

```

`invariants()`方法产生一组可保留观察到的运行的属性，以及它们在函数参数上的实例化。

```py
invs = tracker.invariants('my_sqrt')
invs

```

```py
{('X != 0', ('return_value',)),
 ('X != 0', ('x',)),
 ('X < Y', ('return_value', 'x')),
 ('X <= Y', ('return_value', 'x')),
 ('X > 0', ('return_value',)),
 ('X > 0', ('x',)),
 ('X > Y', ('x', 'return_value')),
 ('X >= 0', ('return_value',)),
 ('X >= 0', ('x',)),
 ('X >= Y', ('x', 'return_value')),
 ('isinstance(X, float)', ('return_value',)),
 ('isinstance(X, float)', ('x',))}

```

和以前一样，实际的实例更易于阅读：

```py
def pretty_invariants(invariants):
    props = []
    for (prop, var_names) in invariants:
        props.append(instantiate_prop(prop, var_names))
    return sorted(props)

```

```py
pretty_invariants(invs)

```

```py
['isinstance(return_value, float)',
 'isinstance(x, float)',
 'return_value != 0',
 'return_value < x',
 'return_value <= x',
 'return_value > 0',
 'return_value >= 0',
 'x != 0',
 'x > 0',
 'x > return_value',
 'x >= 0',
 'x >= return_value']

```

我们看到`x`和返回值都具有`float`类型。 我们还看到两者总是大于零。 这些属性可能会成为有用的前提条件和后置条件，尤其是对于符号分析而言。

但是，还有一个不变式*而不是*普遍成立，即`return_value <= x`，如以下示例所示：

```py
my_sqrt(0.01)

```

```py
0.1

```

显然，0.1 > 0.01成立。 这是我们没有从足够多样化的输入中学习的情况。 一旦有了包含`x = 0.1`的呼叫，不变式`return_value <= x`就被消除：

```py
with InvariantTracker() as tracker:
    y = my_sqrt(25.0)
    y = my_sqrt(10.0)
    y = my_sqrt(0.01)

pretty_invariants(tracker.invariants('my_sqrt'))

```

```py
['isinstance(return_value, float)',
 'isinstance(x, float)',
 'return_value != 0',
 'return_value > 0',
 'return_value >= 0',
 'x != 0',
 'x > 0',
 'x >= 0']

```

稍后我们将讨论如何确保输入的足够多样性。 （提示：这涉及测试生成。）

让我们在`sum3()`上试用我们的不变跟踪器。 我们看到所有类型都是定义明确的。 所有参数都不为零的属性是特定于观察到的调用的。

```py
with InvariantTracker() as tracker:
    y = sum3(1, 2, 3)
    y = sum3(-4, -5, -6)

pretty_invariants(tracker.invariants('sum3'))

```

```py
['a != 0',
 'b != 0',
 'c != 0',
 'isinstance(a, int)',
 'isinstance(b, int)',
 'isinstance(c, int)',
 'isinstance(return_value, int)',
 'return_value != 0']

```

如果我们改为使用字符串调用`sum3()`，则会得到不同的不变量。 值得注意的是，我们获得了后置条件，即返回值以`a`的值开头-如果使用字符串，则为通用后置条件。

```py
with InvariantTracker() as tracker:
    y = sum3('a', 'b', 'c')
    y = sum3('f', 'e', 'd')

pretty_invariants(tracker.invariants('sum3'))

```

```py
['a != 0',
 'a < return_value',
 'a <= return_value',
 'b != 0',
 'c != 0',
 'return_value != 0',
 'return_value > a',
 'return_value >= a',
 'return_value.startswith(a)']

```

如果我们同时使用字符串和数字（​​也为零）来调用`sum3()`，那么将没有所有调用都可以保留的属性。 这就是灵活性的代价。

```py
with InvariantTracker() as tracker:
    y = sum3('a', 'b', 'c')
    y = sum3('c', 'b', 'a')
    y = sum3(-4, -5, -6)
    y = sum3(0, 0, 0)

pretty_invariants(tracker.invariants('sum3'))

```

```py
[]

```

### 将挖掘的不变量转换为注释

与上面的类型一样，我们希望具有一些功能，可以在其中添加挖掘的不变量作为对现有函数的注释。 为此，我们引入`InvariantAnnotator`类，扩展了`InvariantTracker`。

我们从一个辅助方法开始。 `params()`返回以逗号分隔的参数名称列表，如在调用过程中所观察到的。

```py
class InvariantAnnotator(InvariantTracker):
    def params(self, function_name):
        arguments, return_value = self.calls(function_name)[0]
        return ", ".join(arg_name for (arg_name, arg_value) in arguments)

```

```py
with InvariantAnnotator() as annotator:
    y = my_sqrt(25.0)
    y = sum3(1, 2, 3)

```

```py
annotator.params('my_sqrt')

```

```py
'x'

```

```py
annotator.params('sum3')

```

```py
'a, b, c'

```

现在进行实际注释。 `preconditions()`从已挖掘的不变量（即不依赖于返回值的属性）作为带有注释的字符串返回前提条件：

```py
class InvariantAnnotator(InvariantAnnotator):
    def preconditions(self, function_name):
        conditions = []

        for inv in pretty_invariants(self.invariants(function_name)):
            if inv.find(RETURN_VALUE) >= 0:
                continue  # Postcondition

            cond = "@precondition(lambda " + self.params(function_name) + ": " + inv + ")"
            conditions.append(cond)

        return conditions

```

```py
with InvariantAnnotator() as annotator:
    y = my_sqrt(25.0)
    y = my_sqrt(0.01)
    y = sum3(1, 2, 3)

```

```py
annotator.preconditions('my_sqrt')

```

```py
['@precondition(lambda x: isinstance(x, float))',
 '@precondition(lambda x: x != 0)',
 '@precondition(lambda x: x > 0)',
 '@precondition(lambda x: x >= 0)']

```

`postconditions()`对后置条件执行相同的操作：

```py
class InvariantAnnotator(InvariantAnnotator):
    def postconditions(self, function_name):
        conditions = []

        for inv in pretty_invariants(self.invariants(function_name)):
            if inv.find(RETURN_VALUE) < 0:
                continue  # Precondition

            cond = ("@postcondition(lambda " + 
                RETURN_VALUE + ", " + self.params(function_name) + ": " + inv + ")")
            conditions.append(cond)

        return conditions

```

```py
with InvariantAnnotator() as annotator:
    y = my_sqrt(25.0)
    y = my_sqrt(0.01)
    y = sum3(1, 2, 3)

```

```py
annotator.postconditions('my_sqrt')

```

```py
['@postcondition(lambda return_value, x: isinstance(return_value, float))',
 '@postcondition(lambda return_value, x: return_value != 0)',
 '@postcondition(lambda return_value, x: return_value > 0)',
 '@postcondition(lambda return_value, x: return_value >= 0)']

```

有了这些，我们可以采用一个函数并将前置条件和后置条件都添加为注释：

```py
class InvariantAnnotator(InvariantAnnotator):
    def functions_with_invariants(self):
        functions = ""
        for function_name in self.invariants():
            try:
                function = self.function_with_invariants(function_name)
            except KeyError:
                continue
            functions += function
        return functions

    def function_with_invariants(self, function_name):
        function = globals()[function_name]  # Can throw KeyError
        source = inspect.getsource(function)
        return "\n".join(self.preconditions(function_name) + 
                         self.postconditions(function_name)) + '\n' + source

```

荣耀`function_with_invariants()`来了：

```py
with InvariantAnnotator() as annotator:
    y = my_sqrt(25.0)
    y = my_sqrt(0.01)
    y = sum3(1, 2, 3)

```

```py
print_content(annotator.function_with_invariants('my_sqrt'), '.py')

```

```py
@precondition(lambda x: isinstance(x, float))
@precondition(lambda x: x != 0)
@precondition(lambda x: x > 0)
@precondition(lambda x: x >= 0)
@postcondition(lambda return_value, x: isinstance(return_value, float))
@postcondition(lambda return_value, x: return_value != 0)
@postcondition(lambda return_value, x: return_value > 0)
@postcondition(lambda return_value, x: return_value >= 0)
def my_sqrt(x):
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

```

很多不变式，是吗？ 在下面的内容（以及练习中）中，我们将讨论如何重点关注最相关的属性。

### 一些示例

这是另一个例子。 `list_length()`递归计算Python函数的长度。 让我们看看是否可以挖掘其不变式：

```py
def list_length(L):
    if L == []:
        length = 0
    else:
        length = 1 + list_length(L[1:])
    return length

```

```py
with InvariantAnnotator() as annotator:
    length = list_length([1, 2, 3])

print_content(annotator.functions_with_invariants(), '.py')

```

```py
@precondition(lambda L: L != 0)
@precondition(lambda L: isinstance(L, list))
@postcondition(lambda return_value, L: isinstance(return_value, int))
@postcondition(lambda return_value, L: return_value == len(L))
@postcondition(lambda return_value, L: return_value >= 0)
def list_length(L):
    if L == []:
        length = 0
    else:
        length = 1 + list_length(L[1:])
    return length

```

几乎所有这些属性（除了第一个属性）都是相关的。 当然，不变量如此整洁的原因是返回值等于`len(L)`，这是因为`X == len(Y)`是要检查的属性列表的一部分。

下一个示例是一个非常简单的函数：

```py
def sum2(a, b):
    return a + b

```

```py
with InvariantAnnotator() as annotator:
    sum2(31, 45)
    sum2(0, 0)
    sum2(-1, -5)

```

不变量全部捕获了`a`，`b`和返回值`return_value == a + b`之间的关系。

```py
print_content(annotator.functions_with_invariants(), '.py')

```

```py
@precondition(lambda a, b: isinstance(a, int))
@precondition(lambda a, b: isinstance(b, int))
@postcondition(lambda return_value, a, b: a == return_value - b)
@postcondition(lambda return_value, a, b: b == return_value - a)
@postcondition(lambda return_value, a, b: isinstance(return_value, int))
@postcondition(lambda return_value, a, b: return_value == a + b)
@postcondition(lambda return_value, a, b: return_value == b + a)
def sum2(a, b):
    return a + b

```

如果我们有一个没有返回值的函数，则返回值为`None`，我们只能挖掘前提条件。 （好吧，我们得到了一个“后置条件”，即返回值不为零，这对于`None`成立）。

```py
def print_sum(a, b):
    print(a + b)

```

```py
with InvariantAnnotator() as annotator:
    print_sum(31, 45)
    print_sum(0, 0)
    print_sum(-1, -5)

```

```py
76
0
-6

```

```py
print_content(annotator.functions_with_invariants(), '.py')

```

```py
@precondition(lambda a, b: isinstance(a, int))
@precondition(lambda a, b: isinstance(b, int))
@postcondition(lambda return_value, a, b: return_value != 0)
def print_sum(a, b):
    print(a + b)

```

### 检查规格

如上所述，具有不变性的函数可以输入Python解释器中，以便检查所有前置条件和后置条件。 我们创建一个函数`my_sqrt_annotated()`，其中包含上面挖掘的所有不变量。

```py
with InvariantAnnotator() as annotator:
    y = my_sqrt(25.0)
    y = my_sqrt(0.01)

```

```py
my_sqrt_def = annotator.functions_with_invariants()
my_sqrt_def = my_sqrt_def.replace('my_sqrt', 'my_sqrt_annotated')

```

```py
print_content(my_sqrt_def, '.py')

```

```py
@precondition(lambda x: isinstance(x, float))
@precondition(lambda x: x != 0)
@precondition(lambda x: x > 0)
@precondition(lambda x: x >= 0)
@postcondition(lambda return_value, x: isinstance(return_value, float))
@postcondition(lambda return_value, x: return_value != 0)
@postcondition(lambda return_value, x: return_value > 0)
@postcondition(lambda return_value, x: return_value >= 0)
def my_sqrt_annotated(x):
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return approx

```

```py
exec(my_sqrt_def)

```

带注释的版本将检查无效的参数-更确切地说，将检查具有尚未被观察到的属性的参数：

```py
with ExpectError():
    my_sqrt_annotated(-1.0)

```

```py
Traceback (most recent call last):
  File "<ipython-input-170-c3c5c372ccd1>", line 2, in <module>
    my_sqrt_annotated(-1.0)
  File "<ipython-input-100-39ada1fd0b7e>", line 8, in wrapper
    retval = func(*args, **kwargs) # call original function or method
  File "<ipython-input-100-39ada1fd0b7e>", line 8, in wrapper
    retval = func(*args, **kwargs) # call original function or method
  File "<ipython-input-100-39ada1fd0b7e>", line 6, in wrapper
    assert precondition(*args, **kwargs), "Precondition violated"
AssertionError: Precondition violated (expected)

```

这与原始版本相反，原始版本仅挂在负值上：

```py
with ExpectTimeout(1):
    my_sqrt(-1.0)

```

```py
Traceback (most recent call last):
  File "<ipython-input-171-afc7add26ad6>", line 2, in <module>
    my_sqrt(-1.0)
  File "<ipython-input-5-47185ad159a1>", line 7, in my_sqrt
    guess = (approx + x / approx) / 2
  File "<ipython-input-5-47185ad159a1>", line 7, in my_sqrt
    guess = (approx + x / approx) / 2
  File "<string>", line 16, in check_time
TimeoutError (expected)

```

如果我们对函数定义进行更改，以使返回值的属性发生更改，则*回归*被视为违反了后置条件。 让我们通过简单地反转结果来说明这一点，并将$ -2 $作为4的平方根返回。

```py
my_sqrt_def = my_sqrt_def.replace('my_sqrt_annotated', 'my_sqrt_negative')
my_sqrt_def = my_sqrt_def.replace('return approx', 'return -approx')

```

```py
print_content(my_sqrt_def, '.py')

```

```py
@precondition(lambda x: isinstance(x, float))
@precondition(lambda x: x != 0)
@precondition(lambda x: x > 0)
@precondition(lambda x: x >= 0)
@postcondition(lambda return_value, x: isinstance(return_value, float))
@postcondition(lambda return_value, x: return_value != 0)
@postcondition(lambda return_value, x: return_value > 0)
@postcondition(lambda return_value, x: return_value >= 0)
def my_sqrt_negative(x):
    """Computes the square root of x, using the Newton-Raphson method"""
    approx = None
    guess = x / 2
    while approx != guess:
        approx = guess
        guess = (approx + x / approx) / 2
    return -approx

```

```py
exec(my_sqrt_def)

```

从技术上讲，$-2 $ *是*的平方根4，因为$（-2）^ 2 = 4 $成立。 但是，`my_sqrt()`的调用者可能无法预料到这样的变化，因此，第一次调用将被捕获：

```py
with ExpectError():
    my_sqrt_negative(2.0)

```

```py
Traceback (most recent call last):
  File "<ipython-input-175-c80e4295dbf8>", line 2, in <module>
    my_sqrt_negative(2.0)
  File "<ipython-input-100-39ada1fd0b7e>", line 8, in wrapper
    retval = func(*args, **kwargs) # call original function or method
  File "<ipython-input-100-39ada1fd0b7e>", line 8, in wrapper
    retval = func(*args, **kwargs) # call original function or method
  File "<ipython-input-100-39ada1fd0b7e>", line 8, in wrapper
    retval = func(*args, **kwargs) # call original function or method
  [Previous line repeated 4 more times]
  File "<ipython-input-100-39ada1fd0b7e>", line 10, in wrapper
    assert postcondition(retval, *args, **kwargs), "Postcondition violated"
AssertionError: Postcondition violated (expected)

```

我们看到前置条件和后置条件以及类型如何在测试过程中充当 *oracles* 。 特别是，一旦我们为一组功能挖掘了它们，就可以使用测试生成器一次又一次地检查它们-特别是在代码更改之后。 我们拥有的检查越多，检查越具体，我们越有可能检测到不必要的变更影响。

## 生成的测试的挖掘规范

开采的规格只能与开采的规格一样好。 如果我们仅看到一个调用，例如`sum2()`，我们将面临几个挖掘的前提条件和后置条件，这些条件使*过度专业化*到所看到的值：

```py
def sum2(a, b):
    return a + b

```

```py
with InvariantAnnotator() as annotator:
    y = sum2(2, 2)
print_content(annotator.functions_with_invariants(), '.py')

```

```py
@precondition(lambda a, b: a != 0)
@precondition(lambda a, b: a <= b)
@precondition(lambda a, b: a == b)
@precondition(lambda a, b: a > 0)
@precondition(lambda a, b: a >= 0)
@precondition(lambda a, b: a >= b)
@precondition(lambda a, b: b != 0)
@precondition(lambda a, b: b <= a)
@precondition(lambda a, b: b == a)
@precondition(lambda a, b: b > 0)
@precondition(lambda a, b: b >= 0)
@precondition(lambda a, b: b >= a)
@precondition(lambda a, b: isinstance(a, int))
@precondition(lambda a, b: isinstance(b, int))
@postcondition(lambda return_value, a, b: a < return_value)
@postcondition(lambda return_value, a, b: a <= b <= return_value)
@postcondition(lambda return_value, a, b: a <= return_value)
@postcondition(lambda return_value, a, b: a == return_value - b)
@postcondition(lambda return_value, a, b: a == return_value / b)
@postcondition(lambda return_value, a, b: b < return_value)
@postcondition(lambda return_value, a, b: b <= a <= return_value)
@postcondition(lambda return_value, a, b: b <= return_value)
@postcondition(lambda return_value, a, b: b == return_value - a)
@postcondition(lambda return_value, a, b: b == return_value / a)
@postcondition(lambda return_value, a, b: isinstance(return_value, int))
@postcondition(lambda return_value, a, b: return_value != 0)
@postcondition(lambda return_value, a, b: return_value == a * b)
@postcondition(lambda return_value, a, b: return_value == a + b)
@postcondition(lambda return_value, a, b: return_value == b * a)
@postcondition(lambda return_value, a, b: return_value == b + a)
@postcondition(lambda return_value, a, b: return_value > 0)
@postcondition(lambda return_value, a, b: return_value > a)
@postcondition(lambda return_value, a, b: return_value > b)
@postcondition(lambda return_value, a, b: return_value >= 0)
@postcondition(lambda return_value, a, b: return_value >= a)
@postcondition(lambda return_value, a, b: return_value >= a >= b)
@postcondition(lambda return_value, a, b: return_value >= b)
@postcondition(lambda return_value, a, b: return_value >= b >= a)
def sum2(a, b):
    return a + b

```

例如，挖掘的前提条件`a == b`仅适用于观察到的单个调用； 开采后置条件`return_value == a * b`也是一样。 但是，`sum2()`显然可以使用不满足这些条件的其他值成功调用。

为了摆脱这个陷阱，我们必须*从越来越多的运行*中学习。 如果再调用`sum2()`，我们将看到不变量集如何快速变小：

```py
with InvariantAnnotator() as annotator:
    length = sum2(1, 2)
    length = sum2(-1, -2)
    length = sum2(0, 0)

print_content(annotator.functions_with_invariants(), '.py')

```

```py
@precondition(lambda a, b: isinstance(a, int))
@precondition(lambda a, b: isinstance(b, int))
@postcondition(lambda return_value, a, b: a == return_value - b)
@postcondition(lambda return_value, a, b: b == return_value - a)
@postcondition(lambda return_value, a, b: isinstance(return_value, int))
@postcondition(lambda return_value, a, b: return_value == a + b)
@postcondition(lambda return_value, a, b: return_value == b + a)
def sum2(a, b):
    return a + b

```

但是，我们从何处获得如此多样化的运行呢？ 这是生成软件测试的工作。 调用`sum2()`的简单语法将轻松解决该问题。

```py
from [GrammarFuzzer](GrammarFuzzer.html) import GrammarFuzzer  # minor dependency
from [Grammars](Grammars.html) import is_valid_grammar, crange, convert_ebnf_grammar  # minor dependency

```

```py
SUM2_EBNF_GRAMMAR = {
    "<start>": ["<sum2>"],
    "<sum2>": ["sum2(<int>, <int>)"],
    "<int>": ["<_int>"],
    "<_int>": ["(-)?<leaddigit><digit>*", "0"],
    "<leaddigit>": crange('1', '9'),
    "<digit>": crange('0', '9')
}

assert is_valid_grammar(SUM2_EBNF_GRAMMAR)

```

```py
sum2_grammar =  convert_ebnf_grammar(SUM2_EBNF_GRAMMAR)

```

```py
sum2_fuzzer = GrammarFuzzer(sum2_grammar)
[sum2_fuzzer.fuzz() for i in range(10)]

```

```py
['sum2(60, 3)',
 'sum2(-4, 0)',
 'sum2(-579, 34)',
 'sum2(3, 0)',
 'sum2(-8, 0)',
 'sum2(0, 8)',
 'sum2(3, -9)',
 'sum2(0, 0)',
 'sum2(0, 5)',
 'sum2(-3181, 0)']

```

```py
with InvariantAnnotator() as annotator:
    for i in range(10):
        eval(sum2_fuzzer.fuzz())

print_content(annotator.function_with_invariants('sum2'), '.py')

```

```py
@precondition(lambda a, b: a != 0)
@precondition(lambda a, b: isinstance(a, int))
@precondition(lambda a, b: isinstance(b, int))
@postcondition(lambda return_value, a, b: a == return_value - b)
@postcondition(lambda return_value, a, b: b == return_value - a)
@postcondition(lambda return_value, a, b: isinstance(return_value, int))
@postcondition(lambda return_value, a, b: return_value != 0)
@postcondition(lambda return_value, a, b: return_value == a + b)
@postcondition(lambda return_value, a, b: return_value == b + a)
def sum2(a, b):
    return a + b

```

但是，然后仅编写测试（或测试驱动程序）以得出一组前提条件和后置条件可能会花费太多精力–尤其是因为首先可以轻松地从给定的前提条件和后置条件中得出测试。 因此，首先指定不变式，然后让测试生成器或程序证明者来完成这项工作将是更明智的选择。

同样，必须设置如上所述的API语法，使其实际上遵守前提条件-在我们的情况下，我们已经假设其前提条件为仅使用正数调用`sqrt()`。 因此，以某种方式，一个人需要一个规范（模型，语法）来挖掘另一个规范-鸡与蛋的问题。

但是，有一种方法可以解决此问题：如果可以在系统级别自动生成测试，则可以使用*无限执行源*来学习不变式。 在这些执行中的每一个中，将使用满足（隐式）前提的值来调用所有函数，从而使我们能够为这些函数挖掘不变量。 之所以成立，是因为在系统级别上，无效输入必须首先被系统拒绝。 系统级别的有意义的前提条件，确保只有有效的输入才能通过，因此在功能级别分解为多个有意义的前提条件（以及后续的后置条件）。

但是，对此的最大要求是，需要在系统级别使用良好的测试生成器。 在[的下一部分](05_Domain-Specific_Fuzzing.ipynb)中，我们将讨论如何自动生成从配置到图形用户界面的各种域的测试。

## 经验教训

*   类型注释和显式不变量允许*检查*参数和结果以获取预期的数据类型和其他属性。
*   通过在运行时观察参数和结果，可以自动*挖掘*数据类型和不变量。
*   挖掘的不变量的质量取决于执行过程中观察到的值的多样性。 可以通过生成测试来增加这种多样性。

## 后续步骤

本章总结了语义模糊技术的[部分。 在下一部分中，我们将探讨从配置和API到图形用户界面的](04_Semantical_Fuzzing.html)[特定于域的模糊技术](05_Domain-Specific_Fuzzing.ipynb)。

## 背景

[DAIKON动态不变检测器](https://plse.cs.washington.edu/daikon/)可被视为功能规范矿工的母亲。 连续维护和扩展了20多年，它以本章的样式挖掘各种语言的可能不变式，包括C，C ++，C＃，Eiffel，F＃，Java，Perl和Visual Basic。 除了上面讨论的功能之外，它还为可能的不变量提供了丰富的模式目录，支持数据不变量，可以消除其他隐含的不变量，并确定统计可信度以忽略不太可能的不变量。 相应的论文[ [Ernst *等*，2001年。](https://doi.org/10.1109/32.908957)]是软件工程领域最引人注目的论文之一。 基于DAIKON并检测不变量的大量著作已经出版。 有关详细信息，请参见此[策划列表](http://plse.cs.washington.edu/daikon/pubs/)。

在[ [Ernst *等人*，2001。](https://doi.org/10.1109/32.908957)]中已经讨论了测试生成器与不变检测之间的交互作用（顺便说一句，也使用语法）。 Eclat工具[ [Pacheco *等*，2005\.](https://doi.org/10.1007/11531142_22) ]是单元级测试生成器与DAIKON式不变挖掘之间紧密交互的模型示例，其中使用了挖掘的不变式 产生预言，并系统地指导测试生成器进行故障显示输入。

采矿规范不限于前提条件和后置条件。 论文“采矿规范” [ [Ammons *等人*，2002。](https://doi.org/10.1145/503272.503275)]是该领域的另一经典，可以从执行中学习状态协议。 如本章[所述，同名](GrammarMiner.html)的语法挖掘也可以看作是一种规范挖掘方法，这一次是学习输入格式的规范。

关于在现有代码中添加类型注释，博客文章[“ Python中类型提示的状态”](https://www.bernat.tech/the-state-of-type-hints-in-python/) 很好地概述了如何使用和检查Python类型提示。 要添加类型注释，有两种重要的工具也可以实现我们的上述方法：

*   [MonkeyType](https://instagram-engineering.com/let-your-code-type-hint-itself-introducing-open-source-monkeytype-a855c7284881) 实现了上述跟踪执行并为Python 3自变量，返回值和带有类型提示的变量进行注释的方法。
*   [PyAnnotate](https://github.com/dropbox/pyannotate) 做类似的工作，专注于Python 2中的代码。它不产生Python 3样式的注释，而是产生注释，这些注释可以由静态类型检查器处理。

这些工具分别由Facebook和Dropbox的工程师创建，可以帮助他们检查数百万行代码中是否存在类型问题。

## 练习

我们关于挖掘类型和不变量的代码绝不完整。 扩展实现的方法有很多种，其中一些是在练习中讨论的。

### 练习1：联合类型

Python `typing`模块允许表达一个参数可以具有多种类型。 对于`my_sqrt(x)`，这可以表示`x`可以是`int`或`float`：

```py
from [typing](https://docs.python.org/3/library/typing.html) import Union, Optional

```

```py
def my_sqrt_with_union_type(x: Union[int, float]) -> float:
    ...

```

扩展`TypeAnnotator`，使其支持参数和返回值的并集类型。 使用`Optional[X]`作为`Union[X, None]`的简写。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/DynamicInvariants.ipynb#Exercises) to work on the exercises and see solutions.

### 练习2：局部变量的类型

在Python中，不仅可以注释类型的参数，而且还可以注释局部变量和全局变量，例如`my_sqrt()`实现中的`approx`和`guess`：

```py
def my_sqrt_with_local_types(x: Union[int, float]) -> float:
    """Computes the square root of x, using the Newton-Raphson method"""
    approx: Optional[float] = None
    guess: float = x / 2
    while approx != guess:
        approx: float = guess
        guess: float = (approx + x / approx) / 2
    return approx

```

扩展`TypeAnnotator`，使其也可以用类型注释局部变量。 在函数AST中搜索分配，确定分配值的类型，并在左侧进行注释。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/DynamicInvariants.ipynb#Exercises) to work on the exercises and see solutions.

### 练习3：详细的不变式检查器

我们的不变式检查器的实现方式无法向用户明确指出哪个前置条件/​​后置条件失败。

```py
@precondition(lambda s: len(s) > 0)
def remove_first_char(s):
    return s[1:]

```

```py
with ExpectError():
    remove_first_char('')

```

```py
Traceback (most recent call last):
  File "<ipython-input-193-dda18930f6db>", line 2, in <module>
    remove_first_char('')
  File "<ipython-input-100-39ada1fd0b7e>", line 6, in wrapper
    assert precondition(*args, **kwargs), "Precondition violated"
AssertionError: Precondition violated (expected)

```

以下实现添加了一个可选的`doc`关键字参数，如果违反了不变式，则会打印该参数：

```py
def condition(precondition=None, postcondition=None, doc='Unknown'):
   def decorator(func):
       @functools.wraps(func) # preserves name, docstring, etc
       def wrapper(*args, **kwargs):
           if precondition is not None:
               assert precondition(*args, **kwargs), "Precondition violated: " + doc

           retval = func(*args, **kwargs) # call original function or method
           if postcondition is not None:
               assert postcondition(retval, *args, **kwargs), "Postcondition violated: " + doc

           return retval
       return wrapper
   return decorator

def precondition(check, **kwargs):
   return condition(precondition=check, doc=kwargs.get('doc', 'Unknown'))

def postcondition(check, **kwargs):
   return condition(postcondition=check, doc=kwargs.get('doc', 'Unknown'))

```

```py
@precondition(lambda s: len(s) > 0, doc="len(s) > 0")
def remove_first_char(s):
    return s[1:]

remove_first_char('abc')

```

```py
'bc'

```

```py
with ExpectError():
    remove_first_char('')

```

```py
Traceback (most recent call last):
  File "<ipython-input-196-dda18930f6db>", line 2, in <module>
    remove_first_char('')
  File "<ipython-input-194-683ee268305f>", line 6, in wrapper
    assert precondition(*args, **kwargs), "Precondition violated: " + doc
AssertionError: Precondition violated: len(s) > 0 (expected)

```

扩展`InvariantAnnotator`，使其包括所生成的前置条件和后置条件中的条件。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/DynamicInvariants.ipynb#Exercises) to work on the exercises and see solutions.

### 练习4：保存初始值

如果参数的值在函数执行过程中发生更改，则很容易使我们的实现困惑：在函数的开头跟踪值，但仅在返回时才进行检查。 扩展`InvariantAnnotator`及其使用的基础架构，以便

*   它在函数调用的开始和结束时保存参数值；
*   后置条件可以用参数的*初始*初始值以及参数的*最终*值表示；
*   挖掘的后置条件也同时引用了这两个值。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/DynamicInvariants.ipynb#Exercises) to work on the exercises and see solutions.

### 练习5：含义

其他一些不变的变量实际上是*所隐含的：`x > 0`成立，则意味着`x >= 0`和`x != 0`。 扩展`InvariantAnnotator`，以使属性之间的含义被显式编码，并且隐含的属性不再列为不变式。 有关想法，请参见 [Ernst *等人*，2001。](https://doi.org/10.1109/32.908957)。*

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/DynamicInvariants.ipynb#Exercises) to work on the exercises and see solutions.

### 练习6：局部变量

后置条件也可以引用局部变量的值。 考虑扩展`InvariantAnnotator`及其基础结构，以便在执行结束时记录局部变量的值并将其作为不变推理机制的一部分。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/DynamicInvariants.ipynb#Exercises) to work on the exercises and see solutions.

### 练习7：探索不变选择

挖掘出第一组不变式后，让[圆锥模糊测试器](ConcolicFuzzer.html)生成系统地尝试使前置条件和后置条件无效的测试。 您可以推广多远？

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/DynamicInvariants.ipynb#Exercises) to work on the exercises and see solutions.

### 练习8：语法生成的属性

要检查的属性集越大，可以发现更多的潜在不变性。 创建一个*语法*，该语法系统地产生大量属性。 参见[ [Ernst *等人*，2001。](https://doi.org/10.1109/32.908957)]了解可能的模式。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/DynamicInvariants.ipynb#Exercises) to work on the exercises and see solutions.

### 练习9：将不变量嵌入为断言

与其生成不变式作为前提条件和后置条件的注释，不如将它们作为`assert`语句插入到函数代码中，如下所示：

```py
def my_sqrt(x):
    'Computes the square root of x, using the Newton-Raphson method'
    assert isinstance(x, int), 'violated precondition'
    assert (x > 0), 'violated precondition'
    approx = None
    guess = (x / 2)
    while (approx != guess):
        approx = guess
        guess = ((approx + (x / approx)) / 2)
    return_value = approx
    assert (return_value < x), 'violated postcondition'
    assert isinstance(return_value, float), 'violated postcondition'
    return approx

```

这样的表述可以使测试生成器和符号分析更容易访问和解释前置条件和后置条件。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/DynamicInvariants.ipynb#Exercises) to work on the exercises and see solutions.