banner
Zoney

Zoney

一个励志成为Web安全工程师的女青年!

python study 3

五 函数式编程#

函数是 Python 内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元

函数式编程(请注意多了一个 “式” 字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。

我们首先要搞明白计算机(Computer)和计算(Compute)的概念。

在计算机的层次上,CPU 执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。

而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。

对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如 C 语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如 Lisp 语言。

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数

Python 对函数式编程提供部分支持。由于 Python 允许使用变量,因此,Python 不是纯函数式编程语言

高级函数 (Higher-order function)

变量可以指向函数
以 Python 内置的求绝对值的函数 abs () 为例,调用该函数用以下代码:

>>> abs(-10)
10

但是,如果只写 abs 呢?

>>> abs
<built-in function abs>

可见,abs (-10) 是函数调用,而 abs 是函数本身。

要获得函数调用结果,我们可以把结果赋值给变量:

>>> x = abs(-10)
>>> x
10

但是,如果把函数本身赋值给变量呢?

>>> f = abs
>>> f
<built-in function abs>

结论:函数本身也可以赋值给变量,即:变量可以指向函数

如果一个变量指向了一个函数,那么,可否通过该变量来调用这个函数?用代码验证一下:

>>> f = abs
>>> f(-10)
10

成功!说明变量 f 现在已经指向了 abs 函数本身。直接调用 abs () 函数和调用变量 f () 完全相同。

函数名也是变量
那么函数名是什么呢?函数名其实就是指向函数的变量!对于 abs () 这个函数,完全可以把函数名 abs 看成变量,它指向一个可以计算绝对值的函数!

如果把 abs 指向其他对象,会有什么情况发生?

>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable

把 abs 指向 10 后,就无法通过 abs (-10) 调用该函数了!因为 abs 这个变量已经不指向求绝对值函数而是指向一个整数 10!

当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复 abs 函数,请重启 Python 交互环境。

注:由于 abs 函数实际上是定义在import builtins模块中的,所以要让修改 abs 变量的指向在其它模块也生效,要用 import builtins; builtins.abs = 10。

传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

一个最简单的高阶函数:

def add(x, y, f):
    return f(x) + f(y)

当我们调用 add (-5, 6, abs) 时,参数 x,y 和 f 分别接收 - 5,6 和 abs,根据函数定义,我们可以推导计算过程为:

x = -5
y = 6
f = abs
f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
return 11

编写高阶函数,就是让函数的参数能够接收别的函数。

小结
函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。

1.1 map/reduce

Python 内建了 map () 和 reduce () 函数。

map () 函数接收两个参数,一个是函数,一个是Iterable,map 将传入的函数依次作用到序列的每个元素,并把结果作为新的 Iterator返回。

举例说明,比如我们有一个函数 f (x)=x2,要把这个函数作用在一个 list [1, 2, 3, 4, 5, 6, 7, 8, 9] 上,就可以用 map () 实现如下

现在,我们用 Python 代码实现:

>>> def f(x):
...     return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

map () 传入的第一个参数是 f,即函数对象本身。由于结果 r 是一个 Iterator,Iterator 是惰性序列,因此通过 list () 函数让它把整个序列都计算出来并返回一个 list。

你可能会想,不需要 map () 函数,写一个循环,也可以计算出结果:

L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
    L.append(f(n))
print(L)

的确可以,但是,从上面的循环代码,能一眼看明白 “把 f (x) 作用在 list 的每一个元素并把结果生成一个新的 list” 吗?

所以,map () 作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的 f (x)=x2,还可以计算任意复杂的函数,比如,把这个 list 所有数字转为字符串:

>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']

只需要一行代码。

再看 reduce 的用法。reduce 把一个函数作用在一个序列 [x1, x2, x3, ...] 上,这个函数必须接收两个参数,reduce 把结果继续和序列的下一个元素做累积计算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
比方说对一个序列求和,就可以用 reduce 实现:

>>> from functools import reduce
>>> def add(x, y):
...     return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25

当然求和运算可以直接用 Python 内建函数 sum (),没必要动用 reduce。

但是如果要把序列 [1, 3, 5, 7, 9] 变换成整数 13579,reduce 就可以派上用场:

>>> from functools import reduce
>>> def fn(x, y):
...     return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579

这个例子本身没多大用处,但是,如果考虑到字符串 str 也是一个序列,对上面的例子稍加改动,配合 map (),我们就可以写出把 str 转换为 int 的函数:

>>> from functools import reduce
>>> def fn(x, y):
...     return x * 10 + y
...
>>> def char2num(s):
...     digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
...     return digits[s]
...
>>> reduce(fn, map(char2num, '13579'))
13579

整理成一个 str2int 的函数就是:

from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def str2int(s):
    def fn(x, y):
        return x * 10 + y
    def char2num(s):
        return DIGITS[s]
    return reduce(fn, map(char2num, s))

还可以用 lambda 函数进一步简化成:

from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def char2num(s):
    return DIGITS[s]

def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))

也就是说,假设 Python 没有提供 int () 函数,你完全可以自己写一个把字符串转化为整数的函数,而且只需要几行代码!

1.2 filter
Python 内建的 filter () 函数用于过滤序列

和 map () 类似,filter () 也接收一个函数和一个序列。和 map () 不同的是,filter () 把传入的函数依次作用于每个元素,然后根据返回值是 True 还是 False 决定保留还是丢弃该元素。

例如,在一个 list 中,删掉偶数,只保留奇数,可以这么写:

def is_odd(n):
    return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]

把一个序列中的空字符串删掉,可以这么写:

def not_empty(s):
    return s and s.strip()

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
# 结果: ['A', 'B', 'C']

可见用 filter () 这个高阶函数,关键在于正确实现一个 “筛选” 函数。

注意到 filter () 函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫 filter () 完成计算结果,需要用 list () 函数获得所有结果并返回 list。

filter () 的作用是从一个序列中筛出符合条件的元素。由于 filter () 使用了惰性计算,所以只有在取 filter () 结果的时候,才会真正筛选并每次返回下一个筛出的元素。

1.3 sorted
排序算法
排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个 dict 呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。

Python 内置的sorted() 函数就可以对 list 进行排序:

>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]

此外,sorted () 函数也是一个高阶函数,它还可以接收一个 key 函数来实现自定义的排序,例如按绝对值大小排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

key 指定的函数将作用于 list 的每一个元素上,并根据 key 函数返回的结果进行排序。对比原始的 list 和经过 key=abs 处理过的 list:

list = [36, 5, -12, 9, -21]
keys = [36, 5,  12, 9,  21]

然后 sorted () 函数按照 keys 进行排序,并按照对应关系返回 list 相应的元素:

keys排序结果 => [5, 9,  12,  21, 36]
                 |  |    |    |   |
最终结果     => [5, 9, -12, -21, 36]

我们再看一个字符串排序的例子:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

默认情况下,对字符串排序,是按照 ASCII 的大小比较的,由于 'Z' < 'a',结果,大写字母 Z 会排在小写字母 a 的前面。

现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能用一个 key 函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。

这样,我们给 sorted 传入 key 函数,即可实现忽略大小写的排序:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

要进行反向排序,不必改动 key 函数,可以传入第三个参数reverse=True

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。

小结
sorted () 也是一个高阶函数。用 sorted () 排序的关键在于实现一个映射函数。
返回函数
函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的:

def calc_sum(*args):
    ax = 0
    for n in args:
        ax = ax + n
    return ax

但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

当我们调用 lazy_sum () 时,返回的并不是求和结果,而是求和函数:

>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>

调用函数 f 时,才真正计算求和的结果:

>>> f()
25

在这个例子中,我们在函数 lazy_sum 中又定义了函数 sum,并且,内部函数 sum 可以引用外部函数 lazy_sum 的参数和局部变量,当 lazy_sum 返回函数 sum 时,相关参数和变量都保存在返回的函数中,这种称为 “闭包(Closure)” 的程序结构拥有极大的威力。

请再注意一点,当我们调用 lazy_sum () 时,每次调用都会返回一个新的函数,即使传入相同的参数:

>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False

f1 () 和 f2 () 的调用结果互不影响。

闭包
条件
1 外部函数中定义了内部函数
2 外部函数有返回值
3 返回的值是内部函数名
4 内部函数引用了外部函数的变量

def 外部函数():
    ...
    def 内部函数():
        ...
    return 内部函数
  

注意到返回的函数在其定义内部引用了局部变量 args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f() 才执行。

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的 3 个函数都返回了。

你可能认为调用 f1 (),f2 () 和 f3 () 结果应该是 1,4,9,但实际结果是:

>>> f1()
9
>>> f2()
9
>>> f3()
9

全部都是 9!原因就在于返回的函数引用了变量 i,但它并非立刻执行。等到 3 个函数都返回时,它们所引用的变量 i 已经变成了 3,因此最终结果为 9。

返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs

再看看结果:

>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9

缺点是代码较长,可利用 lambda 函数缩短代码。

nonlocal
使用闭包,就是内层函数引用了外层函数的局部变量。如果只是外层变量的值,我们会发现返回的闭包函数调用一切正常:

def inc():
    x = 0
    def fn():
        # 仅读取x的值:
        return x + 1
    return fn

f = inc()
print(f()) # 1
print(f()) # 1

但是,如果对外层变量赋值,由于 Python 解释器会把 x 当作函数 fn () 的局部变量,它会报错:

错误
def inc():
    x = 0
    def fn():
        # nonlocal x
        x = x + 1
        return x
    return fn

f = inc()
print(f()) # 1
print(f()) # 2

原因是 x 作为局部变量并没有初始化,直接计算 x+1 是不行的。但我们其实是想引用 inc () 函数内部的 x,所以需要在 fn () 函数内部加一个 nonlocal x 的声明。加上这个声明后,解释器把 fn () 的 x 看作外层函数的局部变量,它已经被初始化了,可以正确计算 x+1。

使用闭包时,对外层变量赋值前,需要先使用nonlocal声明该变量不是当前函数的局部变量。

小结
1 一个函数可以返回一个计算结果,也可以返回一个函数。
2 返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。

匿名函数
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。

在 Python 中,对匿名函数提供了有限支持。还是以 map () 函数为例,计算 f (x)=x2 时,除了定义一个 f (x) 的函数外,还可以直接传入匿名函数:

>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

通过对比可以看出,匿名函数 lambda x: x * x 实际上就是:

def f(x):
    return x * x

关键字 lambda 表示匿名函数,冒号前面的 x 表示函数参数

匿名函数有个限制,就是只能有一个表达式不用写 return,返回值就是该表达式的结果。

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25

同样,也可以把匿名函数作为返回值返回,比如:

def build(x, y):
    return lambda: x * x + y * y

Python 对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。

装饰器
特点
1 函数 A 是作为参数出现的(函数 B 就接受函数 A 作为参数)
2 要有闭包的特点

#定义一个装饰器
def decorate(func):
    a = 100
    print('wrapper外层打印测试')
 
    def wrapper():
        func()
        print('--------->刷漆')
        print('--------->铺地板', a)
        print('--------->装门')
 
    print('wrapper加载完成......')
    return wrapper
 
 
# 使用装饰器
@decorate
def house():
    print('我是毛坯房....')
 
'''
默认执行的:
1. house为被装饰函数,
2. 将被装饰函数作为参数传给装饰器decorate
3. 执行decorate函数
4. 将返回值又赋值给house
 
'''
print(house)
house()  # wrapper()
# def house1():
#     house()
#     print('刷漆')
#     print('铺地板')
 
 
# 调用函数house
# house()

由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。

>>> def now():
...     print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25

函数对象有一个name 属性(注意:是前后各两个下划线),可以拿到函数的名字:

>>> now.__name__
'now'
>>> f.__name__
'now'

现在,假设我们要增强 now () 函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改 now () 函数的定义,这种在代码运行期间动态增加功能的方式,称之为 “装饰器”(Decorator)。

本质上,decorator 就是一个返回函数的高阶函数

多层装饰器
如果装饰器是多层的,谁距离函数最近就优先使用哪个装饰器

# 装饰器
def zhuang1(func):
    print('------->1 start')
 
    def wrapper(*args, **kwargs):
        func()
        print('刷漆')
 
    print('------->1 end')
 
    return wrapper
 
 
def zhuang2(func):# func=house
    print('------->2 start')
 
    def wrapper(*args, **kwargs):
        func()
        print('铺地板,装门.....')
 
    print('------->2 end')
 
    return wrapper
 
 
@zhuang2
@zhuang1
def house():  # house = wrapper
    print('我是毛坯房.....')
 
 
house()

输出
------->1 start
------->1 end
------->2 start
------->1 end
我是毛坯房.....
刷漆
铺地板,装门.....

带参数的装饰器
'''
带参数的装饰器是三层
最外层的函数负责接收装饰器参数
里面的内容还是原装饰器的内容

# 装饰器带参数
def outer(a):  # 第一层: 负责接收装饰器的参数
    print('------------1 start')
 
    def decorate(func):  # 第二层 : 负责接收函数的
        print('------------2 start')
 
        def wrapper(*args, **kwargs):  # 第三层   负责接收函数的参数
            func(*args)
            print("---->铺地砖{}块".format(a))
 
        print('------------2 end')
        return wrapper  # 返出来的是:第三层
 
    print('------------1 end')
    return decorate  # 返出来的是:第二层
 
 
@outer(10)
def house(time):
    print('我{}日期拿到房子的钥匙,是毛坯房....'.format(time))
 
 
# @outer(100)
# def street():
#     print('新修街道名字是:黑泉路')
 
 
house('2019-6-12')
 
# street()

在面向对象(OOP)的设计模式中,decorator 被称为装饰模式。OOP 的装饰模式需要通过继承和组合来实现,而 Python 除了能支持 OOP 的 decorator 外,直接从语法层次支持 decorator。Python 的 decorator 可以用函数实现,也可以用类实现。

偏函数
Python 的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。

在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下:

int () 函数可以把字符串转换为整数,当仅传入字符串时,int () 函数默认按十进制转换:

>>> int('12345')
12345

但 int () 函数还提供额外的 base 参数,默认值为 10。如果传入 base 参数,就可以做 N 进制的转换:

>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565

假设要转换大量的二进制字符串,每次都传入 int (x, base=2) 非常麻烦,于是,我们想到,可以定义一个 int2 () 的函数,默认把 base=2 传进去:

def int2(x, base=2):
    return int(x, base)

这样,我们转换二进制就非常方便了:

>>> int2('1000000')
64
>>> int2('1010101')
85

functools.partial 就是帮助我们创建一个偏函数的,不需要我们自己定义 int2 (),可以直接使用下面的代码创建一个新的函数 int2:

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

所以,简单总结 functools.partial 的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

注意到上面的新的 int2 函数,仅仅是把 base 参数重新设定默认值为 2,但也可以在函数调用时传入其他值:

>>> int2('1000000', base=10)
1000000

最后,创建偏函数时,实际上可以接收 函数对象、*args 和 **kw 这 3 个参数,当传入:

int2 = functools.partial(int, base=2)

实际上固定了 int () 函数的关键字参数 base,也就是:

int2('10010')

相当于:

kw = { 'base': 2 }
int('10010', **kw)

当传入:

max2 = functools.partial(max, 10)
实际上会把 10 作为 * args 的一部分自动加到左边,也就是:

max2(5, 6, 7)
相当于:

args = (10, 5, 6, 7)
max(*args)
结果为 10。

小结

当函数的参数个数太多,需要简化时,使用 functools.partial 可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

六 模块#

在 Python 中,一个.py 文件就称之为一个模块(Module)

使用模块有什么好处?

1 最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括 Python内置的模块和来自第三方的模块

2 使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突

你也许还想到,如果不同的人编写的模块名相同怎么办?为了避免模块名冲突,Python 又引入了按目录来组织模块的方法,称为(Package)。

举个例子,一个 abc.py 的文件就是一个名字叫 abc 的模块,一个 xyz.py 的文件就是一个名字叫 xyz 的模块。假设我们的 abc 和 xyz 这两个模块名字与其他模块冲突了,于是我们可以通过包来组织模块,避免冲突。方法是选择一个顶层包名,比如 mycompany,按照如下目录存放:

mycompany
├─ __init__.py
├─ abc.py
└─ xyz.py

引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。现在,abc.py 模块的名字就变成了 mycompany.abc,类似的,xyz.py 的模块名变成了 mycompany.xyz。

mycompany
 ├─ web
 │  ├─ __init__.py
 │  ├─ utils.py
 │  └─ www.py
 ├─ __init__.py
 ├─ abc.py
 └─ utils.py

文件 www.py 的模块名就是 mycompany.web.www,两个文件 utils.py 的模块名分别是 mycompany.utils 和 mycompany.web.utils。

mycompany.web 也是一个模块,请指出该模块对应的.py 文件。

!!自己创建模块时要注意命名,不能和 Python 自带的模块名称冲突。例如,系统自带了 sys 模块,自己的模块就不可命名为 sys.py,否则将无法导入系统自带的 sys 模块。

总结
1 模块是一组 Python 代码的集合,可以使用其他模块,也可以被其他模块使用。

2 创建自己的模块时,要注意:
2.1 模块名要遵循 Python 变量命名规范,不要使用中文、特殊字符;
2.2 模块名不要和系统模块名冲突,最好先查看系统是否已存在该模块,检查方法是在 Python 交互环境执行 import abc,若成功则说明系统存在此模块。

使用模块
Python 本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用。

我们以内建的 sys 模块为例,编写一个 hello 的模块:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

import sys

def test():
    args = sys.argv
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
    else:
        print('Too many arguments!')

if __name__=='__main__':
    test()

第 1 行和第 2 行是标准注释,第 1 行注释可以让这个 hello.py 文件直接在 Unix/Linux/Mac 上运行,第 2 行注释表示.py 文件本身使用标准 UTF-8 编码;

第 4 行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

第 6 行使用__author__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;

以上就是 Python 模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。

后面开始就是真正的代码部分。

你可能注意到了,使用 sys 模块的第一步,就是导入该模块:

import sys

导入 sys 模块后,我们就有了变量 sys 指向该模块,利用 sys 这个变量,就可以访问 sys 模块的所有功能。

sys 模块有一个argv 变量用 list 存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py 文件的名称。
例如:
运行 python3 hello.py 获得的 sys.argv 就是 ['hello.py'];

运行 python3 hello.py Michael 获得的 sys.argv 就是 ['hello.py', 'Michael']。

最后,注意到这两行代码:

if __name__=='__main__':
    test()

当我们在命令行运行 hello 模块文件时,Python 解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该 hello 模块时,if 判断将失败,因此,这种 if 测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

我们可以用命令行运行 hello.py 看看效果:

$ python3 hello.py
Hello, world!
$ python hello.py Michael
Hello, Michael!

如果启动 Python 交互环境,再导入 hello 模块:

$ python3
Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 23 2015, 02:52:03) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
>>>

导入时,没有打印 Hello, word!,因为没有执行 test () 函数。

调用 hello.test () 时,才能打印出 Hello, word!:

>>> hello.test()
Hello, world!

作用域
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在 Python 中,是通过 _前缀 来实现的。

1 正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI 等;

2 类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊变量,hello 模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;

3 类似_xxx 和__xxx 这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc 等;

之所以我们说,private 函数和变量 “不应该” 被直接引用,而不是 “不能” 被直接引用,是因为 Python 并没有一种方法可以完全限制访问 private 函数或变量,但是,从编程习惯上不应该引用 private 函数或变量。

private 函数或变量不应该被别人引用,那它们有什么用呢?请看例子:

def _private_1(name):
    return 'Hello, %s' % name

def _private_2(name):
    return 'Hi, %s' % name

def greeting(name):
    if len(name) > 3:
        return _private_1(name)
    else:
        return _private_2(name)

我们在模块里公开 greeting () 函数,而把内部逻辑用 private 函数隐藏起来了,这样,调用 greeting () 函数不用关心内部的 private 函数细节,这也是一种非常有用的代码封装和抽象的方法
即:?外部不需要引用的函数全部定义成 private,只有外部需要引用的函数才定义为 public。

安装第三方模块
在 Python 中,安装第三方模块,是通过包管理工具pip完成的。

一般来说,第三方库都会在 Python 官方的 pypi.python.org 网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者 pypi 上搜索,比如 Pillow 的名称叫 Pillow,因此,安装 Pillow 的命令就是:

pip install Pillow

模块搜索路径
当我们试图加载一个模块时,Python 会在指定的路径下搜索对应的.py 文件,如果找不到,就会报错:

>>> import mymodule
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named mymodule

默认情况下,Python 解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在 sys 模块的 path 变量中:

>>> import sys
>>> sys.path

image

如果我们要添加自己的搜索目录,有两种方法:
一是直接修改 sys.path,添加要搜索的目录:

>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')

这种方法是在运行时修改,运行结束后失效。

二是设置环境变量 PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置 Path 环境变量类似。注意只需要添加你自己的搜索路径,Python 自己本身的搜索路径不受影响。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。