Python补充

Python 补充

【有问题的章节】

字符串和编码

函数参数

返回函数

装饰

Hint

parameter:形参,指的是函数中的参数名称:

1
2
def add(x,y):>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>此处x,y为形参。
return x+y

argument:实参,指的是你提供给函数调用的值:

1
2
3
x=1
y=2
add(x,y)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>此处x,y就变为实参了。

基本知识

缩进

约定缩进为4个空格

重构代码时,必须重新检查缩进是否正确

大小写

python大小写敏感

数据表示

进制表示

进制 前缀
2 0b
8 0o
16 0x

示例:0xff000xa5b4c3d2

数字中间可以用_分隔

1
2
10_000_000_000 = 10000000000
0xa1b2_c3d4 = 0xa1b2c3d4

浮点数(也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,比如,1.23x10^9和12.3x10^8是完全相等的)

1.23x10^9表示为1.23e9

字符串

字符串是以单引号'或双引号"括起来的任意文本

如果字符串内部既包含'又包含"怎么办?可以用转义字符\来标识,比如:

1
'I\'m \"OK\"!'

表示的字符串内容是:

1
I'm "OK"!

转义字符\可以转义很多字符,比如\n表示换行,\t表示制表符,字符\本身也要转义,所以\\表示的字符就是\,可以在Python的交互式命令行用print()打印字符串看看:

1
2
3
4
5
6
7
8
>>> print('I\'m ok.')
I'm ok.
>>> print('I\'m learning\nPython.')
I'm learning
Python.
>>> print('\\\n\\')
\
\

如果字符串里面有很多字符都需要转义,就需要加很多\,为了简化,Python还允许用r''表示''内部的字符串默认不转义,可以自己试试:

1
2
3
4
>>> print('\\\t\\')
\ \
>>> print(r'\\\t\\')
\\\t\\

如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用'''...'''的格式表示多行内容,可以自己试试:

1
2
3
4
5
6
>>> print('''line1
... line2
... line3''')
line1
line2
line3

空值

空值是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。

常量

在Python中,通常用全部大写的变量名表示常量:

1
PI = 3.14159265359

但事实上PI仍然是一个变量,Python根本没有任何机制保证PI不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法,如果你一定要改变变量PI的值,也没人能拦住你。

范围

Python的整数没有大小限制,而某些语言的整数根据其存储长度是有大小限制的,例如Java对32位整数的范围限制在-2147483648-2147483647

Python的浮点数也没有大小限制,但是超出一定范围就直接表示为inf(无限大)。

元组(tuple)

如果要定义一个空的tuple,可以写成()

1
2
3
>>> t = ()
>>> t
()

只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:

1
2
3
>>> t = (1,)
>>> t
(1,)

Python在显示只有1个元素的tuple时,也会加一个逗号,,以免你误解成数学计算意义上的括号。

tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!

不可变对象

对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。

字典(dict)

dict的key必须是不可变对象.

在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key

集合(set)

要创建一个set,需要提供一个list作为输入集合:

1
2
3
>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}

set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:

1
2
3
4
5
6
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}

set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象.

函数

在交互式命令行通过help()查看函数的帮助信息。

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:

1
2
3
>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1

返回值

返回单值

如果没有return语句,函数执行完毕返回None

此外return None可以简写为return

返回多值

Python的函数返回多值其实就是返回一个tuple

空函数

pass语句用来作为占位符.

如果想定义一个什么事也不做的空函数,可以用pass语句:

1
2
def nop():
pass

用于其他语句:

1
2
if age >= 18:
pass

缺少了pass,代码运行就会有语法错误。

函数的参数

位置参数
1
2
3
4
5
6
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s

power(x, n)函数有两个参数:xn,这两个参数都是位置参数(调用函数时,传入的两个值按照位置顺序依次赋给参数xn

默认参数
1
2
3
4
5
6
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s

必选参数在前,默认参数在后默认参数必须指向不变对象!

示例:

1
2
3
4
5
6
7
8
def enroll(name, gender, age=6, city='Beijing'):
print('name:', name)
print('gender:', gender)
print('age:', age)
print('city:', city)

调用1:enroll('Bob', 'M', 7)
调用2:enroll('Adam', 'M', city='Tianjin')

【关于默认参数不变性的理解】

先定义一个函数,传入一个list,添加一个END再返回:

1
2
3
def add_end(L=[]):
L.append('END')
return L

当你正常调用时,结果似乎不错:

1
2
3
4
>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']

当你使用默认参数调用时,一开始结果也是对的:

1
2
>>> add_end()
['END']

但是,再次调用add_end()时,结果就不对了:

1
2
3
4
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了'END'后的list。

原因解释如下:

Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

定义默认参数要牢记一点:默认参数必须指向不变对象

要修改上面的例子,我们可以用None这个不变对象来实现:

1
2
3
4
5
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L

现在,无论调用多少次,都不会有问题:

1
2
3
4
>>> add_end()
['END']
>>> add_end()
['END']

为什么要设计strNone这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。


可变参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。

1
2
3
4
5
6
7
8
9
10
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum

>>> calc(1, 2)
5
>>> calc()
0
list/tuple做可变参数

在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:

1
2
3
>>> nums = [1, 2, 3]
>>> calc(*nums)
14
关键字参数

允许传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

1
2
3
4
5
6
7
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)

>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

也可以先组装成dict,再传递参数:

1
2
3
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
命名关键字参数

需要限制关键字名字时使用。

1
2
3
4
5
6
def person(name, age, *, city, job): 
# 只接受city、job, 特殊分隔符`*`后面的参数被视为命名关键字参数。
print(name, age, city, job)

>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer # 命名关键字参数必须传入参数名

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

1
2
def person(name, age, *args, city, job):
print(name, age, args, city, job)
参数顺序

参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数

对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

遗留的问题【等学完了错误处理再回来解决】
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
# -*- coding: utf-8 -*-

import math

# def mul(x, *args):
# ans = x
# for num in args:
# ans = ans * num
# return ans

def mul(*args):
if len(args) == 0:
print(123213)
return TypeError("Lack of numbers")
else:
ans = 1
for num in args:
ans = ans * num
return ans


# 测试
print('mul(5) =', mul(5))
print('mul(5, 6) =', mul(5, 6))
print('mul(5, 6, 7) =', mul(5, 6, 7))
print('mul(5, 6, 7, 9) =', mul(5, 6, 7, 9))
if mul(5) != 5:
print('测试失败!')
elif mul(5, 6) != 30:
print('测试失败!')
elif mul(5, 6, 7) != 210:
print('测试失败!')
elif mul(5, 6, 7, 9) != 1890:
print('测试失败!')
else:
try:
mul()
print('测试失败!')
except TypeError:
print('测试成功!')

课后练习

注释掉的是正确答案,现在还不太懂。

(文件名:ex_function_para.py)

高级特性

切片

注意点

倒数第一位置的索引是-1

示例
1
2
3
def is_palindrome(n): #判断一个数是否是回文数
l = [ch for ch in str(n)]
return l == l[::-1]

迭代

通过for循环来遍历这个listtuple,这种遍历我们称为迭代(Iteration)

  • 通过collections.abc模块的Iterable类型判断对象是否可迭代:
1
2
3
4
5
6
7
>>> from collections.abc import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
  • for循环中同时迭代索引和元素本身:
1
2
3
4
5
6
7
>>> for i, value in enumerate(['A', 'B', 'C']): 
#`enumerate`函数可以把一个`list`变成索引-元素对
... print(i, value)
...
0 A
1 B
2 C

列表生成式

  • 基本示例:
1
2
3
4
5
6
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
  • 列出当前目录下的所有文件和目录名现:
1
2
3
>>> import os # 导入os模块,模块的概念后面讲到
>>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode']

在一个列表生成式中,for前面的if ... else是表达式,而for后面的if是过滤条件,不能带else

  • if … else的位置
1
2
3
4
5
>>> [x for x in range(1, 11) if x % 2 == 0] # 过滤条件
[2, 4, 6, 8, 10]

>>> [x if x % 2 == 0 else -x for x in range(1, 11)] # 表达式
[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]

2022-7-31 复习到generator


生成器

Python中,这种一边循环一边计算的机制,称为生成器:generator

  • 把一个列表生成式的[]改成(),就创建了一个generator:
1
2
3
4
5
6
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>
  • 函数定义中包含yield关键字,成为generator函数,调用一个generator函数将返回一个generator:
1
2
3
4
5
6
7
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'

generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

正确的写法是创建一个generator对象,然后不断对这一个generator对象调用next()

1
2
3
4
5
6
7
8
9
10
>>> g = odd()
>>> next(g)
step 1
1
>>> next(g)
step 2
3
>>> next(g)
step 3
5

2022-7-27 学到迭代器


可迭代对象

可以直接作用于for循环的对象统称为可迭代对象Iterable,可以使用isinstance()判断一个对象是否是Iterable对象:

1
2
3
4
5
6
7
8
9
10
11
>>> from collections.abc import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

可迭代对象包括:

  • 集合数据类型,如listtupledictsetstr等;

  • generator,包括生成器和带yield的generator function。

迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器Iterator。可以使用isinstance()判断一个对象是否是Iterator对象:

1
2
3
4
5
6
7
8
9
>>> from collections.abc import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

listdictstrIterable变成Iterator可以使用iter()函数:

1
2
3
4
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

Python的for循环本质上就是通过不断调用next()函数实现的,例如:

1
2
for x in [1, 2, 3, 4, 5]:
pass

实际上完全等价于:

1
2
3
4
5
6
7
8
9
10
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break

函数式编程

变量指向函数

1
2
3
>>> f = abs
>>> f(-10)
10

函数名其实就是指向函数的变量!

高阶函数

一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数

示例:

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

调用add(-5, 6, abs)时,参数xyf分别接收-56abs,计算过程为:

1
2
3
4
5
x = -5
y = 6
f = abs
f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
return 11

map

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

示例:

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

# 注意:返回值为迭代器,需要转化为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把结果继续和序列的下一个元素做累积计算,其效果就是:

1
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

示例:

1
2
3
4
5
6
7
8
9
10
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))

filter

示例:删掉一个序列中的空字符串

1
2
3
4
5
def not_empty(s):
return s and s.strip()

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

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

【待理解:filter实现素数筛子(ex_filter_prime_sieve.py)】

sorted

sorted()函数可以接收一个key函数来实现自定义的排序

示例:

按绝对值大小排序:
1
2
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

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

1
2
3
list = [36, 5, -12, 9, -21]

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

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

1
2
3
keys排序结果 => [5, 9,  12,  21, 36]
| | | | |
最终结果 => [5, 9, -12, -21, 36]
字符串排序

默认排序:

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

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

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

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

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

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

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

看到返回函数 2022-7-28


函数作为返回值

匿名函数(lambda)

Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction)

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

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

可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

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

装饰器

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。本质上,decorator就是一个返回函数的高阶函数。

所以,我们要定义一个能打印日志的decorator,可以定义如下:

1
2
3
4
5
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper

偏函数

采用functools.partial创建。

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

示例:

1
2
3
4
5
6
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

模块

使用模块的示例:

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
# --python标准文件模板--

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

' a test module ' # 模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释

__author__ = 'Michael Liao' # 作者

# --python标准文件模板--

import sys # 导入的实质是使变量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测试可以让模块通过命令行执行时额外进行测试
if __name__=='__main__':
test()

学到面向对象高级编程 2022-7-30


一些实际问题

字符画

现在自己写的尺寸有些问题


Python补充
http://example.com/2022/12/17/Python补充/
作者
Thunderbolt
发布于
2022年12月17日
许可协议