name = input('please enter your name:')
/:除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数; // 还有一种除法是//,称为地板除,两个整数的除法仍然是整数: 所以要做精确的除法使用 / 就可以了,还可以使用求取余数的操作,使用 % 就行。
>>> ord('A')
65
>>> ord('中')
20013
>>> chr(66)
'B'
>>> 'Hello, %s' %'World'
'Hello, World'
>>> 'Hi, %s, you have %d' %('HK', 10000)
'Hi, HK, you have 10000'
占位符 | 替换内容 |
---|---|
%d | 整数 |
%f | 浮点数 |
%s | 字符串 |
%x | 十六进制整数 |
格式化小数还可以指定小数的位数:
>>> '%.2f' % 3.1415926
'3.14'
>>> 'Hello, {0}成绩提升了{1}'.format('HK',17.125)
'Hello, HK成绩提升了17.125
①列出班上几个同学的名字,用一个list来表示
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']
②使用len() 函数来获取list元素的个数
>>> len(classmates)
3
③ 使用索引来访问list中每一个位置的元素,索引是从0开始的
>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[2]
'Tracy'
>>> classmates[3]
报错了,索引出现了越界
Traceback (most recent call last):
File "" , line 1, in <module>
IndexError: list index out of range
④ list是一个可变的有序表,可以往list中追加元素到末尾
append
>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']
⑤ 也可以insert将元素插入到指定的位置
>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']
⑥ 删除list末尾的元素 用pop()
>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']
⑦ 使用pop(i) 可以删除指定位置的元素
>>> classmates.pop(1)
'Jack'
>>> classmates
['Michael', 'Bob', 'Tracy']
⑧ 可以将list中的某个元素替换成别的元素,可以直接赋值给相应的索引位置
>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']
⑨ list当中的元素的数据类型可以不同
>>> L = ['Apple', 123, True]
⑩ list当中也可以包含另一个list
>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4
理解:s只要4个元素,其中s[2]又是一个list,拆开书写更容易理解
>>> p = ['asp', 'php']
>>> s = ['python', 'java', p, 'scheme']
要拿到’php’可以写p[1]或者s[2][1],因此s可以看成是一个二维数组,类似的还有三维、四维……数组,不过很少用到。
如果一个list中一个元素也没有,就是一个空的list,它的长度为0:
>>> L = []
>>> len(L)
0
另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改,比如同样是列出同学的名字:
>>> classmates = ('Michael', 'Bob', 'Tracy')
现在,classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素。
不可变tuple的意义:就是因为其不可变所以代码会更安全,如果可以,能用tuple代替list就尽量使用tuple。
注意:
>>> t = (1, 2)
>>> t
(1, 2)
>>> t = (1)
>>> t
1
--- 所以只有一个元素的tuple定义时,必须加一个逗号,以免误解成数学计算意义上的括号
>>> t = (1,)
>>> t
(1,)
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])
这个tuple定义的时候有3个元素,分别是’a’,'b’和一个list。不是说tuple一旦定义后就不可变了吗?怎么后来又变了?
理解:
先看看定义的时候tuple包含的3个元素:
当我们把list的元素’A’和’B’修改为’X’和’Y’后,tuple变为:
list和tuple是Python内置的有序集合,一个可变,一个不可变。根据需要来选择使用它们。
python使用的是缩进原则来实现具体的条件判断:
根据Python的缩进规则,如果if语句判断是True,就把缩进的两行print语句执行了,否则,什么也不做。
age = 3
if age >= 18:
print('adult')
elif age >= 6:
print('teenager')
else:
print('kid')
if x:
print('True')
只要x是非零数值,非空字符串,非空list等,就可以判断为True,否则就是False
python循环分为两种:
① 一种是for…in循环,依次把list或tuple中的每个元素迭代出来;
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)
执行上述代码,会依次打印names中的每一个元素:
Michael
Bob
Tracy
解释:for x in …循环就是把每个元素代入变量x,然后执行缩进块的语句。
实例:计算1-10的整数之和,可以用一个sum变量做累加:
sum = 0
for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
sum = sum + x
print(sum)
range() 函数,可以生成一个整数序列,在通过list() 函数可以转换为list
>>> list(range(5))
[0, 1, 2, 3, 4]
② 另一种循环是while循环,只要条件满足,就不断循环,条件不满足时退出循环。
实例:计算100以内所有奇数之和,可以用while循环实现:
sum = 0
n = 99
while n > 0:
sum = sum + n
n = n - 2
print(sum)
break
使用break 语句可以提前退出循环
n = 1
while n <= 100:
if n > 10: # 当n = 11时,条件满足,执行break语句
break # break语句会结束当前循环
print(n)
n = n + 1
print('END')
解释:打印出1~10后,紧接着打印END,程序结束。可见break的作用是提前结束循环。
continue
使用continue 可以跳过当前这次循环,直接开始下一次循环
实例:在打印过程中,只想打印奇数,可以使用continue语句跳过某些循环
n = 0
while n < 10:
n = n + 1
if n % 2 == 0: # 如果n是偶数,执行continue语句
continue # continue语句会直接继续下一轮循环,后续的print()语句不会执行
print(n)
解释:打印的不再是1~10,而是1,3,5,7,9。
可见continue的作用是提前结束本轮循环,并直接开始下一轮循环。
break和continue会造成代码执行逻辑分叉过多,容易出错。大多数循环并不需要用到break和continue语句。即:break和continue可以少用尽量少用!
Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。
举例:
假设要根据同学的名字查找对应的成绩,如果用list实现,需要两个list:
names = ['Michael', 'Bob', 'Tracy']
scores = [95, 75, 85]
很明显,给定一个名字要查找对应的成绩,就先要在names中找到对应的位置,在从scores取出对应的成绩,list越长,耗时越长。
使用dict实现,只需要一个**“名字”-“成绩”**的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用Python写一个dict如下:
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95
为什么dict查找速度比list快这么多呢 ?
① 因为dict的实现原理和查字典是一样的。假设字典包含了1万个汉字,我们要查某一个字,一个办法是把字典从第一页往后翻,直到找到我们想要的字为止,这种方法就是在list中查找元素的方法,list越大,查找越慢。
② 第二种方法是先在字典的索引表里(比如部首表)查这个字对应的页码,然后直接翻到该页,找到这个字。无论找哪个字,这种查找速度都非常快,不会随着字典大小的增加而变慢。
dict就是第二种实现方式, 给定一个名字,比如’Michael’,dict在内部就可以直接计算出Michael对应的存放成绩的“页码”,也就是95这个数字存放的内存地址,直接取出来,所以速度非常快。
key-value存储方式
在放进去的时候,必须根据key算出value的存放位置,这样,取的时候才能根据key直接拿到value。
>>> d['Adam'] = 67
>>> d['Adam']
67
>>> d['Jack'] = 90
>>> d['Jack']
90
>>> d['Jack'] = 88
>>> d['Jack']
88
>>> d['Thomas']
Traceback (most recent call last):
File "" , line 1, in <module>
KeyError: 'Thomas'
>>> 'Thomas' in d
False
② 通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value:
>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1
>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}
>>> key = [1, 2, 3]
>>> d[key] = 'a list'
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: unhashable type: 'list'
set
set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}
注意,传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.remove(4)
>>> s
{1, 2, 3}
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}
str是不变对象,而list是可变对象。对于可变对象,比如list,对list进行操作,list内部的内容是会变化的,比如:
>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']
而对于不可变对象,比如str,对str进行操作呢:
>>> a = 'abc'
>>> a.replace('a', 'A')
'Abc'
>>> a
'abc'
虽然字符串有个replace()方法,也确实变出了’Abc’,但变量a最后仍是’abc’,应该怎么理解呢?
>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'
函数文档
>>> help(abs)
Help on built-in function abs in module builtins:
abs(x, /)
Return the absolute value of the argument.
>>> abs(100)
100
>>> abs(-20)
20
>>> abs(12.34)
12.34
>>> abs(1, 2)
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: abs() takes exactly one argument (2 given)
>>> abs('a')
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: bad operand type for abs(): 'str'
>>> max(1, 2)
2
>>> max(2, 3, 1, -5)
3
数据类型转换
>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(1.23)
'1.23'
>>> str(100)
'100'
>>> bool(1)
True
>>> bool('')
False
>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1
在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。
def my_abs(x):
if x >= 0:
return x
else:
return -x
print(my_abs(-99))
# 99
注意点:
空函数
def nop():
pass
if age >= 18:
pass
缺少了pass,代码运行就会有语法错误。
参数检查
>>> my_abs(1, 2)
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: my_abs() takes 1 positional argument but 2 were given
>>> my_abs('A')
Traceback (most recent call last):
File "" , line 1, in <module>
File "" , line 2, in my_abs
TypeError: unorderable types: str() >= int()
>>> abs('A')
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: bad operand type for abs(): 'str'
解释:当传入了不恰当的参数时,内置函数abs会检查出参数错误,而我们定义的my_abs没有参数检查,会导致if语句出错,出错信息和abs不一样。所以,这个函数定义不够完善。
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x
>>> my_abs('A')
Traceback (most recent call last):
File "" , line 1, in <module>
File "" , line 3, in my_abs
TypeError: bad operand type
后续还会接着复习到 错误和异常。
返回多个值
应用:比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的坐标:
import math
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print(x, y)
151.96152422706632 70.0
>>> r = move(100, 100, 60, math.pi / 6)
>>> print(r)
(151.96152422706632, 70.0)
原来返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
小结
位置参数
def power(x):
return x * x
>>> power(5)
25
>>> power(15)
225
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
这个修改后的power(x, n)函数,可以计算任意n次方:
>>> power(5, 2)
25
>>> power(5, 3)
125
修改后的power(x, n)函数有两个参数:x和n,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数x和n。
默认参数
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
>>> power(5)
25
>>> power(5, 2)
25
设置默认参数注意点
def enroll(name, gender, age=6, city='Beijing'):
print('name:', name)
print('gender:', gender)
print('age:', age)
print('city:', city)
默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。
注意点
定义默认参数要牢记一点:默认参数必须指向不变对象!
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
None是不变对象,无论调用多少次,都不会有问题:
>>> add_end()
['END']
>>> add_end()
['END']
可变参数
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84
>>> calc(1, 2, 3)
14
>>> calc(1, 3, 5, 7)
84
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
>>> calc(1, 2)
5
>>> calc()
0
>>> nums = [1, 2, 3]
>>> calc(*nums)
14
解释:***nums表示把nums这个list的所有元素作为可变参数传进去。**这种写法相当有用,而且很常见。
关键字参数
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
>>> 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'}
关键字参数的作用:它可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
解释:extra表示把extra这个dict的所有key-value用关键字参数传入到函数的kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。
命名关键字参数
def person(name, age, **kw):
if 'city' in kw:
# 有city参数
pass
if 'job' in kw:
# 有job参数
pass
print('name:', name, 'age:', age, 'other:', kw)
调用者仍可以传入不受限制的关键字参数
>>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
!!!如果要限制关键字参数的名字,就可以使用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:
def person(name, age, *, city, job):
print(name, age, city, job)
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
def person(name, age, *args, city, job):
print(name, age, args, city, job)
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job'
解释:由于调用时缺少参数名city和job,Python解释器把前两个参数视为位置参数,后两个参数传给*args,但缺少命名关键字参数导致报错。
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
上面就是由于,命名关键字参数city具有默认值,调用时,可不传入city参数
>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个作为特殊分隔符。如果缺少,Python解释器将无法识别位置参数和命名关键字参数:
def person(name, age, city, job):
# 缺少 *,city和job被视为位置参数
pass
参数组合
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
!!!对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
实例:我们来计算阶乘n! = 1 x 2 x 3 x … x n,用函数fact(n)表示,
可以看出:
fact(n)=n!=1×2×3×⋅⋅⋅×(n−1)×n=(n−1)!×n=fact(n−1)×n
fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。
fact(n)用递归的方式写出来
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
实验结果
>>> fact(1)
1
>>> fact(5)
120
计算过程
递归函数的优点:定义简单,逻辑清晰。其实所有递归函数都可以写成循环方式,但是循环逻辑没有递归那么清晰。
>>> fact(1000)
Traceback (most recent call last):
File "" , line 1, in <module>
File "" , line 4, in fact
...
File "" , line 4, in fact
RuntimeError: maximum recursion depth exceeded in comparison
解决递归调用栈溢出的方法:通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归:在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
– 上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:
def fact(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
解释:return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1和num * product在函数调用前就会被计算,不影响函数调用。
递归函数调用过程:
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。
小结:
>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
>>> L[0:3]
['Michael', 'Sarah', 'Tracy']
解释:L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。
>>> L[:3]
['Michael', 'Sarah', 'Tracy']
>>> L[1:3]
['Sarah', 'Tracy']
>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']
>>> L = list(range(100))
>>> L
[0, 1, 2, 3, ..., 99]
>>> L[:10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> L[-10:]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> L[10:20]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> L[:10:2]
[0, 2, 4, 6, 8]
>>> L[::5]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
>>> L[:]
[0, 1, 2, 3, ..., 99]
>>> (0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)
>>> 'ABCDEFG'[:3]
'ABC'
>>> 'ABCDEFG'[::2]
'ACEG'
概念:如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
... print(key)
...
a
c
b
dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()。
字符串也是可迭代对象,也可以作用于for循环:当我们使用for循环时,只要作用于一个可迭代对象,for循环就可以正常运行,而我们不太关心该对象究竟是list还是其他数据类型。
>>> for ch in 'ABC':
... print(ch)
...
A
B
C
>>> from collections.abc import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
... print(x, y)
...
1 1
2 4
3 9
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> L = []
>>> for x in range(1, 11):
... L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
方法二:列表生成式:一行语句代替循环生成上面的list
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
小总结:写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。
>>> [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']
>>> 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']
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
... print(k, '=', v)
...
y = B
x = A
z = C
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']
>>> [x for x in range(1, 11) if x % 2 == 0]
[2, 4, 6, 8, 10]
>>> [x if x % 2 == 0 for x in range(1, 11)]
File "" , line 1
[x if x % 2 == 0 for x in range(1, 11)]
^
SyntaxError: invalid syntax
解释:这是因为for前面的部分是一个表达式,它必须根据x计算出一个结果。因此,考察表达式:x if x % 2 == 0,它无法根据x计算出结果,因为缺少else,必须加上else:
>>> [x if x % 2 == 0 else -x for x in range(1, 11)]
[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]
小总结:在一个列表生成式中,for前面的if … else是表达式,而for后面的if是过滤条件,不能带else。
练习
小总结:运用列表生成式,可以快速生成list,可以通过一个list推导出另一个list,而代码却十分简洁。
>>> 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>
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "" , line 1, in <module>
StopIteration
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81
用函数来进行实现
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
a, b = b, a + b
解释:不必显式写出临时变量t就可以赋值
t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]
上面的函数可以输出斐波那契数列的前N个数:
>>> fib(6)
1
1
2
3
5
8
'done'
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>
对于generator函数的解释:generator函数和普通函数的执行流程不一样。普通函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
实际例子来说明上面所讲:
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
File "" , line 1, in <module>
StopIteration
解释:
>>> g = odd()
>>> next(g)
step 1
1
>>> next(g)
step 2
3
>>> next(g)
step 3
5
>>> for n in fib(6):
... print(n)
...
1
1
2
3
5
8
>>> g = fib(6)
>>> while True:
... try:
... x = next(g)
... print('g:', x)
... except StopIteration as e:
... print('Generator return value:', e.value)
... break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
– 如何捕获错误,后面还会复习到
练习 – 试写一个generator,来打印出一个杨辉三角,见 迭代器打印杨辉三角
小总结
>>> r = abs(6)
>>> r
6
2、generator函数的调用实际返回一个generator对象:
>>> g = fib(6)
>>> g
<generator object fib at 0x1022ef948>
>>> 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
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
小总结:
for x in [1, 2, 3, 4, 5]:
pass
实际上面完全等价于:
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
map/reduce
1、map()
>>> 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。
L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
L.append(f(n))
print(L)
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
2、reduce()
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
filter
Python内建的filter()函数用于过滤序列。
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]
– 只有函数最终结果为True的时候,才会保留下来
def not_empty(s):
return s and s.strip()
list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
# 结果: ['A', 'B', 'C']
def _odd_iter():
n = 1
while True:
n = n + 2
yield n
def _not_divisible(n):
return lambda x: x % n > 0
def primes():
yield 2
it = _odd_iter() # 初始序列
while True:
n = next(it) # 返回序列的第一个数
yield n
it = filter(_not_divisible(n), it) # 构造新序列
解释:这个生成器先返回第一个素数2, 然后利用filter() 不断产生筛选后的新的序列
# 打印1000以内的素数:
for n in primes():
if n < 1000:
print(n)
else:
break
练习
回数是指从左向右读和从右向左读都是一样的数,例如12321,909。请利用filter()筛选出回数:
思路:利用回数的性质, 从左向右读和从右向左读都是一样的数,我们可以利用字符串的性质 外加 切片来进行比较,因为filter是需要写出一个判断函数。
小总结:filter()的作用是从一个序列中筛出符合条件的元素。由于filter()使用了惰性计算,所以只有在取filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素。
排序算法(sorted)
排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
list = [36, 5, -12, 9, -21]
keys = [36, 5, 12, 9, 21]
不需要过多解读,其实就是list上的每一个元素都进行了abs运算。
以上是对sorted([36, 5, -12, 9, -21], key=abs)的解释
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
小总结:sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数(key函数实现即可)
练习:
解释:跟常规的list表达不一样,sorted函数运行时,参数L是每次传其中一个元素进去,例子中就是把L的元素每一个tuple() 传到key里,所以可以用t[n]表示每个tuple里的元素。
但是我们在对一般list进行取值的时候,一般情况下是下面这种形式:有点类似于二维数组:
>>> L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
>>> L[0]
('Bob', 75)
>>> L[1]
('Adam', 92)
>>> L[0][0]
'Bob'
对字典中的值如何取:
>>> T = {'kang':1999, 'lei':2000, 'YY':1998}
>>> T['kang']
1999
即函数作为返回值
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
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
>>> f()
25
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False
但是:f1() 和 f2() 调用结果互不影响!
闭包
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
>>> 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
nonlocal(非局部的)
使用闭包,就是内层函数引用了外层函数的局部变量。如果只是读外层变量的值,我们会发现返回的闭包函数调用一切正常:
def inc():
x = 0
def fn():
# 仅读取x的值:
return x + 1
return fn
f = inc()
print(f()) # 1
print(f()) # 1
def inc():
x = 0
def fn():
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。
所以只要加上一个简单的声明即可:
def inc():
x = 0
def fn():
# nonlocal x
x = x + 1
return x
return fn
f = inc()
print(f()) # 1
print(f()) # 2
!!! 使用闭包时,对外层变量赋值前,需要先使用nonlocal声明该变量不是当前函数的局部变量。
练习
问题描述:利用闭包返回一个计数器函数,每次调用它返回递增整数:
方法一:
方法二:
小总结:
匿名函数
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便
在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]
def f(x):
return x * x
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
def build(x, y):
return lambda: x * x + y * y
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
>>> def now():
... print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25
>>> now.__name__
'now'
>>> f.__name__
'now'
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def now():
print('2015-3-25')
>>> now()
call now():
2015-3-25
解释:把@log放到now()函数的定义处,相当于执行了语句:
now = log(now)
解释:由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
上面的例子可能很难理解,举一个实际例子来讲解
其实:当一个函数中,不同逻辑混杂在一起的时候,程序的可读性就会大大折扣,
这个时候就可以使用一种叫做装饰器的东西来整理代码。
我们一般写判断一个数是不是素数的操作:
import time
def is_prime(num):
if num < 2:
return False
elif num == 2:
return True
else:
for i in range(2, num):
if num % i == 0:
return False
return True
def prime_nums():
t1 = time.time()
for i in range(2, 10000):
if is_prime(i):
print(i)
t2 = time.time()
print(t2 - t1)
prime_nums()
使用装饰器
'''
装饰器的使用
慢慢开始引入装饰器这个概念
记录时间
当一个函数中,不同逻辑混杂在一起的时候,程序的可读性就会大大折扣,
这个时候就可以使用一种叫做装饰器的东西来整理代码
'''
import time
# 来求取一个函数的总运行时间
# 里面这个参数实际意思就是我等下要运行prime_nums这个函数
def display_time(func):
# wrapper() 表示这个函数需要运行哪一些内容
def wrapper():
# 比如说我们要做的是计时
# 1、先截取一个时间
t1 = time.time()
# 2、运行一下我要走的那个函数
func()
# 3、截取另一段时间
t2 = time.time()
print(t2 - t1)
return wrapper
# 判断一个数字是不是素数
def is_prime(num):
if num < 2:
return False
elif num == 2:
return True
else:
for i in range(2, num):
if num % i == 0:
return False
return True
# 如何使用装饰器,使用 @
@ display_time
def prime_nums():
for i in range(2, 10000):
if is_prime(i):
print(i)
'''
此时运行这个函数的时候会:
1、先运行display_time这个装饰器
2、运行wrapper里面的内容:
3、最后才是prime_nums()这个函数实现运行操作
'''
prime_nums ()
!!! 简单使用装饰器以后和之前那个运行结果其实是一样的。
@ display_time
def count_prime_nums():
count = 0
for i in range(2, 10000):
if is_prime(i):
count = count + 1
return count
count = count_prime_nums()
print(count)
count_prime_nums()
0.46373701095581055
None
但是上述代码在执行以后,并没有打印出统计结果,那是因为返回值并没有传到func里面:
如果想要返回返回值的话,那么需要在wrapper() 里面将返回值返回出来!!!
即修改以后变成:
def display_time(func):
# wrapper() 表示这个函数需要运行哪一些内容
def wrapper():
# 比如说我们要做的是计时
# 1、先截取一个时间
t1 = time.time()
# 2、运行一下我要走的那个函数
# 将函数返回值记录到一个变量result当中
result = func()
# 3、截取另一段时间
t2 = time.time()
print("Total time: {:.4} s".format(t2 - t1))
return result
return wrapper
如果装饰器下面那个函数带了参数呢?比如在这个例子当中,我们不是到10000,而是想看2到任意数字,则代码变成这样:
def count_prime_nums(maxnum):
count = 0
for i in range(2, maxnum):
if is_prime(i):
count = count + 1
return count
count = count_prime_nums(1999)
print(count)
此时程序会报错:
TypeError: wrapper() takes 0 positional arguments but 1 was given
那么函数带了参数以后装饰器应该怎么进行修改呢?
同样也是在wrapper里面进行修改:
def display_time(func):
# wrapper() 表示这个函数需要运行哪一些内容
# 如果不知道有多少个参数的话,可以写得稍微笼统一点
def wrapper(*args):
# 比如说我们要做的是计时
# 1、先截取一个时间
t1 = time.time()
# 2、运行一下我要走的那个函数
# 将函数返回值记录到一个变量result当中
result = func(*args)
# 3、截取另一段时间
t2 = time.time()
print("Total time: {:.4} s".format(t2 - t1))
return result
return wrapper
最后总的例子实现代码是:
'''
装饰器的使用
慢慢开始引入装饰器这个概念
记录时间
当一个函数中,不同逻辑混杂在一起的时候,程序的可读性就会大大折扣,
这个时候就可以使用一种叫做装饰器的东西来整理代码
'''
import time
# 来求取一个函数的总运行时间
# 里面这个参数实际意思就是我等下要运行prime_nums这个函数
def display_time(func):
# wrapper() 表示这个函数需要运行哪一些内容
# 如果不知道有多少个参数的话,可以写得稍微笼统一点
def wrapper(*args):
# 比如说我们要做的是计时
# 1、先截取一个时间
t1 = time.time()
# 2、运行一下我要走的那个函数
# 将函数返回值记录到一个变量result当中
result = func(*args)
# 3、截取另一段时间
t2 = time.time()
print("Total time: {:.4} s".format(t2 - t1))
return result
return wrapper
# 判断一个数字是不是素数
def is_prime(num):
if num < 2:
return False
elif num == 2:
return True
else:
for i in range(2, num):
if num % i == 0:
return False
return True
# 如何使用装饰器,使用 @
@ display_time
# def prime_nums():
# for i in range(2, 10000):
# if is_prime(i):
# print(i)
def count_prime_nums(maxnum):
count = 0
for i in range(2, maxnum):
if is_prime(i):
count = count + 1
return count
count = count_prime_nums(5000)
print(count)
'''
此时运行这个函数的时候会:
1、先运行display_time这个装饰器
2、运行wrapper里面的内容:
3、最后才是prime_nums()这个函数实现运行操作
'''
# count_prime_nums()
运行结果是:
Total time: 0.1388 s
669
练习:
请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:
小总结:
>>> int('12345')
12345
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
base是可以省略不写的
def int2(x, base=2):
return int(x, base)
>>> int2('1000000')
64
>>> int2('1010101')
85
>>> int2('1000000', base=10)
1000000
int2 = functools.partial(int, base=2)
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 。
小总结:
#!/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’];
if __name__=='__main__':
test()
当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。
$ python3 hello.py
Hello, world!
$ python hello.py Michael
Hello, Michael!
$ 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.test()
Hello, world!
作用域
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过**_前缀**来实现的。
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。-- 还是非常关键的!!!
方法一:
pip install Pillow
方法二:
安装常用模块
模块搜索路径
当我们试图加载一个模块时,Python会在指定的路径下搜索对应的.py文件,如果找不到,就会报错:
>>> import mymodule
Traceback (most recent call last):
File "" , line 1, in <module>
ImportError: No module named mymodule
>>> import sys
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', ..., '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages']
如果我们要添加自己的搜索目录,有两种方法:
>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')
仍以Student类为例,在Python中,定义类是通过class关键字:
class Student(object):
pass
解释:class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的:
>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>
解释:可以看到,变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Student本身则是一个类。
>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
!!!注意:特殊方法“init”前后分别有两个下划线!!!
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59
数据封装
面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:
>>> def print_score(std):
... print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59
但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法:
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))
要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入
>>> bart.print_score()
Bart Simpson: 59
这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。
封装的另一个好处是可以给Student类增加新的方法,比如get_grade:
class Student(object):
...
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
同样的,get_grade方法可以直接在实例变量上调用,不需要知道内部实现细节:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
// 测试
lisa = Student('Lisa', 99)
bart = Student('Bart', 59)
print(lisa.name, lisa.get_grade())
print(bart.name, bart.get_grade())
// run
Lisa A
Bart C
小总结:
>>> bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
File "" , line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'
>>> bart = Student('Bart Simpson', 59)
>>> bart.score
59
>>> bart.score = 99
>>> bart.score
99
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))
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
File "" , line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
class Student(object):
...
def set_score(self, score):
self.__score = score
外部代码想要获取或者修改属性的话,设置相应的get和set方法即可
class Student(object):
...
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
>>> bart._Student__name
'Bart Simpson'
>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 设置__name变量!
>>> bart.__name
'New Name'
表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。不信试试:
>>> bart.get_name() # get_name()内部返回self.__name
'Bart Simpson'
练习:
请把下面的Student对象的gender字段对外隐藏起来,用get_gender()和set_gender()代替,并检查参数有效性:
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印:
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
pass
class Cat(Animal):
pass
解释:对于Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类。Cat和Dog类似。
继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:
// 先创建相应的对象
dog = Dog()
dog.run()
cat = Cat()
cat.run()
运行结果如下:
Animal is running...
Animal is running...
当然,也可以对子类增加一些方法,比如Dog类:
class Dog(Animal):
def run(self):
print('Dog is running...')
def eat(self):
print('Eating meat...')
继承的第二个好处需要我们对代码做一点改进。你看到了,无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running…,符合逻辑的做法是分别显示Dog is running…和Cat is running…,因此,对Dog和Cat类改进如下:
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
再次运行,运行结果为:
Dog is running...
Cat is running...
当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。
理解多态:
要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:
a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型
判断一个变量是否是某个类型可以用**isinstance()**判断:
>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True
看来a、b、c确实对应着list、Animal、Dog这3种类型。但是等等,试试:
>>> isinstance(c, Animal)
True
看来c不仅仅是Dog,c还是Animal!
不过仔细想想,这是有道理的,因为**Dog是从Animal继承下来的,**当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!
>>> b = Animal()
>>> isinstance(b, Dog)
False
def run_twice(animal):
animal.run()
animal.run()
>>> run_twice(Animal())
Animal is running...
Animal is running...
>>> run_twice(Dog())
Dog is running...
Dog is running...
>>> run_twice(Cat())
Cat is running...
Cat is running...
class Tortoise(Animal):
def run(self):
print('Tortoise is running slowly...')
>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...
class Timer(object):
def run(self):
print('Start...')
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
即要求没有那么严格
**Python的“file-like object“**就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。
小总结:
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
使用isinstance()
object -> Animal -> Dog -> Husky
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Husky)
True
没有问题,因为h变量指向的就是Husky对象。
接着判断:
>>> isinstance(h, Dog)
True
解释:
h虽然自身是Husky类型,但由于Husky是从Dog继承下来的,所以,h也还是Dog类型。换句话说,isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
>>> isinstance(h, Animal)
True
>>> isinstance(d, Dog) and isinstance(d, Animal)
True
>>> isinstance(d, Husky)
False
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。
使用dir()
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
>>> class MyDog(object):
... def __len__(self):
... return 100
...
>>> dog = MyDog()
>>> len(dog)
100
>>> 'ABC'.lower()
'abc'
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19
>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
File "" , line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
小总结:
sum = obj.x + obj.y
就不要写:
sum = getattr(obj, 'x') + getattr(obj, 'y')
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
class Student(object):
name = 'Student'
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
解释:从上面的例子可以看出,在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
练习:
为了统计学生人数,可以给Student类增加一个类属性,每创建一个实例,该属性自动增加:
可以通过类名来调用属性
小总结
略,后续有需要再学
在程序运行的过程中,如果发生了错误,可以事先约定返回一个错误代码,这样,就可以知道是否有错,以及出错的原因。在操作系统提供的调用中,返回错误码非常常见。比如打开文件的函数open(),成功时返回文件描述符(就是一个整数),出错时返回-1。
def foo():
r = some_function()
if r==(-1):
return (-1)
# do something
return r
def bar():
r = foo()
if r==(-1):
print('Error')
else:
pass
try
让我们用一个例子来看看try的机制:
try:
print('try...')
r = 10 / 0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')
当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。
try...
except: division by zero
finally...
END
try...
result: 5
finally...
END
try:
print('try...')
r = 10 / int('a')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
finally:
print('finally...')
print('END')
try:
print('try...')
r = 10 / int('2')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else:
print('no error!')
finally:
print('finally...')
print('END')
try:
foo()
except ValueError as e:
print('ValueError')
except UnicodeError as e:
print('UnicodeError')
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
print('Error:', e)
finally:
print('finally...')
调用栈
# err.py:
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
bar('0')
main()
执行结果如下:
$ python3 err.py
Traceback (most recent call last):
File "err.py", line 11, in <module>
main()
File "err.py", line 9, in main
bar('0')
File "err.py", line 6, in bar
return foo(s) * 2
File "err.py", line 3, in foo
return 10 / int(s)
ZeroDivisionError: division by zero
记录错误
如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。
# err_logging.py
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')
同样是出错,但程序打印完错误信息后会继续执行,并正常退出:
$ python3 err_logging.py
ERROR:root:division by zero
Traceback (most recent call last):
File "err_logging.py", line 13, in main
bar('0')
File "err_logging.py", line 9, in bar
return foo(s) * 2
File "err_logging.py", line 6, in foo
return 10 / int(s)
ZeroDivisionError: division by zero
END
通过配置,logging还可以把错误记录到日志文件里,方便事后排查。
抛出错误
因为错误是class,捕获一个错误就是捕获到该class的一个实例。因此,错误并不是凭空产生的,而是有意创建并抛出的。Python的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。
# err_raise.py
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
foo('0')
执行,可以最后跟踪到我们自己定义的错误:
$ python3 err_raise.py
Traceback (most recent call last):
File "err_throw.py", line 11, in <module>
foo('0')
File "err_throw.py", line 8, in foo
raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0
# err_reraise.py
def foo(s):
n = int(s)
if n==0:
raise ValueError('invalid value: %s' % s)
return 10 / n
def bar():
try:
foo('0')
except ValueError as e:
print('ValueError!')
raise
bar()
try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')
只要是合理的转换逻辑就可以,但是,决不应该把一个IOError转换成毫不相干的ValueError。
练习:
运行下面的代码,根据异常信息进行分析,定位出错误源头,并修复:
小总结:
程序能一次写完并正常运行的概率很小,基本不超过1%。总会有各种各样的bug需要修正。有的bug很简单,看看错误信息就知道,有的bug很复杂,我们需要知道出错时,哪些变量的值是正确的,哪些变量的值是错误的,因此,需要一整套调试程序的手段来修复bug。
def foo(s):
n = int(s)
print('>>> n = %d' % n)
return 10 / n
def main():
foo('0')
main()
$ python err.py
>>> n = 0
Traceback (most recent call last):
...
ZeroDivisionError: integer division or modulo by zero
断言
凡是用print()来辅助查看的地方,都可以用断言(assert)来替代:
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。
如果断言失败,assert语句本身就会抛出AssertionError:
$ python err.py
Traceback (most recent call last):
...
AssertionError: n is zero!
$ python -O err.py
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
注意:断言的开关“-O”是英文大写字母O,不是数字0。
关闭后,你可以把所有的assert语句当成pass来看。
logging
import logging
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
import logging
logging.basicConfig(level=logging.INFO)
$ 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
小总结:
略
略
略
略