Python进阶

函数使用

把函数作为参数

1
2
3
4
5
# 把函数作为参数
import math
def add_sqrt(x, y, f):
return f(x) + f(y)
print add(25, 9, math.sqrt)

python内置高级函数

map()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
# map()函数:它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回。

# 例1、通过map()函数将不规范的英文名字列表转规范
def format_name(s):
return s[0].upper() + s[1:].lower()

print map(format_name, ['adam', 'LISA', 'barT'])

# 例2、把list的每个元素都作平方
def fn(x):
return x*x
print map(fn, [1, 2, 3, 4, 5, 6, 7, 8, 9])
# 结果:[1, 4, 9, 10, 25, 36, 49, 64, 81]

reduce()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# reduce()函数:接收参数为一个函数 f,一个list。reduce()对list的每个元素反复调用函数f,并返回最终结果值。
# 例1:
def f(x, y):
return x + y
print reduce(f, [1, 3, 5, 7, 9])
# 计算过程:
# 先计算头两个元素:f(1, 3),结果为4;
# 再把结果和第3个元素计算:f(4, 5),结果为9;
# 再把结果和第4个元素计算:f(9, 7),结果为16;
# 再把结果和第5个元素计算:f(16, 9),结果为25;
# 由于没有更多的元素了,计算结束,返回结果25。

# reduce()还可以接收第3个可选参数,作为计算的初始值。
reduce(f, [1, 3, 5, 7, 9], 100)
# 第一轮计算就是:f(100, 1)

filter()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# filter()函数:接收一个函数 f 和一个list,这个函数 f 的作用是对每个元素进行判断,返回 True或 False,filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list。
# 例1,要从一个list [1, 4, 6, 7, 9, 12, 17]中删除偶数,保留奇数,首先,要编写一个判断奇数的函数:
def is_odd(x):
return x % 2 == 1
print filter(is_odd, [1, 4, 6, 7, 9, 12, 17])
# 结果:[1, 7, 9, 17]


# 例2,删除 None 或者空字符串: s.strip(rm) 删除 s 字符串中开头、结尾处的 rm 序列的字符。当rm为空时,默认删除空白符(包括'\n', '\r', '\t', ' ')
def is_not_empty(s):
return s and len(s.strip()) > 0
print filter(is_not_empty, ['test', None, '', 'str', ' ', 'END'])
# 结果:['test', 'str', 'END']


# 用filter()过滤出1~100中平方根是整数的数
import math
def is_sqr(x):
r = int(math.sqrt(x))
return r*r==x
print filter(is_sqr, range(1, 101))

sorted()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# sorted()函数:可以接收一个list和一个自定义的排序函数
sorted([36, 5, 12, 9, 21])

# 自定义比较函数:传入两个待比较的元素 x, y,如果 x 应该排在 y 的前面,返回 -1,如果 x 应该排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。
# 注意:字符串默认按照ASCII码大小来比较,'Z'的ASCII码比'a'小,即大写的字母会排在前面。
# 倒序排序
def reversed_cmp(x, y):
if x > y:
return -1
if x < y:
return 1
return 0

sorted([36, 5, 12, 9, 21], reversed_cmp)

# 返回函数
def calc_prod(lst):
def lazy_prod():
def f(x,y):
return x * y
return reduce(f, lst, 1)
return lazy_prod

f = calc_prod([1, 2, 3, 4])
print f()

python 中的闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# python 中的闭包
# 希望一次返回3个函数,分别计算1x1,2x2,3x3。但实际结果:全部都是 9,原因就是当count()函数返回了3个函数时,这3个函数所引用的变量 i 的值已经变成了3。由于f1、f2、f3并没有被调用,所以,此时他们并未计算 i*i
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
# 修改后:f函数可以正确地返回一个闭包g,g所引用的变量j不是循环变量,因此将正常执行。
def count():
fs = []
for i in range(1, 4):
def f(j):
def g():
return j*j
return g
r = f(i)
fs.append(r)
return fs
f1, f2, f3 = count()
print f1(), f2(), f3()

python 中的匿名函数 lambda 表达式

1
2
3
4
# python 中的匿名函数 lambda 表达式
# 匿名函数有个限制,就是只能有一个表达式,不写return,返回值就是该表达式的结果。
# 以map()函数为例:lambda x: x * x 实际上代替了一个计算 x*x 的函数
map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])

无参数decorator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 无参数decorator
# decorator 本质上就是一个高阶函数,它接收一个函数作为参数,然后,返回一个新函数。使用 decorator 用Python提供的 @ 语法
# 即:对于加了@xxx 的函数本身进行功能扩展

# 例子1
# 定义一个 @logCallMethodName 来记录被调用的函数名
def logCallMethodName(f):
def fn(x):
print 'call ' + f.__name__ + '()...'
return f(x)
return fn

# 对一个实现阶乘的函数添加 @logCallMethodName
@logCallMethodName
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))

# 调用函数
print factorial(10)

# 输出内容
call factorial()...
3628800

# 但是被加@logCallMethodName 的函数必须是只能有一个参数,以下的代码就会报错
@logCallMethodName
def add(x, y):
return x + y
print add(1, 2)

# 如果要自适应任何参数定义的函数,可以利用Python的 *args 和 **kw,保证任意个数的参数总是能正常调用:
def log(f):
def fn(*args, **kw):
print 'call ' + f.__name__ + '()...'
return f(*args, **kw)
return fn


# 例子2
# @performance,它可以打印出函数调用的时间。
import time

# 声明@
def performance(f):
def fn(*args, **kw):
startTime = time.time()
r = f(*args, **kw)
endTime = time.time()
print 'call %s() in %fs' %(f.__name__, (endTime - startTime))
return r
return fn

# 给函数添加 @
@performance
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
# 函数调用
print factorial(10)

带参数decorator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 带参数decorator
# 对于以下的装饰函数,可以发现,对于被装饰的函数,log打印的语句是不能变的(除了函数名)。
def log(f):
def fn(x):
print 'call ' + f.__name__ + '()...'
return f(x)
return fn
# 希望打印出'[INFO] call xxx()...',有的函数不太重要,希望打印出'[DEBUG] call xxx()...',这时,log函数本身就需要传入'INFO'或'DEBUG'这样的参数
# 类似于:
@log('DEBUG')
def my_func():
...

上面的写法简单翻译过来就是:
log_decorator = log('DEBUG')
my_func = log_decorator(my_func)
又相当于:
log_decorator = log('DEBUG')
@log_decorator
def my_func():
...

# 于是有了如下的三层嵌套的代码
def log(prefix):
def log_decorator(f):
def wrapper(*args, **kw):
print '[%s] %s()...' % (prefix, f.__name__)
return f(*args, **kw)
return wrapper
return log_decorator

@log('DEBUG')
def test():
pass
print test()

# 拆分开就是:
# 标准decorator:
def log_decorator(f):
def wrapper(*args, **kw):
print '[%s] %s()...' % (prefix, f.__name__)
return f(*args, **kw)
return wrapper
return log_decorator
# 返回decorator:
def log(prefix):
return log_decorator(f)
# 拆开之后调用会失败,因为在3层嵌套的decorator定义中,最内层的wrapper引用了最外层的参数prefix,所以,把一个闭包拆成普通的函数调用会比较困难。

# 例子2
# 将 无参数decorator 中例子2修改为参数输入 's'或'ms' 来控制输出的调用时间单位。
import time

def performance(unit):
def perf_decorator(f):
def wrapper(*args, **kw):
startTime = time.time()
r = f(*args, **kw)
endTime = time.time()
costTime = (endTime - startTime) * 1000 if unit == 'ms' else endTime - startTime
print 'call %s() in %f %s' % (f.__name__, costTime, unit)
return r
return wrapper
return perf_decorator

@performance('ms')
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))

print factorial(10)

# 需要注意的是:decorator 装饰函数返回的函数是不同于原有函数的,
# 我们可以在上面最终调用的地方输出函数名:
print factorial.__name__
# 会发现,实际输出的是 wrapper,即:我们在装饰函数内所返回的函数名。
# 这时Python内置的 functools 可以把原函数的一些属性复制到新函数中(@functools.wraps应该作用在返回的新函数上。)
import time, functools
def performance(unit):
def perf_decorator(f):
@functools.wraps(f)
def wrapper(*args, **kw):
...

偏函数functools.partial

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 偏函数functools.partial
# 当一个函数有很多参数时,调用者就需要提供多个参数。如果减少参数个数,就可以简化调用者的负担。
# 比如,int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:
print int('12345')
# 但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做 N 进制的转换:
print int('12345', base=8)
print int('12345', 16)
# 假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
def int2(x, base=2):
return int(x, base)
print int2('1000000')
# functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
import functools
int2 = functools.partial(int, base=2)
int2('1000000')

模块

区分包和普通目录

包下面一定要有一个__init__.py文件(且每一层都要有),哪怕是一个空文件。

Image

导入模块

要使用一个模块,我们必须首先导入该模块。Python使用import语句导入一个模块。例如,导入系统自带的模块 math:

1
import math

如果我们只希望导入用到的math模块的某几个函数,而不是所有函数,可以用下面的语句:

1
from math import pow, sin, log

如果遇到名字冲突怎么办?比如math模块有一个log函数,logging模块也有一个log函数,如果同时使用,如何解决名字冲突?

如果使用import导入模块名,由于必须通过模块名引用函数名,因此不存在冲突:

1
2
3
import math, logging
print math.log(10) # 调用的是math的log函数
logging.log(10, 'something') # 调用的是logging的log函数

如果使用 from…import 导入 log 函数,势必引起冲突。这时,可以给函数起个“别名”来避免冲突:

1
2
3
4
from math import log
from logging import log as logger # logging的log现在变成了logger
print log(10) # 调用的是math的log
logger(10, 'import from logging') # 调用的是logging的log

所以导入模块的方式可总结如下四种(以导入Python的os.path模块为例,该模块提供了 isdir() 和 isfile()函数,判断指定的目录和文件是否存在):

1
2
3
4
import os
import os.path
from os import path
from os.path import isdir, isfile

python中动态导入模块(导入模块时出现异常的处理)

如果导入的模块不存在,Python解释器会报 ImportError 错误:

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

例如:Python 2.6/2.7提供了json 模块,但Python 2.5以及更早版本没有json模块,不过可以安装一个simplejson模块,这两个模块提供的函数签名和功能都一模一样。

所以导入方式可以以如下的方式:

1
2
3
4
try:
import json
except ImportError:
import simplejson as json

安装第三方模块

1
2
3
4
# 方式1
easy_install
# 方式2(推荐,已内置到2.7+)
pip install

第三方模块可以从以下地址获取到:

https://pypi.org/

类与实例

定义类并创建实例

1
2
class Person(object):
pass

按照 Python 的编程习惯,类名以大写字母开头,紧接着是(object),表示该类是从哪个类继承下来的。

1
2
3
4
5
6
7
class Person(object):
pass
xiaoming = Person()
xiaohong = Person()
print xiaoming
print xiaohong
print xiaoming == xiaohong

实例赋值:(两个 Person 类的实例的 list,并给两个实例的 name 赋值,然后按照 name 进行排序。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person(object):
pass

p1 = Person()
p1.name = 'Bart'

p2 = Person()
p2.name = 'Adam'

p3 = Person()
p3.name = 'Lisa'

L1 = [p1, p2, p3]
L2 = sorted(L1, lambda p1, p2: cmp(p1.name, p2.name))

print L2[0].name
print L2[1].name
print L2[2].name

初始化实例属性(类似与Java的构造函数)

在定义 Person 类时,可以为Person类添加一个特殊的__init__()方法,当创建实例时,init()方法被自动调用,我们就能在此为每个实例都统一加上以下属性:

1
2
3
4
5
class Person(object):
def __init__(self, name, gender, birth):
self.name = name
self.gender = gender
self.birth = birth

注意:init() 方法的第一个参数必须是 self(也可以用别的名字,但建议使用习惯用法),后续参数则可以自由指定,和定义函数没有任何区别。相应地,创建实例时,就必须要提供除 self 以外的参数:

1
2
xiaoming = Person('Xiao Ming', 'Male', '1991-1-1')
xiaohong = Person('Xiao Hong', 'Female', '1992-2-2')

访问属性使用 . 操作符:

1
2
3
4
print xiaoming.name
# 输出 'Xiao Ming'
print xiaohong.birth
# 输出 '1992-2-2'

定义Person类的__init__方法,除了接受 name、gender 和 birth 外,还可接受任意关键字参数,并把他们都作为属性赋值给实例:

(还可以通过 setattr(self, ‘name’, ‘xxx’) 设置属性。)

1
2
3
4
5
6
7
8
9
10
class Person(object):
def __init__(self, name, gender, birth, **kw):
self.name = name
self.gender = gender
self.birth = birth
for k, v in kw.iteritems():
setattr(self, k, v)
xiaoming = Person('Xiao Ming', 'Male', '1990-1-1', job='Student')
print xiaoming.name
print xiaoming.job

对象属性访问控制

Python对属性权限的控制是通过属性名来实现的,如果一个属性由双下划线开头(__),该属性就无法被外部访问。

1
2
3
4
5
6
7
8
9
class Person(object):
def __init__(self, name, score):
self.name = name
self.__score = score

p = Person('Bob', 59)

print p.name
print p.__score

python中创建类属性(类似于Java中的静态变量)

类本身也是一个对象,如果在类上绑定一个属性,则所有实例都可以访问类的属性,并且,所有实例访问的类属性都是同一个!也就是说,实例属性每个实例各自拥有,互相独立,而类属性有且只有一份。

定义类属性可以直接在 class 中定义:

1
2
3
4
class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name

获取类属性:

1
2
print Person.address
# => Earth

对一个实例调用类的属性也是可以访问的,所有实例都可以访问到它所属的类的属性:

1
2
3
4
5
6
p1 = Person('Bob')
p2 = Person('Alice')
print p1.address
# => Earth
print p2.address
# => Earth
1
2
3
4
5
Person.address = 'China'
print p1.address
# => 'China'
print p2.address
# => 'China'

python中类属性和实例属性名字冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name

p1 = Person('Bob')
p2 = Person('Alice')

print 'Person.address = ' + Person.address

p1.address = 'China'
print 'p1.address = ' + p1.address

print 'Person.address = ' + Person.address
print 'p2.address = ' + p2.address

结果如下:

1
2
3
4
Person.address = Earth
p1.address = China
Person.address = Earth
p2.address = Earth

我们发现,在设置了 p1.address = ‘China’ 后,p1访问 address 确实变成了 ‘China’,但是,Person.address和p2.address仍然是’Earch’,怎么回事?

原因是 p1.address = ‘China’并没有改变 Person 的 address,而是给 p1这个实例绑定了实例属性address ,对p1来说,它有一个实例属性address(值是’China’),而它所属的类Person也有一个类属性address,所以:

访问 p1.address 时,优先查找实例属性,返回’China’。

访问 p2.address 时,p2没有实例属性address,但是有类属性address,因此返回’Earth’。

可见,当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问。

当我们把 p1 的 address 实例属性删除后,访问 p1.address 就又返回类属性的值 ‘Earth’了:

1
2
3
del p1.address
print p1.address
# => Earth

可见,千万不要在实例上修改类属性,它实际上并没有修改类属性,而是给实例绑定了一个实例属性。

python中定义实例方法(类似Java中的 getter/setter 方法)

一个实例的私有属性就是以__开头的属性,无法被外部访问,那这些属性定义有什么用?

虽然私有属性无法从外部访问,但是,从类的内部是可以访问的。除了可以定义实例的属性外,还可以定义实例的方法。

实例的方法就是在类中定义的函数,它的第一个参数永远是 self,指向调用该方法的实例本身,其他参数和一个普通函数是完全一样的:

1
2
3
4
5
6
7
8
9
class Person(object):
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name

p1 = Person('Bob')
print p1.get_name() # self不需要显式传入
# => Bob

例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person(object):
def __init__(self, name, score):
self.name = name
self.__score = score
def get_grade(self):
if self.__score >= 80:
return 'A'
if self.__score >= 60:
return 'B'
return 'C'

p1 = Person('Bob', 90)
p2 = Person('Alice', 65)
p3 = Person('Tim', 48)

print p1.get_grade()
print p2.get_grade()
print p3.get_grade()

python中定义类方法(类似Java中的静态方法)

通过标记一个 @classmethod,该方法将绑定到 Person 类上,而非类的实例。类方法的第一个参数将传入类本身,通常将参数名命名为 cls

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person(object):
__count = 0

@classmethod
def how_many(cls):
return cls.__count
def __init__(self, name):
self.name = name
Person.__count = Person.__count + 1

print Person.how_many()
p1 = Person('Bob')
print Person.how_many()

Python进阶
https://binbiubiu.github.io/20200727140000/
作者
Binbiubiu
发布于
2020年7月27日