目录

Welcome

week 1 notes


Python 基础

基本操作
导入

# import lib from specific url
from urllib.request import urlopen

Elements of Programming

编程语言通常会提供三种方式来生成复杂的指令:

编程中的两种元素

expressions

表达式包括两种:

call expressions

call exressions 是复合表达式的一种,是 $f(x)$ 的一般形式,指计算(mapping)的过程。其特点:

call expressions 通常是如下的形式:

  • operator 指定了 function
  • 执行的过程是 function 以指定的 argument 调用,并返回了value
  • argument 的顺序 matters
  • 可以使用 function name 取代数学符号(infix notation)进行函数调用,没有歧义并调用方便

name and the environment

name 是一种用于关联 computational objects 的方式。name 可以通过与该对象绑定(binding)的方式来与其关联,从而达到通过 name 来操作该对象的目的。name 的绑定方式有 3 种:

由于这种绑定关系的的存在,计算机需要分配一部分空间存储这些 name, object 以及关系。被分配的空间被称为环境environment)。下面是 python 的示例:

#binding example
#import name with bound function

>>>from math import sqrt
>>>sqrt(256)

#assign name to a value
>>> radius = 10

#assgin name to an function
>>> max
#<built-in function max>
>>>f = max

#define a function
def square(x):
    return x*x

name in python

>>> area, circumference = pi * radius * radius, 2 * pi * radius
>>> area
314.1592653589793
>>> radius = 11
>>> area
314.1592653589793
#another assignment needed for updating the value of area
>>> area = pi * radius * radius
380.132711084365

>>> x, y = 3, 4.5
>>> y, x = x, y
>>> x
4.5
>>> y
3

表达式解释的顺序

基础的解释顺序为:

  1. operator
  2. operand(可能是 primtive expr,也可能是 subexpression)
  3. subexpression 的 function (operator)

特点是:要计算主表达式,就得先计算出其子表达式的值。这种评估的过程被称为递归recursive

Nested Expressions

如果 operand 是子表达式(sub-expression),那么对该表达式按基础顺序继续执行,直到得到值(value)。例子如下:


该结构被称为表达式树expression tree

注意 CS 中树的构建是从上往下的,也就意味着求出根表达式需要求出其节点的值。而相比一半的算数式子,表达式的结构非常有利于解释执行(解释)的顺序,尤其是在多层嵌套的前提下。

注意事项

# the operation does not return any values, but changes value of x
x = 3

纯/非纯函数

纯函数pure function)指有输入(argument)并会产生输出 (the result) 的函数,比如:

>>> abs(-2)
2

非纯函数Non-pure functions)指应用时带有“副作用(side effects)” 的函数(通常指改变解释器状态)。打印是一种常见的副作用:

>>> print(1, 2, 3)
1 2 3
实际上,print() 函数的打印值并不是其返回值。其返回值是 NoneType 类型的,值为 None 的变量:
>>> print(print(1), print(2))
1
2
None None

纯函数的优点

Defining New Functions

自定义函数是抽象过程中更加高级的做法。这种做法允许我们将 name 绑定到指定的复合指令集合上。比如这段程序:

>>> def square(x):
        return mul(x, x)
实际上使用了 square 来抽象了让 x 自己乘以自己的过程。此处的 x 被称为 formal parameterx 为自乘的数据提供了 name。

How to define a function

def <name>(<formal parameters>):
    return <return expression>

Environments

environments 通过一系列的 frame 来衡量表达式。以 frame 为单位,每个 frame 内部都会存储对应的 name,以及该 name 绑定的 value.

在 python 中,默认存在一个全局(golbal)frame。赋值 / 外部导入的 name 都会存储在这个 frame 中:


function 的 binding (无论是导入的,还是自定义的),也存储在这里:


我们将存储在 frame 中的 name 称为 bound name,而出现在函数内部的,同样的 name 称为 intrinsic name。每个函数可以同时存在多个 bound name,但 intrinsic name 有且只有一个。

注意事项

自定义函数的调用过程

考虑以下函数:

from operator import add, mul
def square(x):
    return mul(x, x)

def sum_squares(x, y):
    return add(square(x), square(y))
	
    result = sum_squares(5, 12)

  1. 首先,python 会评估 name sum_squares. 此时 sum_squares 与自定函数绑定,其关系存储在 global frame 中。
  2. 当程序执行到调用 sum_square(),也就是 result = sum_squares(5, 12) 这一步时,此时:
    1. 该调用创建了一个 sum_suqare() 专属的 local frame
    2. local frame 中保存了 sum_suqare() 内部 operand 的绑定关系。此处是 square()。需要注意的是这里的 operator add() 是 built-in(外部导入的),因此其绑定存储在 global frame 中,而不是 sun_square() 的 local frame 中
  3. 接下来需要应用另外一个自定义函数 square(),因此针对该函数的绑定,需要再创建一个 Local frame 进行存储
  4. 执行完毕之后,将得到的结果一级一级往上反馈,最终得到最后结果:




可以发现的是:

Local names

函数的实现与其 formal parameter 无关。比如下面的两个函数实际上是同一个:

>>> def square(x):
        return mul(x, x)
>>> def square(y):
        return mul(y, y)
也就是说,formal parameter 的 name 只会在函数体内部生效。这样的设计的好处:

我们将 local name 生效的区域(函数体)成为它的 scope。如果某个 name 无法被访问,那么我们就称其 out of scope

python 命名的选择

函数与抽象

从之前的例子可以看到,我们在不考虑 square() 如何实现的情况下使用 sum_squares() 函数。我们将 square() 这种处于另一个函数内部的函数实现称之为函数抽象(functional abstraction)。从这点上来看,下面两个函数是没有区别的:

# these two functions are indistinguishable becuase of their return value
>>> def square(x):
        return mul(x, x)
>>> def square(x):
        return mul(x, x-1) + x

函数抽象的概念

函数抽象需要考虑三个核心的属性:

python 的运算符

Designing Functions

函数设计应该遵循一个 idea:

functions are abstractions.

具体的来说:

Documentation

python 中通常包括了函数的描述,这类 documentation 被称为 docstring

>>> def pressure(v, t, n):
        """Compute the pressure in pascals of an ideal gas.

        Applies the ideal gas law: http://en.wikipedia.org/wiki/Ideal_gas_law

        v -- volume of gas, in cubic meters
        t -- absolute temperature in degrees kelvin
        n -- particles of gas
        """
        k = 1.38e-23  # Boltzmann's constant
        return n * k * t / v
还有一类以 # 起头的信息被称为 CommentsDocstring Conventions

Control statement

statement

statementexpression 在本质上有不同:

statement 意味着应用更改:比如赋值,返回等等。exrepssion 在被评估的时候也可以被视作 statement,但其生成的结果会丢失。比如:

>>> def square(x):
        mul(x, x) # Watch out! This call doesn't return a value
如果希望应用修改(返回返回值),则需要使用 return statement:
>>> def square(x):
        return mul(x, x)

Compound Statements

结构如下:

<header>:
    <statement>
    <statement>
    ...
<separating header>:
    <statement>
    <statement>
    ...
...

根据上述定义,def 属于 compound statement

上述的结构可以被视作 sequence,也就是该结构总可以分为两个部分:

这是一种递归的结构。

Local Assignment

用户自定义的函数是在对应的 local frame 中运行的。local frame 在该函数被调用时创建;函数体中的 return statement 会起到重定向的作用。函数什么时候结束取决于

赋值语句可以处于函数内部。任何函数内部的赋值,其绑定信息都存储于local frame,对外部的 name 不造成任何影响。

Conditional statement

if <expression>:
    <suite>
elif <expression>:
    <suite>
else:
    <suite>

Iteration

while <expression>:
    <suite>

Testing

Assertions

python 中可以使用 assert statement 做验证。如果 assert 返回的对象为 True,那么什么也不会发生。否则,错误会被抛出:

>>> def fib_test():
        assert fib(2) == 1, 'The 2nd Fibonacci number should be 1'
        assert fib(3) == 1, 'The 3rd Fibonacci number should be 1'
        assert fib(50) == 7778742049, 'Error at the 50th Fibonacci number'

Doctest

docstring 中可以包含 test case,用于测试:

>>> def sum_naturals(n):
        """Return the sum of the first n natural numbers.

        >>> sum_naturals(10)
        55
        >>> sum_naturals(100)
        5050
        """
        total, k = 0, 1
        while k <= n:
            total, k = total + k, k + 1
        return total
        
>>> from doctest import testmod
>>> testmod()
TestResults(failed=0, attempted=2)