Python 从零到一

本文以实际工作案例出发,提炼出极简的Python教程,不啰嗦笔墨,旨在给大家梳理思路,快速上手。

基本概念

变量

与其他编程语言的变量基本一样,变量只是计算机内存中存储信息的一部分内存。变量可以处理不同类型的值,称为数据类型。基本的类型是数值与字符串。

数值

在Python有3种类型的数值:

  • 整型
  • 浮点型
  • 复数

字符串

字符串是字符的有序序列,也可以理解为其他语言中的字符数组。

可以使用单引号、双引号或三引号指示字符串。

定义一个字符串很简单,只是给变量赋值一个值而已。如下:

a = 'testops'
b = "testops"

注意:字符串是不可变的数据类型,这也就意味着一旦创建一个字符串,我们就不能修改它了。

访问字符串中的序列

我们可以通过索引或下标来访问字符串中的序列。如下:

str01 = 'testops is awesome'
print(str01[0]) # t
print(str01[3:5]) # to

如果索引超出字符串的长度会出现什么情况?

缩进

缩进是Python的一种语法,缩进用来表示不同的代码分组。同一层次的语句具有相同的缩进,每一组相同缩进的语句称为一个块。错误的缩进会引发缩进异常。

建议在每个缩进层次使用4个空格,但不要混合使用制表符和空格来缩进。这是因为在不同的平台,代码有可能不能正常工作。

运算符与表达式

运算符的优先级,

1 + 2 * 3
7
2 * 3 ** 2
18

# 算术运算符优先级高于比较运算符
2 * 3 > 1 + 2
True

# 比较运算符的优先级高于逻辑运算符
2 * 3 > 1 + 2 and True
True

(2 * 3 > 1 + 2) and True
True
# 拿不准的时候加括号

算术运算符

算术运算符通常只针对数值类型。注意与Python2的区别。

自然除,

>>> 3 / 5
0.6

整除,

>>> 3 // 5
0

逻辑运算符

参与逻辑运算的成员只能是bool类型,或者可以隐式转化为bool类型的类型。

and需要运算符两边都是True结果才为True

True and True
True

or 只要运算符两边任意一个为True,结果就为True,

True or False
True

短路,

not True
False

看一个例子,

def add(x, y):
    print('{0} + {1}'.format(x, y))
    return x + y

add(1, 3)
1 + 3
4

add(1, 3) > add(1, 2) and (2, 4) < add(3, 4)
1 + 3
1 + 2
2 + 4
3 + 4
True
    
# 逻辑运算总是从左到右计算,一旦能够决定表达式最终的值,将立刻停止计算,
# 并返回
add(1, 3) < add(1, 2) and (2, 4) < add(3, 4)
1 + 3
1 + 2
False

add(1, 3) > add(1, 2) or add(2, 4) < add(3, 4)
1 + 3
1 + 2
True

比较运算符

  1. 相等 ==
  2. 不等于 !=
  3. 大于 >
  4. 大于等于 >=
  5. 小于 <
  6. 小于等于 <=

几个例子,

1 == 1
True

1 == 2
False

1 != 2
True

1 > 2
False

2 > 1
True

2 >= 1
True

2 >= 2
True

除了==和!=,两边的类型相同。

位运算符

# 按位与
bin(60)
'0b111100'
# 0011 1100
bin(12)
'0b1100'
# 0000 1100
60 & 12
12

# 按位或
60 | 12
60
# 按位异或
# 相同为0,不同为1
60 ^ 12
48

# 取反
~60
-61

# 右移
60 >> 2
15

# 左移
60 << 2
240

其他运算符

  1. 赋值
a = 1
a = 3 + 4
# 左边是一个标识符,右边是一个值(或者可以计算为一个值)
# 让这个标识符指向这个值所的内存
  1. 成员运算符
  2. 身份运算符

成员运算符

用于判断一个元素是否在容器中。

  1. in
  2. not in

举个简单的例子:

>>> L1 = [1, 2, 3]
>>> 1 in L1
True
>>> s = 'spam'
>>> 'a' in s
True
>>> 'x' in s
False
>>> 'x' not in s
True

数据结构

数据结构是计算机存储、组织数据的方式。数据结构是通过某种方式组织在一起的元素的集合。

Python中内置了七种数据结构。

五种线性结构

  1. 列表
  2. 元组
  3. 字符串
  4. bytes
  5. bytearray

两种非线性结构

  1. 字典
  2. 集合

列表、元组、字符串属于线性结构,我们可以对其进行切片操作、解包/封包操作。

序列类型操作符

下表是所有序列类型都适用的操作符:

序列操作符作用
seq[ind]获得下标为ind的元素
seq[ind1:ind2]获得下标从ind1到ind2间的元素集合
seq * expr序列重复expr次
seq1 + seq2连接序列seq1和seq2
obj in seq判断obj元素是否包含在seq中
obj not in判断obj元素是否不包含在seq中

线性数据结构的共性

这几种数据结构的共性:

  1. 都是顺序存储
  2. 顺序访问
  3. 可迭代对象(可迭代对象可以用len方法获取其长度)
  4. 通过索引进行元素的访问
  5. 可以进行切片操作

切片

切片不会对原有的序列做任何修改,切片的语法为:

seq[start:stop]

从索引start开始,到索引stop结束,不包含stop,返回新的序列,不会对原有的对象做任何修改。

几个特性:

  • start超出索引范围:start = 0
  • stop超出索引范围:stop = -1
  • 负数索引:实际上可转化为:len(seq) + index
  • start >= stop时,返回空列表

slice的实现:

lst = list(range(0, 10))


def slice(lst, start=0, stop=0):
    if start < 0:
        start = len(lst) + start
    if stop <= 0:
        stop = len(lst) + stop
    if stop <= start:
        return []
    if stop > len(lst):
        stop = len(lst)
    if start < 0:
        start = 0
    ret = []
    for i, v in enumerate(lst):
        if i >= start and i < stop:
            ret.append(v)
    return ret

print(slice(lst, 3, 2))
print(slice(lst, 2, 5))
print(slice(lst, -100, 100))

运行结果为:

 : []
 : [2, 3, 4]
 : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

如果有了步长之后,上面的规则就会发生变化。接下来加入步长的slice实现:

def slice(lst, start=0, stop=0, step=1):
    ret = []
    if stop < 0:
        tmp = start
        start = tmp
        stop = start
    current = start
    while current < stop:
        try:
            ret.append(lst[current])
        except IndexError:
            pass
        current += step
    return ret

切片的一些常用操作:

>>> lst = list(range(0, 10))
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> lst[:] # 等效于copy方法

>>> lst[-5:-3] # 支持负数索引

# start大于等于stop时,返回空列表
>>> lst[3:1]

# 列出偶数,步长为2
lst[::2]
[0, 2, 4, 6, 8]

# 列出偶数,步长为2,并倒序输出
lst[::2][::-1]
[8, 6, 4, 2, 0]

# 列出奇数,步长为2,并倒序输出
lst[::-2]
[9, 7, 5, 3, 1]

# 列出偶数,步长为2,并倒序输出
lst[-2::-2]
[8, 6, 4, 2, 0]

索引

如果索引超出范围,将引发IndexError的异常。修改元素的时候,如果超出索引范围,也同样引发IndexError异常。

  • index(value)方法根据value找索引
  • count(value)方法统计value出现的次数

引用

列表批量赋值:

## 当赋值的序列连续时
# 对切片赋值,会替代原来的元素
>>> lst = list(range(0, 10))
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> lst[3:5] = ['x', 'y', 'z']
>>> lst
[0, 1, 2, 'x', 'y', 'z', 5, 6, 7, 8, 9]
>>> lst = list(range(0, 10))
>>> lst[3:5] = ['x']
>>> lst
[0, 1, 2, 'x', 5, 6, 7, 8, 9]
>>> lst = list(range(0, 10))
>>> lst[3:5] = 'x'
>>> lst
[0, 1, 2, 'x', 5, 6, 7, 8, 9]
## 当赋值的序列不连续时
>>> lst = list(range(0, 10))
>>> lst[3:8:2] = ['x', 'y', 'z']
>>> lst
[0, 1, 2, 'x', 4, 'y', 6, 'z', 8, 9]
>>> lst = list(range(0, 10))
>>> lst[3:8:2] = ['x']
ValueError: attempt to assign sequence of size 1 to extended slice of size 3
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

不建议使用以上的方式对切片赋值的操作

解包/封包

解构与封装可以叫做解包与封包。

  • 解构把集合里的元素复制给变量;
  • 封装是用变量构建元组。

解构:按照元素顺序,把线性解构的元素依次赋值给变量。JavaScript的ES6语法中也是支持类似Python这种解包与封包的特性。

封装的例子:

t = 1, 2
print(t)
(1, 2)

print(type(t))
<class 'tuple'>

定义一个元组,可以省略小括号。

t1 = (1, 2)
t2 = 1, 2
print(t1 == t2) // t1与t2是等效的
True

封装出来的是元组。封装没有什么难度。解构的变化多样,接下来重点看看解构。

先看一个例子:

In [29]: def swap(a, b):
    ...:     i = a
    ...:     a = b
    ...:     b = i
    ...:     return (a, b)
    ...: 

In [30]: swap(1, 3)
Out[30]: (3, 1)

对上面的代码进行改写,由3行代码,变成了一行代码:

In [31]: def swap(a, b):
    ...:     a, b = b, a
    ...:     return (a, b)
    ...: 

In [32]: swap(1, 3)
Out[32]: (3, 1)

对于如下的代码操作,就是解包:

In [33]: x, y = (1, 3)

In [34]: x
Out[34]: 1

In [35]: y
Out[35]: 3

上面的代码使用的是元组,列表也是可以的:

In [36]: a, b = 1, 3

In [37]: a
Out[37]: 1

In [38]: b
Out[38]: 3

接下来看一下封包:

In [39]: t = 1, 3

In [40]: t
Out[40]: (1, 3)

In [41]: type(t)
Out[41]: tuple

继续看例子:

In [42]: head, tail = list(range(0, 10))
# 将会得到如下的错误,因为=两边的元素数量不一致导致的
ValueError: too many values to unpack (expected 2)

In [43]: head, *tail = list(range(0, 10))

In [44]: head
Out[44]: 0

In [45]: tail
Out[45]: [1, 2, 3, 4, 5, 6, 7, 8, 9]

In [46]: *head, tail = list(range(0, 10))

In [47]: head
Out[47]: [0, 1, 2, 3, 4, 5, 6, 7, 8]

In [48]: tail
Out[48]: 9

如果对一个含有2个元素的列表进行解包:

In [49]: head, *tail = [1, 2]

In [50]: head
Out[50]: 1

In [51]: tail
Out[51]: [2]

如果对一个含有一个元素的列表进行解包:

In [52]: head, *tail = [1]

In [53]: head
Out[53]: 1

In [54]: tail
Out[54]: []

如果对一个空列表进行解包:

In [55]: head, *tail = []
ValueError: not enough values to unpack (expected at least 1, got 0)

针对上述例子的总结:

  1. 左边不能只有一个星号,还要有其他元素
  2. 如果左边不用星号,那么左边的元素个数要与右边的元素个数相同
  3. 左边变量数小于右边元素个数,且左边没有加星号会报错
  4. 元素按照顺序赋值给变量
  5. 变量和元素必须匹配
  6. 加星号变量,可以接收任意个数的元素
  7. 加星号的变量不能单独出现

针对上述,写一个具体的例子:

def it(lst):
    if lst:
        head, *tail = lst
        print(head)
        it(tail)

it(list(range(0, 10)))
0
1
2
3
4
5
6
7
8
9

更复杂一点的例子:

In [63]: head, *tail = [1, 2, 3]

In [64]: head
Out[64]: 1

In [65]: tail
Out[65]: [2, 3]

下面这个例子,在Python2中不能实现:

In [59]: head, *mid, tail = [1, 2, 3, 4, 5]

In [60]: head
Out[60]: 1

In [61]: mid
Out[61]: [2, 3, 4]

In [62]: tail
Out[62]: 5

接下来还有更好玩的,如果我们要丢弃=右边某个值,可以使用下划线来,演示如下:

In [66]: lst = list(range(0, 10))

In [67]: lst
Out[67]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [68]: a, b, _, c, *_ = lst

In [69]: a
Out[69]: 0

In [70]: b
Out[70]: 1

In [71]: c
Out[71]: 3

如果我们只想要序列的首位两个元素,可以这样操作:

In [72]: head, *_, tail = lst

In [73]: head
Out[73]: 0

In [74]: tail
Out[74]: 9

再来一发,两边结构要一样:

In [75]: lst = [1, [2, 3], 4]

In [76]: a, (b, c), d = lst

In [77]: a
Out[77]: 1

In [78]: b
Out[78]: 2

In [79]: c
Out[79]: 3

In [80]: d
Out[80]: 4

对上面的例子,再来稍微变化一下,不过两边的结构要一样,解构是支持多层次的。:

In [81]: lst = [1, [2, 3, 4, 5, 6, 8], 9]

In [82]: lst
Out[82]: [1, [2, 3, 4, 5, 6, 8], 9]

In [83]: a, (b, *_, c), d = lst

In [84]: a
Out[84]: 1

In [85]: b
Out[85]: 2

In [86]: c
Out[86]: 8

In [87]: d
Out[87]: 9

注意:

  • 解包的时候,两边的结构要一致 (重要的事情说三遍)
  • 解包的时候,两边的结构要一致 (重要的事情说三遍)
  • 解包的时候,两边的结构要一致 (重要的事情说三遍)
  • 只要两边结构一样就行
>>> a, (b, (c, (d,))) = [1, [2, [3, [4]]]]
>>> a
1
>>> b
2
>>> c
3
>>> d
4

python的一个惯例,使用单个下划线表示丢弃该变量。单个下划线也是Python合法的标识符,但是如果不是要丢弃一个变量,通常不要用单个下划线表示一个有意义的变量。

head, *_ = 'I love python'
print(head)
I
key, *_, value = 'env = properties'.partition('=')
print(key)
env
print(value)
properties

非常复杂的数据结构,多层嵌套的线性结构的时候,可以用解构快速提取其中的值。

控制结构

通常的程序设计语言有三种控制结构。

顺序结构

代码从上而下顺序执行。

a = 0
a = a + 1
print(a)

分支结构

if cond1:
    block1
elif cond2:
    block2

if 1 < 2:
    print('1 less 2')
print('main block')

分支结构永远只有一个分支会被执行。

循环结构

  1. white语句
  2. for语句

语句结构:

while cond:
    block

一个例子,

a = 0

while a < 10:
    print(a)
    a += 1

通常在while循环中,循环体中需要修改条件,以使得条件为假。

for循环,

for element in iterator:
    block

循环体中绝对不要修改可迭代对象。如下的代码可能会导致机器死机或重启,

lst = range(0, 10)

for i in lst:
    lst.append(i)

结构可以嵌套的,

for i in range(0, 10):
    if i % 2 == 0:
        print(i)

break用于提前结束循环。continue用于跳过之后的语句。

break的示例,

for i in range(0, 10):
    print(i)
    if i > 3:
        break

continue的示例,

for i in range(0, 10):
    if i == 3:
        continue
    print(i)

示例

求素数,

a = 7
for i in range(2, a):
    if a % i == 0:
        break
else:
    print('yes')

循环结构中else子句判断循环有没有提前退出,如果提前退出了,else子句不执行,如果没有提前退出,执行else。

多重循环,

is_break = False
for i in range(0, 10):
    for x in range(0, 10):
        if x >= 3:
            is_break = True
            break
        print('x = {0}'.format(x))
    if is_break:
        break

x = 0
x = 1
x = 2

一段小代码,

NUM = 35

for _ in range(0, 3): # 这里的下划线表示,不需要使用到该变量
    user_input = int(input('pls input a number: '))
    if user_input == NUM:
        print('you win')
        break
    elif user_input < NUM:
        print('less')
    else:
        print('big')
else:
    print('you lose')

打印杨辉三角,

import math

for n in range(0, 10):
    if n == 0:
        print(1)
    else:
        for m in range(0, n+1):
            num = math.factorial(n) // (math.factorial(m) * math.factorial(n-m))
            print(num, end=' ')
        print()

1
1 1 
1 2 1 
1 3 3 1 
1 4 6 4 1 
1 5 10 10 5 1 
1 6 15 20 15 6 1 
1 7 21 35 35 21 7 1 
1 8 28 56 70 56 28 8 1 
1 9 36 84 126 126 84 36 9 1

打印2到100的素数,

for n in range(2, 101):
    for x in range(2, n):
        if n % x == 0:
            break
    else:
        print(n)

2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97

函数

函数基础

简单地说,一个函数就是一组Python语句的组合,它们可以在程序中运行一次或多次运行。Python中的函数在其他语言中也叫做过程或子例程,那么这些被包装起来的语句通过一个函数名称来调用。

有了函数,我们可以在很大程度上减少复制及粘贴代码的次数了(相信很多人在刚开始时都有这样的体验)。我们可以把相同的代码提炼出来做成一个函数,在需要的地方只需要调用即可。那么,这样就提高了代码的复用率了,整体代码看起来比较简练,没有那么臃肿了。

函数在Python中是最基本的程序结构,用来最大化地让我们的代码进行复用;与此同时,函数可以把一个错综复杂的系统分割为可管理的多个部分,简化编程、代码复用。

接下来我们看看什么是函数,及函数该如何定义。有两种方式可以进行函数的定义,分别是deflambda关键字。

函数定义

先总结一下为什么要使用函数?

  1. 代码复用最大化及最小化冗余代码;
  2. 过程分解(拆解)。把一个复杂的任务拆解为多个小任务。

函数定义的语法为(使用def关键字创建了一个函数对象,并把该对象赋值给了一个函数名称。):

def func_name(arg1, arg2, arg3, ..., argN):
    statement
    return value

根据上面定义,可以简单地描述为:Python中的函数是具有0个或多个参数,具有若干行语句并且具有返回值(返回值可有可无)的一个语句块(注意缩进)。

那么我们就定义一个比较简单的函数,该函数没有参数,进入ipython交互式环境:

In [1]: def hello():
  ...:    print('Leave me alone, the world')
  ...:

def语句定义了一个函数,但是并不会调用该函数。我们在代码中可以调用已定义的函数,在函数的名称后面加上一对小括号就可以调用,小括号中还有可选的函数参数。接下来调用(执行)该函数。

In [2]: hello()
Leave me alone, the world

我们发现hello()函数并没有return语句,在Python中,如果没有显式的执行return语句,那么函数的返回值默认为None

我们说过,定义函数有两种形式,另外一种形式是使用lambda来定义。使用lambda定义的函数是匿名函数,这个我们在后面的内容进行讲解,这里暂且不表。

接下来我们看看一个带参数的函数定义及调用:

>>> def intersect(seq1, seq2):
...    res = []
...    for x in seq1:
...        if x in seq2:
...            res.append(x)
...    return res
... 
>>> s1 = 'SPAM'
>>> s2 = 'SCAM'
>>> intersect(s1, s2)
['S', 'A', 'M']

这个例子主要作用是:传入两个列表,求它们的共同的元素。当我们调用该函数时,需要给此函数传入两个参数。这里我们把s1传递给了函数体中的seq1,把s2传递给了seq2,然后把返回值res返回给调用者。上面的演示中我们并没有接收该函数的返回值,如果在后面的代码中需要用到该函数的返回值,那么可以把该函数的返回值赋值给一个变量即可。如:

>>> result = intersect(s1, s2)
>>> result
['S', 'A', 'M']

针对上面的例子,可以使用前面文章中介绍过的列表推导式:

>>> [x for x in s1 if x in s2]
['S', 'A', 'M']

函数参数

函数参数类型有如下。

  • 位置参数:位置参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样,不然会出现语法错误。
  • 默认参数:默认参数的值如果没有传入,则被认为是默认值。
  • 可变参数:可变参数允许我们传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
  • 命名参数:命名参数允许我们传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

在Python中定义函数,可以用必选参数、默认参数、可变参数和关键字参数,这4种参数都可以一起使用,或者只用其中某些,但是请注意,参数定义的顺序必须是: 必选参数、默认参数、可变参数和关键字参数

一个例子:

def func_params(a, b=5, c=8, *args, **kwargs):
    print('a is {}'.format(a))
    print('b is {}'.format(b))
    print('c is {}'.format(c))
    print('args are {}'.format(args))
    print('kwargs are {}'.format(kwargs))


func_params(2, 3)
print('=' * 40)
func_params(5, c=10)
print('=' * 40)
func_params(c=10, a=30)
print('=' * 40)
func_params(1, 2, 3, 4, 5, 6, d=1, e=2, f=3)

执行结果为:

python func_params.py
a is 2
b is 3
c is 8
args are ()
kwargs are {}
========================================
a is 5
b is 5
c is 10
args are ()
kwargs are {}
========================================
a is 30
b is 5
c is 10
args are ()
kwargs are {}
========================================
a is 1
b is 2
c is 3
args are (4, 5, 6)
kwargs are {'e': 2, 'd': 1, 'f': 3}

函数参数之坑:

  • 默认参数必须指向不可变参数。
  • 关键字参数必须写在位置参数之后,否则会抛出语法错误。
# 先定义一个函数,传入一个list,添加一个END再返回
def add_end(L=[]):
    L.append('END')
    return L

# 调用时就出现问题了
>>> add_end()
['END']
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

# 如何解决这个问题呢
def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

关于参数的传递:

  • 当参数为不可变的数据类型时,可以理解为值传递
  • 当参数为可变的数据类型时,可以理解为引用传递

变量作用域

一个程序的所有变量并不是在哪个位置都可以访问的。访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了在哪一部分程序你可以访问哪个特定的变量名称。两种最基本的变量作用域如下。

  1. 全局变量:通常定义在函数外的变量拥有全局作用域。
  2. 局部变量:通常定义在函数内部的变量拥有一个局部作用域。

局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。在函数体内若需要修改全局变量的值,则需要使用global关键字声明为全局变量,否则是不可能为定义在函数外的变量赋值的。如下示例:

def func():
    global x
    print('x is {}'.format(x))
    x = 2
    print('change global x to {}'.format(x))


x = 50
func()
print('value of x is {}'.format(x))

运行结果为:

python func_scope.py
x is 50
change global x to 2
value of x is 2

函数实战

函数基础就介绍到这里,接下来上一个实际的小示例,通过requests库查询手机号的归属地信息。如果系统没有安装requests包,使用pip或easy_install进行安装:

pip install requests

代码为(在ipython交互式环境中执行的该代码):

In [1]: import requests

In [2]: def phone():
   ...:     num = input('Enter your phone number>>> ')
   ...:     api = 'http://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel='
   ...:     r = requests.get(api + num.strip())
   ...:     if r.ok:
   ...:         print(r.text)
   ...:     r.close()
   ...:     return None
   ...: 

In [3]: phone()
Enter your phone number>>> 13651813235
__GetZoneResult_ = {
    mts:'1365181',
    province:'上海',
    catName:'中国移动',
    telString:'13651813235',
areaVid:'29423',
ispVid:'3236139',
carrier:'上海移动'
}

上面示例中的函数并没有接收参数,接下来把该函数改造为带参数的。代码为:

In [4]: def phone(phone_num):
   ...:     api = 'http://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel='
   ...:     res = requests.get(api+phone_num)
   ...:     if res.ok:
   ...:         print(res.text)
   ...:     res.close()
   ...:     return None
   ...: 

In [5]: phone('13651813235')
__GetZoneResult_ = {
    mts:'1365181',
    province:'上海',
    catName:'中国移动',
    telString:'13651813235',
areaVid:'29423',
ispVid:'3236139',
carrier:'上海移动'
}

模块

模块让我们能够有逻辑地组织Python代码段,把相关的代码分配到一个模块里,能让我们的代码更好用,更易懂。

简单地说,模块就是一个保存了Python代码的文件,即包括Python定义和声明的文件。文件名就是模块名加上.py后缀。模块能定义函数、类和变量。模块里也能包含可执行的代码。

包是一个有层次的文件目录结构,由模块和子包组成。包通常是使用用“圆点模块名”的结构化模块命名空间。例如,名为A.B的模块表示了名为“A”的包中名为“B”的子模块。

import语句

想使用Python源文件,只需在另一个源文件里执行import语句,语法如下:

import module1[, module2[,... moduleN]

当解释器遇到import语句,模块在当前的搜索路径就会被导入。 搜索路径是一个解释器会先进行搜索的所有目录的列表。习惯上所有的import语句都放在模块(或脚本)的开头,但这并不是必须的。被导入的模块名入在本模块的全局语义表中。如下示例:

import sys
print(sys.platform)
linux2

from … import语句

Pytthon允许我们从一个模块中导入指定的部分。语法如下:

from modname import name1[, name2[, ... nameN]]

把一个模块的所有内容全部导入也是可以的,语法如下:

from modname import *

不过很少像上面这么使用。

还可以使用as给模块起个别名,如下:

from modname import name1 as testops

模块的搜索路径

当我们导入一个模块时,Python解释器对模块位置的搜索顺序是:

  • 当前目录
  • 如果不在当前目录,Python则搜索环境变量PYTHONPATH下的每个目录
  • 如果还找不到,Python会检查默认路径。UNIX下,默认路径一般为/usr/local/lib/python<version>
  • 模块搜索路径存储在sys模块的sys.path变量中
    import sys
    sys.path
    ['',
    '/root/testops/venv/bin',
    '/root/testops/venv/lib64/python27.zip',
    '/root/testops/venv/lib64/python2.7',
    '/root/testops/venv/lib64/python2.7/plat-linux2',
    '/root/testops/venv/lib64/python2.7/lib-tk',
    '/root/testops/venv/lib64/python2.7/lib-old',
    '/root/testops/venv/lib64/python2.7/lib-dynload',
    '/usr/lib64/python2.7',
    '/usr/lib/python2.7',
    '/root/testops/venv/lib/python2.7/site-packages',
    '/root/testops/venv/lib/python2.7/site-packages/IPython/extensions',
    '/root/.ipython']
    

模块的名字

每个模块都有一个名称,在模块中可以通过__name__来确定模块的名称。

  • 如果模块是被导入,__name__的值为模块名称(如testops.py模块,那么该模块的名字为testops)
  • 如果模块以脚本执行,__name__的值为__main__

这在一个场合特别有用:例如我们只想在程序本身直接被执行的时候运行主块,而在被被的模块导入的时候不运行主块,这时可以通过模块的__name__属性来完成。如下:

if __name__ == '__main__':
    print('module run as script')
else:
    print('module imported by other module')

# 以脚本来执行
python mod_name.py 
module run as script

# 以模块来执行
import mod_name
module imported by other module

面向对象

如果以前没有接触过面向对象的编程语言,那么我们需要先了解一些面向对象语言的一些基础知识,这样有助于我们更容易地学习Python的面向对象编程。

面向对象中的术语

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 方法:类中定义的函数。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 继承:即一个派生类继承基类的字段和方法。
  • 实例化:创建一个类的实例,类的具体对象。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

创建类

使用class关键字创建一个类,class之后为类的名称并以冒号结尾,最简单的类定义形式如下:

class ClassName:
    def method(self):
        return xxx
    def xxx(self):
        return yyyy

在类体中,通常由类成员、方法、属性组成。

类的继承

一个类可以继承另外一个类或多个类,方式如下:

class A:
    pass

class B:
    pass

class C(A, B):
    pass

实例化

类的定义要经过实例化才能生效。一个简单的例子:

class A:
    def say_hi(self):
        print('hello from class A')


class B(A):
    def say_hello(self):
        print('hello from class B')

a = A()
a.say_hi()

b = B()
b.say_hi()
b.say_hello()

执行结果为:

python class_simple.py hello from class A
hello from class A
hello from class B

self参数

注意到上面的类定义中的方法都有一个self参数,这是类方法与普通函数的唯一区别,但是在调用类方法时,可以不必为这个参数赋值,Python会提供这个值。这个特别的变量表示对象本身,按照惯例它为self,不过也可以是其它非关键字。

Python中的self类似于Java中的this。

init方法

init方法在类的一个对象被实例化时被执行。这个方法可以用来给对象做一些初始化的工作。init方法两边各有两个下划线。一个简单的例子:

class Person:
    def __init__(self, name):
        self.name = name

    def say_hi(self):
        print('hello, my name is {}'.format(self.name))

p = Person('testops')
p.say_hi()

# 执行
python class_init.py
hello, my name is testops

异常

异常是可以被处理;错误不可处理。
常见的错误,语法错误、缩进错误、解释器内部错误等。

Python提供了两种方式来捕获异常:

  • try/expect/else语句
  • try/finally语句

try/except

语法为:

try:
    # 可能抛出异常的语句。会一直执行,直到抛出异常。
except:
    # 异常处理语句,当try块任意语句抛出异常时执行。

一个简单的例子:

try:
    print(1 + 1)        # 正常执行
    print('------')      # 正常执行
    print(1 / 0)        # 抛出异常
    print(u'不会被执行') # 不会被执行
except:
    print('except')

try/finally

一个简单的例子:

f = None

try:
    f = open('/root/read_only.txt')
    f.write('hehe\n') # 在这里抛出异常
except:
    print('open error')
finally:
    print('close file')
    if f is not None:
        f.close()
# 执行结果为
open error
close file

finally 语句总是会执行,无论有没有抛出异常,所以清理工作,通常会放 到finally语句块里执行。

需要记住的

三个内置必须使用熟练:typehelpdir

  • type可以查看指定变量的类型;
  • help可以查看指定模块的文档;
  • dir可以指定模块有哪些属性;

数据类型的组成,又3部分组成:

  1. 身份 id 方法来看一看它的唯一标识符,内存地址靠这个
  2. 类型 type 来看一看
  3. 值数据项

数据类型的可变与不可变:

  1. 不可变类型:int, string, tuple
  2. 可变类型:list,tuple,dict

Python优缺点

缺点

  1. 全局解释器锁(Global Interpreter Lock,简称GIL)
    • CPU计算密集型任务
    • 对于IO密集型任务
  2. 性能问题(执行效率)
    • 开发效率远远弥补了执行效率
    • 现在的服务器资源充足
    • 需要效率的地方用c/c++实现,用Python去调用,扬长避短

优点

  1. 日常工作自动化
    • Python自动安装软件
    • 测试用例
    • 测试数据库性能、压测
    • 请求量、优化参数等等
  2. 运维系统、监控系统
  3. saltstack、ansible二次开发等
  4. 封装现有的测试框架
  5. 提高身价

Python技术等级划分

  • 初级
  • 中级
  • 高级
  • 资深
  • 专家

Python进阶闯关

  1. 基础知识:学习理论+练习 (初级)
  2. 进阶(分层次地学习) (中级)
    • 函数式编程
    • 推导式
    • lambda
    • map、filter、reduce
    • 异常
    • 闭包
    • 装饰器
    • 生成器
  3. 通过项目实践 (高级)
    • 通过Django、Tornado、flask编写web服务

推荐的书籍

  1. 基础
    • Python开发技术详解
    • http://python.usyiyi.cn/
    • python中文手册
    • Dive into Python
    • Python Cookbook - http://python3-cookbook.readthedocs.io/zh_CN/latest/index.html
  2. 注重编程规范
    • 编程规范(PEP8规范)
  3. 进阶
    • 中文版:https://eastlakeside.gitbooks.io/interpy-zh/content/
    • 英文版:http://book.pythontips.com/en/latest/

制定一个计划

  1. 每周固定时间去学习Python
  2. 例如,基础知识分配3个月,高级特性2个月
  3. 工作中能用到Python的地方尽量去使用

Python开发技巧

  • 编码统一使用Unicode
  • 多用ipython,你会爱上Python
  • 轻便灵活Flask,急速开发用Django
  • 严格遵守PEP8编码规范
  • 写好unittest,让我们的代码更上一个level
  • 不要使用TAB,而是使用空格进行缩进,保证程序的可移植性
  • Github用起来,开撸之前先去找一找,说不定有意想不到的效果

命名规范

  • 命名规范
TypePublicInternal
Moduleslower_with_under_lower_with_under
Packageslower_with_under
ClassesCapWords_CapWords
ExceptionsCapWords
Functionslower_with_under()_lower_with_under()
Global/Class ConstantsCAPS_WITH_UNDER_CAPS_WITH_UNDER
Global/Class Variableslower_with_under_lower_with_under
Instance Variableslower_with_under_lower_with_under (protected) or __lower_with_under (private)
Method Nameslower_with_under()
  • 函数与函数之间有两个空行
  • 类中的方法与方法之间有一个空行
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LavenLiu

常言道:投资效率是最大的投资。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值