Python

安装

1
brew install python3
1
2
3
4
guxc@guxuchengdeMacBook-Pro ~ % python
Python 3.11.6 (main, Nov 2 2023, 04:39:40) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()

基础使用

hello,world

1
vi helllo.py
1
print('hello, world')
1
python hello.py

或者在mac和linux也支持直接运行

1
2
3
#!/usr/bin/env python3

print('hello, world')
1
2
3
4
$ chmod a+x hello.py
$ ./hello.py
hello, world

input/print

1
2
name = input('please enter your name: ')
print('hello',name)
1
2
3
python hello.py
please enter your name: world
hello world

数据类型和变量

浮点:

很大或者很小的浮点数,必须用科学计数法表示,把10用e替代,1.23x109就是1.23e9,或者12.3e8,0.000012可以写成1.2e-5

变量

python是动态语言

1
2
3
4
a = 123 # a是整数
print(a)
a = 'ABC' # a变为字符串
print(a)

常量

通常大写变量名,习惯非机制

1
PI = 3.14159265359

除法

1
2
>>> 10 / 3
3.3333333333333335

地板除

1
2
>>> 10 // 3
3

字符串和编码

Python 3的字符串使用Unicode,直接支持多语言。

1
2
>>> print('包含中文的str')
包含中文的str

当str和bytes互相转换时,需要指定编码。

以Unicode表示的str通过encode()方法可以编码为指定的bytes

1
2
3
4
>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'

要把bytes变为str,就需要用decode()方法:

1
2
3
4
>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'

要计算str包含多少个字符,可以用len()函数:

1
2
3
4
>>> len('ABC')
3
>>> len('中文')
2

当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。

通常在文件开头写上这两行

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

格式化字符串

1
2
3
4
>>> 'Hello, %s' % 'world'
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'
占位符 替换内容
%d 整数
%f 浮点数
%s 字符串
%x 十六进制整数
1
2
print('%2d-%02d' % (3, 1))
print('%.2f' % 3.1415926)

list和tuple

list是一种有序的集合,可以随时添加和删除其中的元素。

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
classmates = ['Michael', 'Bob', 'Tracy']
//取第一个
>>> classmates[0]
'Michael'

//取倒数第一个
>>> classmates[-1]
'Tracy'

//追加末尾(['Michael', 'Bob', 'Tracy', 'Adam'])
>>> classmates.append('Adam')

//插到索引为1的位置(['Michael', 'Jack', 'Bob', 'Tracy', 'Adam'])
>>> classmates.insert(1, 'Jack')

//删除list末尾的元素(['Michael', 'Jack', 'Bob', 'Tracy'])
>>> classmates.pop()

//删除指定位置(索引为1)的元素(['Michael', 'Bob', 'Tracy'])
>>> classmates.pop(1)

//替换元素(['Michael', 'Sarah', 'Tracy'])
>>> classmates[1] = 'Sarah'

//list里面的元素的数据类型也可以不同
>>> L = ['Apple', 123, True]


//list元素也可以是另一个list
>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4

>>> s[2][1]
'php'


//空的list,len 0
>>> L = []
>>> len(L)
0

tuple和list非常类似,但是tuple一旦初始化就不能修改

当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> t = (1, 2)
>>> t
(1, 2)

//只有1个元素的tuple定义时必须加一个逗号,
>>> t = (1,)
>>> t
(1,)


//tuple里的list可以变
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

条件判断/match

1
2
3
//只要x是非零数值、非空字符串、非空list等,就判断为True,否则为False。
if x:
print('True')
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
score = 'B'

match score:
case 'A':
print('score is A.')
case 'B':
print('score is B.')
case 'C':
print('score is C.')
case _: # _表示匹配到其他任何情况
print('score is ???.')



age = 15

match age:
//第一个case x if x < 10表示当age < 10成立时匹配
case x if x < 10:
print(f'< 10 years old: {x}')
case 10:
print('10 years old.')
//第三个case 11|12|...|18能匹配多个值,用|分隔。
case 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18:
print('11~18 years old.')
case 19:
print('19 years old.')
case _:
print('not sure.')

循环

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
//list输出
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)

//数字相加
sum = 0
for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
sum = sum + x
print(sum)

//range()函数,可以生成一个整数序列
>>> list(range(5))
[0, 1, 2, 3, 4]


//0加到100
sum = 0
for x in range(101):
sum = sum + x
print(sum)

//也支持while
sum = 0
n = 99
while n > 0:
sum = sum + n
n = n - 2
print(sum)

dict和set

dict在其他语言中也称为map

list是可变的,就不能作为key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
d['Adam'] = 67

>>> 'Thomas' in d
False

//如果key不存在,可以返回None,或者自己指定的value,返回None的时候Python的交互环境不显示结果。
>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1

//要删除一个key
>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}

set不存储value,key不能重复

不能放入可变对象,比如list

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
>>> s = {1, 2, 3}
>>> s
{1, 2, 3}

//提供一个list作为输入集合
>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}


//重复元素在set中自动被过滤
>>> s = {1, 1, 2, 2, 3, 3}
>>> s
{1, 2, 3}

//添加元素
>>> s.add(4)

//删除元素
>>> s.remove(4)

//数学交集
>>> s1 = {1, 2, 3}
>>> s2 = {2, 3, 4}
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}

不可变对象

str是不变对象,而list是可变对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> a = 'abc'
>>> a.replace('a', 'A')
'Abc'
>>> a
'abc'

//这里replace方法创建了一个新字符串'Abc'并返回
//如果我们用变量b指向该新字符串,就容易理解了,变量a仍指向原有的字符串'abc',但变量b却指向新字符串'Abc'了:
>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'

函数

1
2
3
4
5
6
7
def my_abs(x):
if x >= 0:
return x
else:
return -x

print(my_abs(-99))

如果你已经把my_abs()的函数定义保存为abstest.py文件了

用from abstest import my_abs来导入my_abs()函数,注意abstest是文件名(不含.py扩展名)

1
2
3
>>> from abstest import my_abs                          │
│>>> my_abs(-9) │
│9

空函数

pass可以用来作为占位符,让代码能运行起来

1
2
def nop():
pass

pass还可以用在其他语句里,比如:

1
2
if age >= 18:
pass

返回多个值

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

1
2
3
4
5
6
7
import math

//angle默认0,可以不传
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny

默认参数

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

默认参数必须指向不变对象

如果指向可变对象比如list

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

多次执行

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

可以修改为

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']

可变参数

在参数前面加了一个*号,在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:

1
2
3
4
5
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
1
2
3
4
>>> calc(1, 2)
5
>>> calc()
0

有一个list或者tuple调用可变参数时

1
2
3
>>> nums = [1, 2, 3]
>>> calc(*nums)
14

*nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。

关键字参数

同时支持必选参数和可选参数

kw是一个dict

1
2
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
1
2
3
4
5
6
7
8
>>> person('Michael', 30)
name: Michael age: 30 other: {}

>>> 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'}

*args是可变参数,args接收的是一个tuple;

**kw是关键字参数,kw接收的是一个dict。

迭代

默认dict迭代key

1
2
3
4
5
6
7
8
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
... print(key)
...
a
c
b

如果要迭代value

1
for value in d.values()

如果要同时迭代key,value

1
2
3
4
5
6
7
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
... print(k, '=', v)
...
y = B
x = A
z = C

如果要生成[1x1, 2x2, 3x3, …, 10x10]

1
2
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

1
2
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

两层循环实现全排列

1
2
>>> [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']

生成器

在循环的过程中不断推算出后续的元素,不必创建完整的list

1
2
L = [x * x for x in range(10)]
g = (x * x for x in range(10))

创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。

1
2
3
4
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)

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'

如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator:

1
2
3
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>

普通函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

高阶函数

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

print(add(-5, 6, abs))

模块

一个abc.py的文件就是一个名字叫abc的模块

一个顶层包名,比如mycompany,按照如下目录存放

1
2
3
4
mycompany
├─ __init__.py
├─ abc.py
└─ xyz.py

init.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。init.py可以是空文件,也可以有Python代码

可以有多级目录,组成多级层次的包结构

1
2
3
4
5
6
7
8
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。

使用内建sys模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/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__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;

sys模块有一个argv变量,用list存储了命令行的所有参数

运行python3 hello.py获得的sys.argv就是[‘hello.py’];

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

if name==’main‘:

test()

这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

私有/非公开函数变量

通过_前缀来实现的,比如前面的__author__,或者_xxx,样的函数或变量就是非公开的(private),不应该被直接引用

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

第三方模块

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

如果你正在使用Mac或Linux,安装pip本身这个步骤就可以跳过了。

如果你正在使用Windows,请参考安装Python一节的内容,确保安装时勾选了pip和Add python.exe to Path。

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

1
pip install Pillow

推荐直接使用Anaconda,这是一个基于Python的数据处理和科学计算平台,它已经内置了许多非常有用的第三方库,我们装上Anaconda,就相当于把数十个第三方模块自动安装好了,非常简单易用。

可以从Anaconda官网下载GUI安装包,安装包有500~600M,所以需要耐心等待下载。下载后直接安装,Anaconda会把系统Path中的python指向自己自带的Python,并且,Anaconda安装的第三方模块会安装在Anaconda自己的路径下,不影响系统已安装的Python目录。

安装好Anaconda后,重新打开命令行窗口,输入python,可以看到Anaconda的信息:

1
2
3
4
5
│C:\> python                                             │
│Python 3.6.3 |Anaconda, Inc.| ... on win32 │
│Type "help", ... for more information. │
│>>> import numpy │
│>>> _

可以尝试直接import numpy等已安装的第三方模块。

from module_name import item_name

这里的 module_name 是你想要导入的模块的名字,item_name 是你希望从该模块中导入的对象(可以是函数、类、变量等)。

1
2
3
from math import sqrt
result = sqrt(16) # 直接使用sqrt函数,无需加math.前缀
print(result) # 输出 4.0
  • 解释:这会从 math 模块中导入 sqrt 函数,直接在代码中使用 sqrt()
1
2
3
from math import sqrt, pi
print(sqrt(25)) # 输出 5.0
print(pi) # 输出 3.141592653589793
  • 解释:从 math 模块中导入了多个对象,可以同时导入多个函数或变量。
1
2
from math import sqrt as square_root
print(square_root(25)) # 输出 5.0
  • 解释:通过 as 关键字,给导入的函数或类起一个别名,方便在当前脚本中使用。

错误

python内置了一套try…except…finally…的错误处理机制

1
2
3
4
5
6
7
8
9
try:
print('try...')
r = 10 / 0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')

执行完except后,如果有finally语句块,则执行finally语句块(可以没有finally语句)

计算10 / 0

1
2
3
4
try...
except: division by zero
finally...
END

计算10/2

1
2
3
4
try...
result: 5
finally...
END

记录错误,日志收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import logging

def foo(s):
return 10 / int(s)

def bar(s):
return foo(s) * 2

def main():
try:
bar('0')
except Exception as e:
logging.exception(e)

main()
print('END')

程序打印完错误信息后会继续执行

抛出错误/定义错误

定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例:

1
2
3
4
5
6
7
8
9
10
class FooError(ValueError):
pass

def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n

foo('0')

如果可以选择Python已有的内置的错误类型(比如ValueError,TypeError),尽量使用Python内置的错误类型。

调试

方法一:print()把可能有问题的变量打印出来看看

1
2
3
4
5
6
7
8
9
def foo(s):
n = int(s)
print('>>> n = %d' % n)
return 10 / n

def main():
foo('0')

main()

方法二:断言,print()的地方用assert替代,如果断言失败,assert语句本身就会抛出AssertionError

1
2
3
4
5
6
7
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n

def main():
foo('0')

启动Python解释器时可以用-O参数来关闭assert

1
2
3
4
$ python -O err.py
Traceback (most recent call last):
...
ZeroDivisionError: division by zero

方法三:logging

1
2
3
4
5
6
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
1
2
3
4
5
6
$ python err.py
INFO:root:n = 0
Traceback (most recent call last):
File "err.py", line 8, in <module>
print(10 / n)
ZeroDivisionError: division by zero

单元测试

1
2
3
4
5
6
7
8
9
10
11
12
class Dict(dict):
def __init__(self, **kw):
super().__init__(**kw)

def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

def __setattr__(self, key, value):
self[key] = value
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
import unittest

from mydict import Dict

class TestDict(unittest.TestCase):
def test_init(self):
d = Dict(a=1, b='test')
self.assertEqual(d.a, 1)
self.assertEqual(d.b, 'test')
self.assertTrue(isinstance(d, dict))

def test_key(self):
d = Dict()
d['key'] = 'value'
self.assertEqual(d.key, 'value')

def test_attr(self):
d = Dict()
d.key = 'value'
self.assertTrue('key' in d)
self.assertEqual(d['key'], 'value')

def test_keyerror(self):
d = Dict()
with self.assertRaises(KeyError):
value = d['empty']

def test_attrerror(self):
d = Dict()
with self.assertRaises(AttributeError):
value = d.empty


1
2
$ python -m unittest mydict_test

setUp与tearDown

setUp()和tearDown()方法,这两个方法会分别在每调用一个测试方法的前后分别被执行。

setUp()和tearDown()方法有什么用呢?设想你的测试需要启动一个数据库,这时,就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码:

面向对象

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

def print_score(self):
print('%s: %s' % (self.name, self.score))

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问

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

def print_score(self):
print('%s: %s' % (self.__name, self.__score))
1
2
3
4
5
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal(object):
def run(self):
print('Animal is running...')

class Dog(Animal):
pass

class Cat(Animal):
pass

dog = Dog()
dog.run()

cat = Cat()
cat.run()

slots

限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。

1
2
class Student(object):
__slots__ = ('name', 'age')
1
2
3
4
5
6
7
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用

@property

用于将类的方法变成属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Circle:
def __init__(self, radius):
self._radius = radius

@property
def radius(self):
return self._radius

@property
def area(self):
return 3.14 * (self._radius ** 2)

# 创建 Circle 对象
c = Circle(5)

# 通过属性访问 radius 和 area
print(c.radius) # 5
print(c.area) # 78.5

if name == ‘main‘:

用于判断当前模块是否作为主程序执行。它的作用是让某些代码仅在直接运行 Python 脚本时执行,而在该脚本作为模块导入到其他脚本时不会执行。

每个 Python 模块都有一个内置的特殊变量 __name__。它表示当前模块的名字。

  • 如果 Python 文件被直接运行,__name__ 的值将是 '__main__'
  • 如果 Python 文件被导入为模块,则 __name__ 的值将是模块的名称(即脚本文件的名称,不包括扩展名 .py

**if __name__ == '__main__':**** 是什么?**

这个条件语句用于检查当前脚本是否是作为主程序运行。如果是,则执行 **if** 块中的代码;如果脚本是作为模块被导入到其他程序中,则不会执行 **if** 块中的代码。



参考

https://liaoxuefeng.com/books/python/introduction/index.html