读书笔记-Python科学编程入门(A Primer on Scientific Programming with Python)(第五版)-第三章

第三章-函数与分支(Functions and Branching)

介绍了两个概念:函数和程序流程分支(通常指 if 语句)


3.1-函数

Python 中的函数不同于数学上的函数,是可以在任何时间地点执行、可重复使用的语句的集合。

3.1.1

Python 的函数结构

  • F(C) = \frac{9}{5}C + 32 在 Python 中记为:
    def F(C):
        return (9.0/5)*C + 32

     

  • 函数的开头:def 函数名(参数列表): 
  • 函数体:相对 def 缩进一次;遇到 return 语句直接退出函数,并返回 return 后的值(即返回值);函数的返回值可为任意对象

3.1.2

程序流程的理解

  • 可浏览 http://www.pythontutor.com/visualize.html 来观察程序的运行流程
  • 首先,选语言,写代码,单击“Visualize Execution”按钮:

  • 然后,通过“Firs”、“Back”、“Forward”和“Last”按钮进行观察:

     

3.1.3

局部变量(Local Variables)和全局变量(Global Variables)

  • 函数内定义的变量-局部变量
  • 主程序内定义的变量-全局变量
  • 局部变量的修改不会影响全局变量
  • 函数内若要使用全局变量,则要用 global 变量名 进行定义,如:
    a = 20; b = -2.5 # 全局变量
    
    def f1(x):
        a = 21 # a为一个新的局部变量
        return a*x + b
    
    print a # 打印 20
    
    def f2(x):
        global a # 声明此处的a为全局变量
        a = 21 # a为全局变量
        return a*x + b
    
    f1(3); print a # 全局变量a不变,仍为20
    f2(3); print a # 全局变量a改变,变为21
  • Python 中寻找变量的顺序:局部变量 -> 全局变量 -> Python 中的内建函数(built-in Python functions)

3.1.4

函数可有多个参数

  • y(t) = v_0t - \frac{1}{2}gt^2 在 Python 中记为:
    def yfunc(t, v0):
        g = 9.81
        return v0*t - 0.5*g*t**2

     

  • 当时间 t 为 0.1,初速度 v0 为 6 时,主程序中可以这样调用函数:
    y = yfunc(0.1, 6)
    y = yfunc(0.1, v0=6)
    y = yfunc(t=0.1, v0=6)
    y = yfunc(v0=6, t=0.1)
    可用 参数名 = 值 直接传递参数,若不使用 参数名  的顺序应与参数列表的顺序一致;参数名 = 值 的形式应在只有 的形式之后(例如,yfunc(t=0.1, 6) 不合法

3.1.5

  • 在调用函数前,应将函数中的全局变量定义好

3.1.6

  • Python 的函数不单单能表达数学中的函数,任何需要重复使用的语句均能写成函数,例如
    def makelist(start, stop, inc):
        value = start
        result = []
        while value <= stop:
            result.append(value)
            value = value + inc
        return result # 返回一个列表
    
    mylist = makelist(0, 100, 0.2)
    print mylist # 将会打印出 0, 0.2, 0.4, 0.6, ... 99.8, 100

     

  • 其中,函数参数列表中的三个参数 start、stop 和 inc 以及函数内的 value 和 result 均为函数内的局部变量 ,mylist 为全局变量
  • Python 自带的 range(start, stop, inc) 的参数只允许整型变量,但是我们写的 makelist(start, stop, inc) 允许浮点型变量

3.1.7

函数可有多个返回值

  • 例,同时计算 y(t) = v_0t - \frac{1}{2}gt^2 和 {y}'(t) = v_0 - gt
    def yfunc(t, v0):
        g = 9.81
        y = v0*t - 0.5*g*t**2
        dydt = v0 - g*t
        return y, dydt

     

  • 调用该函数:
    position, velocity = yfunc(0.6, 3)

     

  • 实际上,多返回值函数的返回值类型为多元组

3.1.8

给出了一个通过累加计算对数的函数的例子

3.1.9

函数可以没有返回值

  • 此时 Python 会在最后一行自动加上 return None
  • Python 中 None 意味着 nothing

3.1.10

关键字参数(Keyword Arguments)

  • 使用关键字参数,给函数的对应参数赋予默认值:
    def somefunc(arg1, arg2, kwarg1=True, kwarg2=0):
        print arg1, arg2, kwarg1, kwarg2

    当不给 kwarg1 和 kwarg2 赋值时,分别默认为 True 和 0

3.1.11

文档字符串(Doc Strings)

  • 使用文档字符串,给函数加上说明注释:
    def C2F(C):
        """Convert Celsius degrees (C) to Fahrenheit."""
        return (9.0/5)*C + 32
       
    def line(x0, y0, x1, y1):
        """
        Compute the coefficients a and b in the mathematical
        expression for a straight line y = a*x + b that goes
        through two points (x0, y0) and (x1, y1).
        x0, y0: a point on the line (floats).
        x1, y1: another point on the line (floats).
        return: coefficients a, b (floats) for the line (y=a*x+b).
        """
        a = (y1 - y0)/float(x1 - x0)
        b = y0 - a*x0

    文档字符串需写在函数开头之后、函数体之前

  • 可通过 函数名.__doc__ 读取文档字符串:
    >>> print line.__doc__
    
    Compute the coefficients a and b in the mathematical
    expression for a straight line y = a*x + b that goes
    through two points (x0, y0) and (x1, y1).
    x0, y0: a point on the line (floats).
    x1, y1: another point on the line (floats).
    return: coefficients a, b (floats) for the line (y=a*x+b).

3.1.12

函数可做函数的参数

  • 例如, f(x) 的二次导可写为 {f}''(x) = \frac{f(x - h) - 2f(x) + f(x + h)}{h^2},在 Python 中可写为:
    def diff2nd(f, x, h=1E-6):
        r = (f(x-h) - 2*f(x) + f(x+h))/float(h*h)
        return r

    其中,参数 f 即为先前定义的某个函数的函数名

3.1.13

主程序(the Main Program)

  • 我们定义函数的那个地方,通常称为主函数;主函数定义的变量,均为全局变量(参见 3.1.3):
    from math import *         # 在主程序中
    
    def f(x):                  # 在主程序中
        e = exp(-0.1*x)
        s = sin(6*pi*x)
        return e*s
    
    x = 2                      # 在主程序中
    y = f(x)                   # 在主程序中
    print 'f(%g)=%g' % (x, y)  # 在主程序中

     

  • 以上面的代码为例,主程序的运行流程为:
    • 从 math 模块中引入函数(Import functions from the math module)
    • 定义函数 f(x)(define a function f(x))
    • 定义全局变量 x(define x)
    • 调用函数 f 并执行函数体(call f and execute the function body)
    • 定义全局变量 y,并赋值为函数 f 的返回值(define y as the value returned from f)
    • 打印字符串(print the string.)
  • 可以在 3.1.2 提到的网站运行看看

3.1.14

Lambda 函数

  • 只有一条语句的函数,可由 lambda 函数直接用一行表示:
    """
    只有一条语句的函数
    def g(arg1, arg2, arg3, ...):
        return expression
    
    可用lambda函数直接表示
    g = lambda arg1, arg2, arg3, ...: expression
    """
    
    #例如,以下两式等价
    def f(x):
        return x**2 + 4 
    
    f = lambda x: x**2 + 4

     

  • lambda 函数也支持关键字参数,例如:
    d2 = diff2nd(lambda t, A=1, a=0.5: -a*2*t*A*exp(-a*t**2), 1.2)

     


3.2-分支

分段函数 f(x)=\left\{\begin{matrix} sin\,x, & 0\leqslant x\leqslant \pi\\ 0, & otherwise \end{matrix}\right. 可用 if-else 分支语句表示为:

from math import pi
def f(x):
    if 0 <= x <= pi:
        value = sin(x)
    else:
        value = 0
    return value

3.2.1

if-else 语句

  • 常见的写法:
    if 判断条件:
        <语句块, 当条件为 True 时执行>
    else:
        <语句块, 当条件为 False 时执行>

    else 可省略

  • 用 elif,可加入更多判断;例,N(x) = \left\{\begin{matrix} 0, & x < 0 \\ x, & 0 \leqslant x < 1 \\ 2-x, & 1 \leqslant x < 2 \\ 0, & x \geqslant 2 \end{matrix}\right. 可表示为:
    def N(x):
        if x < 0:
            return 0.0
        elif 0 <= x < 1:
            return x
        elif 1 <= x < 2:
            return 2 - x
        elif x >= 2:
            return 0.0

    def N(x):
        if 0 <= x < 1:
            return x
        elif 1 <= x < 2:
            return 2 - x
        else:
            return 0

     

3.2.2

用一行表示 if-else

  • 形如
    if condition:
        a = value1
    else:
        a = value2

    可表示为

    a = (value1 if condition else value2)

     

  • 形如
    def f(x):
        return (sin(x) if 0 <= x <= 2*pi else 0)

    可表示为

    f = lambda x: sin(x) if 0 <= x <= 2*pi else 0

     


3.3-循环、分支和函数的综合运用(Mixing Loops, Branching, and Functions in Bioinformatics Examples)

DNA 序列由 A、T、G、C 四种碱基(bases)组合而成;以此为例,展示循环、分支和函数的综合运用

3.3.1

举例说明,查看 DNA 中特定碱基出现次数的若干方案

  • 方案一:列表迭代法(List iteration)
    def count_v1(dna, base):
        dna = list(dna)   # 将类型转为list
        i = 0             # 计数器
        for c in dna:
            if c == base:
                i += 1
    return i

    将 DNA 序列转存为列表,数其中碱基的个数。

  • 方案二:字符串迭代法(String iteration)
    def count_v2(dna, base):
        i = 0             # 计数器
        for c in dna:
            if c == base:
                i += 1
    return i

    实际上,Python 中包含很多元素的对象(如,string、list、tuple等),都能用 for 循环遍历

  • 方案三:索引迭代法(Index iteration)
    def count_v3(dna, base):
        i = 0             # 计数器
        for j in range(len(dna)):
            if dna[j] == base:
                i += 1
        return i

    很多语言(如,Fortran、C、Java 等),都能用索引遍历

  • 方案四:While 循环法(While loops)
    def count_v4(dna, base):
        i = 0             # 计数器
        j = 0             # 计数器
        while j < len(dna):
            if dna[j] == base:
                i += 1
                j += 1
        return i

    注意缩进

  • 方案五:布尔列表求和法(Summing a boolean list)
    def count_v5(dna, base):
        m = []            # 将base与dna中的元素作匹配:若dna[i]==base,则m[i]=True
        for c in dna:
            if c == base:
                m.append(True)
            else:
                m.append(False)
        return sum(m)

    实际上,True 值为 1,False 值为 0;将布尔列表求和,结果即为 True 的个数

  • 方案六:if 语句单行表示法(Inline if test)
    def count_v6(dna, base):
        m = []            # 将base与dna中的元素作匹配:若dna[i]==base,则m[i]=True
        for c in dna:
            m.append(True if c == base else False)
        return sum(m)

    方案方法五的更简练的写法,增加可读性

  • 方案七:直接使用布尔值法(Using boolean values directly)
    def count_v7(dna, base):
        m = []            # 将base与dna中的元素作匹配:若dna[i]==base,则m[i]=True
        for c in dna:
            m.append(c == base)
        return sum(m)

    c == base 结果非 True False,可直接使用

  • 方案八:列表推导法(List comprehensions)
    def count_v8(dna, base):
        m = [c == base for c in dna]
        return sum(m)

    利用 [expr for e in sequence] 直接生成列表;其中,sequence 为已知数列,expr 为与 e 相关的表达式

  • 方案九:方案八的简洁表达法
    def count_v9(dna, base):
        return sum([c == base for c in dna])

    去掉变量 m,将函数体并为一行

  • 方案十:迭代求和法(Using a sum iterator)​
    def count_v10(dna, base):
        return sum(c == base for c in dna)

    实际上,DNA 序列相当长,列表可能存不下;去掉方法九中的方括号,可避免生成列表

  • 方案十一:索引提取法(Extracting indices)​
    def count_v11(dna, base):
        return len([i for i in range(len(dna)) if dna[i] == base])

    将 dna 中与 base 相同的元素的索引存储在新列表中

  • 方案十二:使用 Python 库(Using Python’s library)​
    def count_v12(dna, base):
        return dna.count(base)

    直接使用 Python 库函数

3.3.2

对以上方案的解题效率进行评估

  • 首先,生成用于测试的 DNS 序列
    N = 1000000
    dna = 'A'*N

    此方法,可生成 1000000 个 A 构成的序列,但缺乏随机性;可用 random 库生成随机的序列:

    import random
    alphabet = list(’ATGC’)
    dna = [random.choice(alphabet) for i in range(N)]
    dna = ’’.join(dna)     # 将列表变为字符串

    random.choice(x) 会随机选择列表 x 中的一个元素;但在 Python 2.x 中,range(N) 会生成一个列表,当 N 很大时,列表将无法构建,我们可用 xrange(N) 代替,通过一次生成一个整数来避免生成一个大列表;Pyhon 3.x 中 range,与 2.x 中的 xrange 等价:

    import random
    
    def generate_string(N, alphabet=’ACGT’):
        return ’’.join([random.choice(alphabet) for i in xrange(N)])
    
    dna = generate_string(600000)

    生成了 600000 长度的随机 DNA 序列

  • 计算 CPU 时间
    import time
    ...
    t0 = time.clock()
    # 执行其他代码
    t1 = time.clock()
    cpu_time = t1 - t0

    time.clock() 返回当前程序的 CPU 时间;若关心实际运行时间(包括读写文件等),可用 time.time()

  • 测试每个方案的 CPU 时间
    import time
    functions = [count_v1, count_v2, count_v3, count_v4,
                count_v5, count_v6, count_v7, count_v8,
                count_v9, count_v10, count_v11, count_v12]
    timings = []     # timings[i]保存functions[i]的CPU时间
    
    for function in functions:
        t0 = time.clock()
        function(dna, ’A’)
        t1 = time.clock()
        cpu_time = t1 - t0
        timings.append(cpu_time)

    对于 Python 而言,函数也是个对象,因此函数列表和其他列表没有区别

  • ​​​​​​​利用 zip,同时返回函数名和 CUP 时间
    for cpu_time, function in zip(timings, functions):
        print ’{f:<9s}: {cpu:.2f} s’.format(
            f=function.func_name, cpu=cpu_time)
  • 测试结果显示,在 MacBook Air 11 上的 Ubuntu 操作系统中,使用了 list.append 的方案,耗时是用了函数推导法的方案的两倍;字符串迭代法会再更快一些;而使用了 Python 内建函数  dna.count(base) 的方案,比自己编写的快了30倍!
  • 因此,即使是很简单的任务,也要先 Google;因为前人研究出来的解决方案,远比自己拍脑袋想出来的高级

3.3.3

编写测试函数,检验以上方案的正确度

  • 假设 dna.count('A') 返回的是标准答案
    def test_count_all():
        dna = ’ATTTGCGGTCCAAA’
        expected = dna.count(’A’)
    
        functions = [count_v1, count_v2, count_v3, count_v4,
                    count_v5, count_v6, count_v7, count_v8,
                    count_v9, count_v10, count_v11, count_v12]
    
        for f in functions:
            success = f(dna, ’A’) == expected
            msg = ’%s failed’ % f.__name__
            assert success, msg

    其中,f.__name__ 返回函数名

 

  • 测试函数应当遵守如下约定:
    • test_ 开头(have a name starting with test_)
    • 没有参数(have no arguments)
    • 有个名为 success 的布尔变量,通过测试其值为 True,否则为 False(let a boolean variable, say success, be True if a test passes and be False if the test fails)
    • 有个名为 msg 的字符串变量,提示错误信息(create a message about what failed, stored in some string, say msg)
    • 使用 assert success, msg 的结构,当 success False 时,终止程序并输出 msg 错误信息(use the construction assert success, msg, which will abort the program and write out the error message msg if success is False)

3.4-小结(Summary)

本章所学术语

  • 函数(function)
  • 方法(method)
  • 返回语句(return statement)
  • 位置参数(position arguments)
  • 关键字参数(key arguments)
  • 局部和全局变量(local and global variables)
  • 文档字符串(doc strings)
  • if、else 和 elif 分支语句(if tests with if, elif, and else (branching))
  • None 对象(the None object)
  • 测试函数(test functions (for verification))

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值