python
Python简介
Python是一种跨平台的计算机程序设计语言。是一种面向对象的动态类型语言,最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。
Python是一种解释型脚本语言,可以应用于以下领域:
Web 和 Internet开发、科学计算和统计、人工智能、教育、桌面界面开发、软件开发、后端开发、网络爬虫等。与C/C++、Java等语言不同,Python中没有用花括号来构造代码块而是使用了缩进的方式来设置代码的层次结构。
感受Python的简约-九九乘法表
1
2
3
4for i in range(1,10):
for j in range(1,i+1):
print('%d*%d=%d' % (i,j,i*j), end='\t')
print()输出结果
1
2
3
4
5
6
7
8
91*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
4*1=4 4*2=8 4*3=12 4*4=16
5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81
Python中的注释符号
单行注释 - 以#和空格开头的部分
1
# print("你好,世界!")
多行注释 - 三个引号开头,三个引号结尾
1
2
3
4
5
6"""
第一个Python程序 - hello, world!
Version: 1.0
Author: ZTY
"""
Python中对变量类型进行转换
int()
:将一个数值或字符串转换成整数,可以指定进制。float()
:将一个字符串转换成浮点数。str()
:将指定的对象转换成字符串形式,可以指定编码。chr()
:将整数转换成该编码对应的字符串(一个字符)。ord()
:将字符串(一个字符)转换成对应的编码(整数)。
Python中的运算符(部分)
运算符 | 描述 |
---|---|
[] [:] |
下标,切片 |
** |
指数 |
~ + - |
按位取反, 正负号 |
* / % // |
乘,除,模,整除 |
+ - |
加,减 |
>> << |
右移,左移 |
<= < > >= |
小于等于,小于,大于,大于等于 |
== != |
等于,不等于 |
is is not |
身份运算符 |
in not in |
成员运算符 |
not or and |
逻辑运算符 |
注意
取模运算(“Module Operation”)和取余运算(“Complementation ”)两个概念有重叠的部分但又不完全一致。主要的区别在于对负整数进行除法运算时操作不同。另外各个环境下%运算符的含义不同,比如c/c++,java 为取余,而python则为取模。
对于整型数a,b来说,取模运算或者求余运算的方法都是:
- 1.求整数商: c = a/b
- 2.计算模或者余数: r = a - c*b
求模运算和求余运算在第一步不同: 取余运算在取c的值时,向0 方向舍入(fix()函数);而取模运算在计算c的值时,向负无穷方向舍入(floor()函数)。
- 例如计算:-7 Mod 4
那么:a = -7;b = 4
第一步:求整数商c,如进行求模运算c = -2(向负无穷方向舍入),求余c = -1(向0方向舍入)
第二步:计算模和余数的公式相同,但因c的值不同,求模时r = 1,求余时r = -3
练习1
输入半径计算圆的周长和面积
1
2
3
4
5
6
7import math
r = float(input('请输入圆的半径:'))
p = 2*math.pi*r
a = math.pi*r*r
print('周长:%.2f' % p)
print('面积:%.2f' % a)输入年份判断是不是闰年
1
2
3
4
5year = int(input('请输入年份: '))
# 如果代码太长写成一行不便于阅读 可以使用\对代码进行折行
is_leap = (year % 4 == 0 and year % 100 != 0) or \
year % 400 == 0
print(is_leap)
Python中的分支结构
在Python中,要构造分支结构可以使用
if
、elif
和else
关键字。相当于C/C++中的if
、else if
、else
关键字。C/C++、Java等语言不同,Python中没有用花括号来构造代码块而是使用了缩进的方式来设置代码的层次结构,如果if条件成立的情况下需要执行多条语句,只要保持多条语句具有相同的缩进就可以了,换句话说连续的代码如果又保持了相同的缩进那么它们属于同一个代码块,相当于是一个执行的整体。
练习2
百分制成绩转换为等级制成绩
1
2
3
4
5
6
7
8
9
10
11
12score = float(input('请输入成绩: '))
if score >= 90:
grade = 'A'
elif score >= 80:
grade = 'B'
elif score >= 70:
grade = 'C'
elif score >= 60:
grade = 'D'
else:
grade = 'E'
print('对应的等级是:', grade)
Python中的循环结构
python range() 函数可创建一个整数列表,一般用在 for 循环中。
函数语法:
1
range(start, stop[, step])
参数说明:
- start: 计数从 start 开始。默认是从 0 开始。
例如range(5)等价于range(0, 5); - stop: 计数到 stop 结束,但不包括 stop。例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
- step:步长,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)
- start: 计数从 start 开始。默认是从 0 开始。
for-in循环
如果明确的知道循环执行的次数或者要对一个容器进行迭代,那么我们推荐使用
for-in
循环1~100之间的偶数求和
1
2
3
4sum = 0
for x in range(2, 101, 2):
sum += x
print(sum)
while循环
如果要构造不知道具体循环次数的循环结构,我们推荐使用
while
循环。while
循环通过一个能够产生或转换出bool
值的表达式来控制循环,表达式的值为True
循环继续,表达式的值为False
循环结束。0~100随机数猜数游戏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import random
answer = random.randint(1,100)
cnt=0
while True:
cnt +=1
guess = input('请输入你猜的数字')
g = int(guess)
if g>answer:
print('你猜大了')
elif g<answer:
print('你猜小了')
else:
print('恭喜你猜对了')
break
if cnt>=5:
print('你猜了%d次才猜对,太笨了!' %cnt)和C语言一样,上面的代码中使用了
break
关键字来提前终止循环,需要注意的是break
只能终止它所在的那个循环。除了break
之外,还有另一个关键字是continue
,它可以用来放弃本次循环后续的代码直接让循环进入下一轮。
拓展练习
生成斐波那契数列的前20个数
斐波那契数列(Fibonacci sequence),又称黄金分割数列,是意大利数学家莱昂纳多·斐波那契(Leonardoda Fibonacci)在《计算之书》中提出一个在理想假设条件下兔子成长率的问题而引入的数列,所以这个数列也被戏称为”兔子数列”。斐波那契数列的特点是数列的前两个数都是1,从第三个数开始,每个数都是它前面两个数的和,形如:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …。
1
2
3
4
5a = 0
b = 1
for _ in range(20):
a, b = b, a + b
print(a, end=' ')值得注意的是
a, b = b, a + b
是并列赋值语句,它与下列语句处理的结果是不同的!前者a和b同时被赋值,后者a先于b被赋值。1
2a = b
b = a + b
Python中的函数与模块
通过例子,研究函数如何定义函数及其特点
1
2
3
4
5
6
7
8from random import randint #引入随机数函数
def roll_dice(n=2):
"""摇色子"""
total = 0
for _ in range(n):
total += randint(1, 6)
return total1
2
3
4
5
6# 在参数名前面的*表示args是一个可变参数
def add(*args):
total = 0
for val in args:
total += val
return total上述示例说明(定义方法):
- 在Python中可以使用
def
关键字来定义函数。 - 函数执行完成后我们可以通过
return
关键字来返回一个值。
- 在Python中可以使用
特点:
- 在Python中,函数的参数可以有默认值,也支持使用可变参数。
- Python中每个文件就代表了一个模块(module),使用以下语句
from module import function
就可以导入在该模块中的函数。 若导入的多个模块中包含同名函数,可通过以下方式区分要使用的函数。
1
2
3
4
5import module1 as m1
import module2 as m2
m1.function()
m2.function()
注意:如果我们导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,如果你不希望执行这些代码,则需要在这些代码前进行如下操作:
1
2
3if __name__ == '__main__':
#这里是可执行的代码- 原因:
__name__
是Python中一个隐含的变量它代表了模块的名字,只有被Python解释器直接执行的模块的名字才是__main__
。
- 原因:
字符串和常用数据结构
字符串的使用
- 在Python中,字符用单引号或者双引号包围起来,表示一个字符串。
以三个双引号或单引号开头的字符串可以折行。
1
2
3
4
5
6
7
8s1 = '我是字符串!'
s2 = "我是字符串!"
s3 = """我是\
字符串!"""
s4 = '''我是\
字符串!'''
print(s1, s2, s3, s4 , end='')
#输出结果:我是字符串! 我是字符串! 我是字符串! 我是字符串!说明:可以在字符串中使用
\
(反斜杠)来表示转义,也就是说\
后面的字符不再是它原来的意义,例如:\n
不是代表反斜杠和字符n
,而是表示换行;而\t
也不是代表反斜杠和字符t
,而是表示制表符。如果不希望字符串中的\表示转义,我们可以通过在字符串的最前面加上字母
r
来加以说明。1
2
3
4
5
6
7
8s1 = r'\'你好世界!\''
s2 = r'\n\\你好世界!\\\n'
print(s1)
print(s2)
'''输出结果:
\'你好世界!\'
\n\\你好世界!\\\n
'''
Python为字符串类型提供了非常丰富的运算符,我们可以使用
+
运算符来实现字符串的拼接,可以使用*
运算符来重复一个字符串的内容,可以使用in
和not in
来判断一个字符串是否包含另外一个字符串(成员运算),我们也可以用[]
和[:]
运算符从字符串取出某个字符或某些字符(切片运算)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17s1 = 'hello ' * 3
print(s1) # hello hello hello
s2 = 'world'
s1 += s2
print(s1) # hello hello hello world
print('ll' in s1) # True
print('good' in s1) # False
str2 = 'abc123456'
# 从字符串中取出指定位置的字符(下标运算)
print(str2[2]) # c
# 字符串切片(从指定的开始索引到指定的结束索引)
print(str2[2:5]) # c12
print(str2[2:]) # c123456
print(str2[2::2]) # c246
print(str2[::2]) # ac246
print(str2[::-1]) # 654321cba
print(str2[-3:-1]) # 45注意:
- 没有步长的切片:我们用的语法是
s[start:stop]
- 有步长的切片:我们用的语法是
s[start:stop:stride]
- 没有步长的切片:我们用的语法是
格式化输出字符串的三种方法,例:
a * b = 50
常规
1
2a, b = 5, 10
print('%d * %d = %d' % (a, b, a * b))字符串方式
1
2a, b = 5, 10
print('{0} * {1} = {2}'.format(a, b, a * b))简化
1
2a, b = 5, 10
print(f'{a} * {b} = {a * b}')
其他对字符串的操作
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
35str1 = 'hello, world!'
# 通过内置函数len计算字符串的长度
print(len(str1)) # 13
# 获得字符串首字母大写的拷贝
print(str1.capitalize()) # Hello, world!
# 获得字符串每个单词首字母大写的拷贝
print(str1.title()) # Hello, World!
# 获得字符串变大写后的拷贝
print(str1.upper()) # HELLO, WORLD!
# 从字符串中查找子串所在位置
print(str1.find('or')) # 8
print(str1.find('shit')) # -1
# 与find类似但找不到子串时会引发异常
# print(str1.index('or'))
# print(str1.index('shit'))
# 检查字符串是否以指定的字符串开头
print(str1.startswith('He')) # False
print(str1.startswith('hel')) # True
# 检查字符串是否以指定的字符串结尾
print(str1.endswith('!')) # True
# 将字符串以指定的宽度居中并在两侧填充指定的字符
print(str1.center(50, '*'))
# 将字符串以指定的宽度靠右放置左侧填充指定的字符
print(str1.rjust(50, ' '))
str2 = 'abc123456'
# 检查字符串是否由数字构成
print(str2.isdigit()) # False
# 检查字符串是否以字母构成
print(str2.isalpha()) # False
# 检查字符串是否以数字和字母构成
print(str2.isalnum()) # True
str3 = ' jackfrued@126.com '
print(str3)
# 获得字符串修剪左右两侧空格之后的拷贝
print(str3.strip())
列表
与字符串类似,列表也是一种结构化的、非标量类型,它是值的有序序列,每个值都可以通过索引进行标识,定义列表可以将列表的元素放在
[]
中,多个元素用,进行分隔,可以使用for
循环对列表元素进行遍历,也可以使用[]
或[:]
运算符取出列表中的一个或多个元素。基本操作
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
27list1 = [1, 3, 5, 7, 100]
print(list1) # [1, 3, 5, 7, 100]
# 乘号表示列表元素的重复
list2 = ['hello'] * 3
print(list2) # ['hello', 'hello', 'hello']
# 计算列表长度(元素个数)
print(len(list1)) # 5
# 下标(索引)运算
print(list1[0]) # 1
print(list1[-1]) # 100
#修改列表元素
list1[2] = 300
print(list1) # [1, 3, 300, 7, 100]
````
* 遍历操作
```python
# 通过循环用下标遍历列表元素
for index in range(len(list1)):
print(list1[index])
# 通过for循环遍历列表元素
for elem in list1:
print(elem)
# 通过enumerate函数处理列表之后再遍历可以同时获得元素索引和值
for index, elem in enumerate(list1):
print(index, elem)添加或移除元素
- 添加元素
1
2
3
4
5
6
7
8
9list1 = [1, 3, 5, 7, 100]
# 在最后延长
list1.append(200)
# 在下表1位置插入
list1.insert(1, 400)
# 合并两个列表
list1.extend([1000, 2000])
# list1 += [1000, 2000]
print(list1) # [1, 400, 3, 5, 7, 100, 200, 1000, 2000]- 移除元素
1
2
3
4
5
6
7
8
9
10# 先通过成员运算判断元素是否在列表中,如果存在就删除该元素
if 3 in list1:
list1.remove(3)
print(list1) # [1, 400, 5, 7, 100, 200, 1000, 2000]
# 从指定的位置删除元素
list1.pop(0) # 删除下标为0的元素
list1.pop(len(list1) - 1) # 删除表末元素(根据下标)
print(list1) # [400, 5, 7, 100, 200, 1000]
# 清空列表元素
list1.clear()和字符串一样,列表也可以做切片操作,通过切片操作我们可以实现对列表的复制或者将列表中的一部分取出来创建出新的列表。操作方法类似,就不例举了。
排序操作
1
2
3
4
5
6
7
8
9list1 = ['orange', 'apple', 'zoo', 'internationalization', 'blueberry']
# sorted函数返回列表排序后的拷贝不会修改传入的列表
list2 = sorted(list1)
# 倒序排序
list3 = sorted(list1, reverse=True)
# 通过key关键字参数指定根据字符串长度进行排序而不是默认的字母表顺序
list4 = sorted(list1, key=len)
# 给列表对象发出排序消息直接在列表对象上进行排序(直接改变list1)
list1.sort(reverse=True)
生成式和生成器
用列表的生成表达式语法创建列表容器-生成式
1
2
3
4
5f = [x for x in range(1, 10)]
print(f) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
f = [x + y for x in 'AB' for y in '123']
print(f) # ['A1', 'A2', 'A3', 'B1', 'B2', 'B3']- 用这种语法创建列表之后元素已经准备就绪所以需要耗费较多的内存空间。
通过生成器获取到数据-生成器
1
2
3
4
5
6
7import sys
f = (x ** 2 for x in range(1, 10))
print(sys.getsizeof(f)) # 120
print(f) # <generator object <genexpr> at 0x000001FE58DBA4F8>
for val in f:
print(val, end=' ') # 1 4 9 16 25 36 49 64 81- 生成器可以获取到数据但它不占用额外的空间存储数据,每次需要数据的时候就通过内部的运算得到数据需要花费额外的时间。
生成器定义的其他方式-通过
yield
关键字将一个普通函数改造成生成器函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 斐波拉切数列
def fib(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
yield a
def main():
for val in fib(20):
print(val)
if __name__ == '__main__':
main()- 简单理解:
yield
就是return
返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后(下一行)开始。
- 简单理解:
元组
元组与列表类似也是一种容器数据类型,可以用一个变量(对象)来存储多个数据,与列表的区别是元组的元素不能修改。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 定义元组
t = ('周天宇', '男', 20, '四川成都')
print(t) # ('周天宇', 男, 20, '四川成都')
# 获取元组中的元素
print(t[0]) # 周天宇
# 遍历元组中的值
for member in t:
print(member, end=' ') # 周天宇 男 20 四川成都
# 将元组转换成列表
person = list(t)
print(person) # ['周天宇', '男', 20, '四川成都']
# 列表是可以修改它的元素的
person[0] = '周杰伦'
print(person) # ['周杰伦', '男', 20, '四川成都']
# 将列表转换成元组
t2 = tuple(person)- 如果不需要对元素进行添加、删除、修改的时候,可以考虑使用元组。
- 如果一个函数要返回多个值,可以使用元组。
集合
Python中的集合跟数学上的集合是一致的,不允许有重复元素(自动删除),而且可以进行交集、并集、差集等运算。
1
2
3
4
5
6
7
8
9# 创建集合的字面量语法
set1 = {1, 2, 3, 3, 3, 2}
print(set1) # {1, 2, 3,}
print('Length =', len(set1))
# 创建集合的构造器语法
set2 = set(range(1, 10))
set3 = set((1, 2, 3, 3, 2, 1))
# 创建集合的推导式语法(推导式也可以用于推导集合)
set4 = {num for num in range(1, 100) if num % 3 == 0 or num % 5 == 0}添加和删除元素
1
2
3
4
5
6set1.add(4)
set2.update([11, 12])
set2.discard(5) # 去除5
if 4 in set2:
set2.remove(4)
print(set3.pop()) # pop()删除并返回值discard()
和remove()
的区别是remove的元素在set当中没有的话会报错,而discard不会。
交集、并集、差集等运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23# 交集
print(set1 & set2)
# print(set1.intersection(set2))
# 并集
print(set1 | set2)
# print(set1.union(set2))
# 差集
print(set1 - set2)
# print(set1.difference(set2))
# 对称差
print(set1 ^ set2)
# print(set1.symmetric_difference(set2))
# 判断子集
print(set2 <= set1)
# print(set2.issubset(set1))
# 判断超集
print(set1 >= set2)
# print(set1.issuperset(set2))
字典
它可以存储任意类型对象,与列表、集合不同的是,字典的每个元素都是由一个键和一个值组成的“键值对”,键和值通过冒号分开。
创建字典
1
2
3
4
5
6
7
8
9
10
11
12
13# 创建字典的字面量语法
scores = {'周天宇': 95, '胡歌': 78, '周杰伦': 82}
print(scores) # {'周天宇': 95, '胡歌': 78, '周杰伦': 82}
# 创建字典的构造器语法
items1 = dict(one=1, two=2, three=3, four=4)
# 通过zip函数将两个序列压成字典
items2 = dict(zip(['a', 'b', 'c'], '123'))
# 创建字典的推导式语法
items3 = {num: num ** 2 for num in range(1, 10)}
print(items1, items2, items3)
# {'one': 1, 'two': 2, 'three': 3, 'four': 4}
# {'a': '1', 'b': '2', 'c': '3'}
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}其他操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 通过键可以获取字典中对应的值
print(scores['周天宇']) # 95
# 对字典中所有键值对进行遍历
for key in scores: # key只被“返回键”。
print(f'{key}: {scores[key]}')
# 更新字典中的元素
scores['周天宇'] = 85 # 修改
scores['林俊杰'] = 71 # 增添
scores.update(王力宏=77, 陶喆=87) # 增添
print(scores.get('蔡依林'))
# get方法也是通过键获取对应的值但是可以设置默认值
print(scores.get('蔡依林', 60))
# 删除字典中的元素
print(scores.popitem())
print(scores.pop('周天宇', 85))
# 清空字典
scores.clear()
练习3
跑马灯文字
1
2
3
4
5
6
7
8
9
10
11
12
13import os
import time
def main():
text = '蝉鸣的夏季,我想遇见你......'
while True:
os.system('cls')
print(text)
time.sleep(0.2)
text = text[1:] + text[0]
if __name__ == "__main__":
main()os.system('cls')
清除屏幕显示内容。time.sleep(0.2)
休眠0.2秒。
返回文件后缀
1
2
3
4
5
6
7
8
9
10
11
12def get_(fname,has_dot = False):
pos = fname.rfind('.')
if 0 < pos < len(fname) - 1:
index = pos if has_dot else pos +1
return fname[index:]
else:
return ''
if __name__ == "__main__":
fname = input('文件名')
has_dot = True
print(get_(fname,has_dot))- Python
rfind()
返回字符串最后一次出现的位置,如果没有匹配项则返回-1
。 - 语法
str.rfind(str, beg=0 end=len(string))
str
— 查找的字符串。beg
— 开始查找的位置,默认为0。end
— 结束查找位置,默认为字符串的长度。
- Python
返回最大值和第二大值
1
2
3
4
5
6
7
8
9
10
11
12
13
14def max2(x):
m1, m2 = (x[0], x[1]) if x[0] > x[1] else (x[1], x[0])
for index in range(2, len(x)):
if x[index] > m1:
m2 = m1
m1 = x[index]
elif x[index] > m2:
m2 = x[index]
return m1, m2
if __name__ == "__main__":
x = [5,9,12,8,36,4]
a, b = max2(x)
print(a, b)- Python中返回多个值的实质是返回一个
tuple
类型(元组)。 return (a, b)
也可以写作return a, b
。(c, d) = function(x)
也可以写作c, d = function(9, 4)
。
- Python中返回多个值的实质是返回一个
面向对象编程基础
- 面向对象有三大支柱:封装、继承和多态。
- 封装:我自己对封装的理解是”隐藏一切可以隐藏的实现细节,只向外界暴露(提供)简单的编程接口。
类和对象
- 类是对象的蓝图和模板,对象是类的实例。
定义类:Python中可以使用
class
关键字定义类。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Student(object):
# __init__是一个特殊方法用于在创建对象时进行初始化操作
# 通过这个方法我们可以为学生对象绑定name和age两个属性
def __init__(self, name, age):
self.name = name
self.age = age
def study(self, course_name):
print('%s正在学习%s.' % (self.name, course_name))
# PEP 8要求标识符的名字用全小写多个单词用下划线连接
# 但是部分程序员和公司更倾向于使用驼峰命名法(驼峰标识)
def watch_movie(self):
if self.age < 18:
print('%s只能观看《熊出没》。' % self.name)
else:
print('%s正在观看《不能说的秘密》。' % self.name)创建和使用对象
1
2
3
4
5
6
7
8
9
10
11
12
13def main():
# 创建学生对象并指定姓名和年龄
stu1 = Student('周天宇', 20)
# 给对象发study消息
stu1.study('Python程序设计')
# 给对象发watch_movie消息
stu1.watch_movie()
stu2 = Student('小屁孩', 12)
stu2.study('思想品德')
stu2.watch_movie()
if __name__ == '__main__':
main()
访问可见性问题
在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
# AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# AttributeError: 'Test' object has no attribute '__foo'
print(test.__foo)
if __name__ == "__main__":
main()事实上,Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来妨碍对它们的访问。如果你知道更换名字的规则仍然可以访问到它们。
1
2test._Test__bar()
print(test._Test__foo)在实际开发中,并不建议将属性设置为私有的,因为这会导致子类无法访问。所以大多数Python程序员会遵循一种命名惯例,就是让属性名以单下划线开头来表示属性是受保护的。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻。
1
2
3
4
5
6def __init__(self, foo):
self._foo = foo # 此处改为单下划线
def _bar(self): # 此处改为单下划线
print(self._foo)
print('_bar')Python类中的那些魔法方法,如
__str__
、__repr__
等,这些方法并不是私有成员,虽然它们以双下划线开头,但是他们也是以双下划线结尾的,这种命名并不是私有成员的命名。
面向对象进阶
@property装饰器
我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。
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
38class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
# 访问器 - getter方法
def name(self):
return self._name
# 访问器 - getter方法
def age(self):
return self._age
# 修改器 - setter方法
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('周天宇', 16)
person.play()
person.age = 20
person.play()
# person.name = '白元芳' # AttributeError: can't set attribute(name属性未添加修改器所以不可通过这种方式修改)
if __name__ == '__main__':
main()
slots魔法:限制动态添加变量
- Python是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。
但是如果我们需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义
__slots__
变量来进行限定。需要注意的是__slots__
的限定只对当前类的对象生效,对子类并不起任何作用。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
34class Person(object):
# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')
def __init__(self, name, age):
self._name = name
self._age = age
def name(self):
return self._name
def age(self):
return self._age
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('周天宇', 20)
person.play()
person._gender = '男'
# person._is_handsome = True
# AttributeError: 'Person' object has no attribute '_is_handsome'(类开头的__slots__限定了不能添加新的属性。)合理使用
__slots__
属性可以节省一个对象所消耗的空间。一个普通对象使用一个dict来保存它自己的属性,你可以动态地向其中添加或删除属性,但是如果使用__slots__
属性,那么该对象用来保存其自身属性的结构一旦创建则不能再进行任何修改。因此使用slot结构的对象节省了一部分开销。
Python 实例方法、类方法和静态方法
实例方法,类方法与静态方法的区别
用代码说明实例方法,类方法,静态方法的区别。注意下述代码中方法
foo
,class_foo
,static_foo
的定义以及使用。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
30class Kls(object):
def foo(self, x):
print('executing foo(%s,%s)' % (self, x))
def class_foo(cls,x):
print('executing class_foo(%s,%s)' % (cls,x))
def static_foo(x):
print('executing static_foo(%s)' % x)
ik = Kls()
# 实例方法
ik.foo(1)
print(ik.foo)
print('==========================================')
# 类方法
ik.class_foo(1)
Kls.class_foo(1)
print(ik.class_foo)
print('==========================================')
# 静态方法
ik.static_foo(1)
Kls.static_foo('hi')
print(ik.static_foo)输出结果
1
2
3
4
5
6
7
8
9
10executing foo(<__main__.Kls object at 0x0551E190>,1)
<bound method Kls.foo of <__main__.Kls object at 0x0551E190>>
==========================================
executing class_foo(<class '__main__.Kls'>,1)
executing class_foo(<class '__main__.Kls'>,1)
<bound method type.class_foo of <class '__main__.Kls'>>
==========================================
executing static_foo(1)
executing static_foo(hi)
<function static_foo at 0x055238B0>实例方法:调用时会把实例ik作为第一个参数传递给
self
参数。因此,调用ik.foo(1)
时输出了实例ik的地址。类方法:调用时会把类Kls作为第一个参数传递给
cls
参数。因此,调用ik.class_foo(1)
时输出了Kls
类型信息。
前面提到,可以通过类也可以通过实例来调用类方法,在上述代码中,我们再一次进行了验证。静态方法:调用时并不需要传递类或者实例。其实,静态方法很像我们在类外定义的函数,只不过静态方法可以通过类或者实例来调用而已。
感谢CSDN博主haozlee原文链接。
类之间的关系
类和类之间的关系有三种:is-a
、has-a
和use-a
关系
- is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
- has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
- use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。
继承和多态
继承:提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。
- 子类继承父类提供的属性和方法。
- 子类可以定义自己特有的属性和方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78class Person(object):
"""人"""
def __init__(self, name, age):
self._name = name
self._age = age
def name(self):
return self._name
def age(self):
return self._age
def age(self, age):
self._age = age
def play(self):
print('%s正在愉快的玩耍.' % self._name)
def watch_video(self):
if self._age >= 18:
print('%s正在观看《不能说的秘密》.' % self._name)
else:
print('%s只能观看《熊出没》.' % self._name)
class Student(Person):
"""学生"""
def __init__(self, name, age, grade):
super().__init__(name, age)
self._grade = grade
def grade(self):
return self._grade
def grade(self, grade):
self._grade = grade
def study(self, course):
print('%s的%s正在学习%s.' % (self._grade, self._name, course))
class Teacher(Person):
"""老师"""
def __init__(self, name, age, title):
super().__init__(name, age)
self._title = title
def title(self):
return self._title
def title(self, title):
self._title = title
def teach(self, course):
print('%s%s正在讲%s.' % (self._name, self._title, course))
def main():
stu = Student('王大锤', 15, '初三')
stu.study('数学')
stu.watch_video()
t = Teacher('周天宇', 20, '大二')
t.teach('Python程序设计')
t.watch_video()
if __name__ == '__main__':
main()多态:通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为。
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
37from abc import ABCMeta, abstractmethod
class Pet(object, metaclass=ABCMeta):
"""宠物"""
def __init__(self, nickname):
self._nickname = nickname
def make_voice(self):
"""发出声音"""
pass
class Dog(Pet):
"""狗"""
def make_voice(self):
print('%s: 汪汪汪...' % self._nickname)
class Cat(Pet):
"""猫"""
def make_voice(self):
print('%s: 喵...喵...' % self._nickname)
def main():
pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
for pet in pets:
pet.make_voice()
if __name__ == '__main__':
main()- abc.ABCMeta:简单的说ABCMeta就是让你的类变成一个纯虚类,子类必须实现某个方法,这个方法在父类中用
@abc.abstractmethod
修饰。 - 你可以在父类中实现这两个虚方法,也可以不实现。但在子类中就必须实现被“虚化”的函数,否则会报错。例如上述的
make_voice()
就在子类(Dog
、Cat
)中被实现了两种版本,这个方法就表现出了多态行为(同样的方法做了不同的事情)。
- abc.ABCMeta:简单的说ABCMeta就是让你的类变成一个纯虚类,子类必须实现某个方法,这个方法在父类中用
综合案例
- 工资结算系统
某公司有三种类型的员工分别是部门经理、程序员和销售员需要设计一个工资结算系统根据提供的员工信息来计算月薪:
- 部门经理的月薪是每月固定15000元。
- 程序员的月薪按本月工作时间计算 每小时150元。
- 销售员的月薪是1200元的底薪加上销售额5%的提成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92from abc import ABCMeta, abstractmethod
class Employee(object, metaclass=ABCMeta):
"""员工"""
def __init__(self, name):
"""
初始化方法
:param name: 姓名
"""
self._name = name
def name(self):
return self._name
def get_salary(self):
"""
获得月薪
:return: 月薪
"""
pass
class Manager(Employee):
"""部门经理"""
def get_salary(self):
return 15000.0
class Programmer(Employee):
"""程序员"""
def __init__(self, name, working_hour=0):
super().__init__(name)
self._working_hour = working_hour
def working_hour(self):
return self._working_hour
def working_hour(self, working_hour):
self._working_hour = working_hour if working_hour > 0 else 0
def get_salary(self):
return 150.0 * self._working_hour
class Salesman(Employee):
"""销售员"""
def __init__(self, name, sales=0):
super().__init__(name)
self._sales = sales
def sales(self):
return self._sales
def sales(self, sales):
self._sales = sales if sales > 0 else 0
def get_salary(self):
return 1200.0 + self._sales * 0.05
def main():
emps = [
Manager('刘备'), Programmer('诸葛亮'),
Manager('曹操'), Salesman('荀彧'),
Salesman('吕布'), Programmer('张辽'),
Programmer('赵云')
]
for emp in emps:
if isinstance(emp, Programmer):
emp.working_hour = int(input('请输入%s本月工作时间: ' % emp.name))
elif isinstance(emp, Salesman):
emp.sales = float(input('请输入%s本月销售额: ' % emp.name))
# 同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态)
print('%s本月工资为: ¥%s元' %
(emp.name, emp.get_salary()))
if __name__ == '__main__':
main()super()函数
super()
函数是用于调用父类(超类)的一个方法。- super是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
- Python3.x 和 Python2.x 的一个区别是:Python3.x可以使用直接使用
super().xxx
代替super(Class, self).xxx
:
1
2
3
4
5
6
7
8
9class A:
def add(self, x):
y = x+1
print(y)
class B(A):
def add(self, x):
super().add(x)
b = B()
b.add(2) # 3super().__init__()
- 作用:执行父类的构造函数,使得我们能够调用父类的属性。
- 对继承自父类的属性进行初始化。而且是用父类的初始化方法来初始化继承的属性。也就是说,子类继承了父类的所有属性和方法,父类属性自然会用父类方法来进行初始化。
- 当然,如果初始化的逻辑与父类的不同,不使用父类的方法,自己重新初始化也是可以的。
isinstance() 函数
isinstance()
函数来判断一个对象是否是一个已知的类型,类似type()
。isinstance()
与type()
区别:type()
不会认为子类是一种父类类型,不考虑继承关系。isinstance()
会认为子类是一种父类类型,考虑继承关系。如果要判断两个类型是否相同推荐使用
isinstance()
。
- 语法:
isinstance(object, classinfo)
- 参数:
- object — 实例对象。
- classinfo — 可以是直接或间接类名、基本类型或者由它们组成的元组。
- 如果对象的类型与参数二的类型(classinfo)相同则返回
True
,否则返回False
。
文件和异常
读写文本文件
读文本文件
Python
open()
方法用于打开一个文件,并返回文件对象,在对文件进行处理过程都需要使用到这个函数。1
2
3
4
5
6
7
8def main():
f = open('致橡树.txt', 'r', encoding='utf-8')
print(f.read())
f.close()
if __name__ == '__main__':
main(- 一般在读取文本文件时,需要在使用
open()
函数时指定好带路径的文件名(可以使用相对路径或绝对路径)并将文件模式设置为'r'
(如果不指定,默认值也是'r'
),然后通过encoding参数指定编码。 - 使用
open()
方法一定要保证关闭文件对象,即调用close()
方法。 open()
函数常用形式是接收两个参数:文件名(file)和模式(mode)。1
open(file, mode='r')
更多
open()
参数和方法链接:菜鸟教程
- 一般在读取文本文件时,需要在使用
如果open函数指定的文件并不存在或者无法打开,那么将引发异常状况导致程序崩溃。为了让代码有一定的健壮性和容错性,我们可以使用Python的异常机制对可能在运行时发生状况的代码进行适当的处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def main():
f = None
try:
f = open('致橡树.txt', 'r', encoding='utf-8')
print(f.read())
except FileNotFoundError:
print('无法打开指定的文件!')
except LookupError:
print('指定了未知的编码!')
except UnicodeDecodeError:
print('读取文件时解码错误!')
# finally块的代码不论程序正常还是异常都会执行到
finally:
if f:
f.close()
if __name__ == '__main__':
main()更简洁的写法
with
关键字。1
2with open('file_name','r') as f:
r=f.read()当
with
里面的语句产生异常的话,也会正常关闭文件。如果要让代码有更好的健壮性和容错性,这样写即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14def main():
try:
with open('致橡树.txt', 'r', encoding='utf-8') as f:
print(f.read())
except FileNotFoundError:
print('无法打开指定的文件!')
except LookupError:
print('指定了未知的编码!')
except UnicodeDecodeError:
print('读取文件时解码错误!')
if __name__ == '__main__':
main()更多
try/except
语句和异常处理详解:菜鸟教程除了使用文件对象的read方法读取文件之外,还可以使用
for-in
循环逐行读取或者用readlines
方法将文件按行读取到一个列表容器中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import time
def main():
# 一次性读取整个文件内容
with open('致橡树.txt', 'r', encoding='utf-8') as f:
print(f.read())
# 通过for-in循环逐行读取
with open('致橡树.txt', 'r', encoding='utf-8') as f:
for line in f:
print(line, end='')
time.sleep(0.5)
print()
# 读取文件按行读取到列表中
with open('致橡树.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
print(lines)
if __name__ == '__main__':
main()注意:使用
readline()
直接读取文件会读出换行符\n
,如果想要和上面的for-in
循环效果一样,可这样写:1
2
3
4
5
6
7
8
9
10
11
12import time
def main():
with open('逍遥游.txt', 'r', encoding='utf-8') as f:
for line in f.readlines():
line=line.strip('\n')
print(line)
time.sleep(0.5)
if __name__ == '__main__':
main()
写入文本文件
- 将1-9999之间的素数分别写入三个文件中。
1 | from math import sqrt # 求平方根 |
- 需要注意的是元组中的元素为不可变更的对象,所以这里的三个txt文件用的是列表来操作。
append()
函数,用于在列表末尾添加新的对象。- 语法:
1
list.append(obj)
读写二进制文件
复制图片文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def main():
try:
with open('guido.jpg', 'rb') as fs1:
data = fs1.read()
print(type(data)) # <class 'bytes'>
with open('吉多.jpg', 'wb') as fs2:
fs2.write(data)
except FileNotFoundError as e: # e用于创建自定义异常类的实例
print('指定的文件无法打开.')
except IOError as e:
print('读写文件时出现错误.')
print('程序执行结束.')
if __name__ == '__main__':
main()type()
函数,只有第一个参数则返回对象的类型,三个参数返回新的类型对象。- 语法:
1
2
3
4
5
6
7
8type(object)
type(name, bases, dict)
'''
name -- 类的名称。
bases -- 基类的元组。
dict -- 字典,类内定义的命名空间变量。
'''
读写JSON文件
- JSON与Python数据类型对应关系:
JSON | Python |
---|---|
dict | object |
list, tuple | array |
str | string |
int, float, int- & float-derived Enums | number |
True / False | true / false |
None | null |
- Python与JSONn数据类型对应关系:
JSON | Python |
---|---|
object | dict |
array | list |
string | str |
number (int / real) | int / float |
true / false | True / False |
null | None |
使用Python中的json模块就可以将字典或列表以JSON格式保存到文件中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import json
def main():
mydict = {
'name': '周天宇',
'age': 20,
'friends': ['周杰伦', '林俊杰'],
}
try:
with open('data.json', 'w', encoding='utf-8') as fs:
json.dump(mydict, fs)
except IOError as e:
print(e)
print('保存数据完成!')
if __name__ == '__main__':
main()json模块主要有四个比较重要的函数,分别是:
dump
- 将Python对象按照JSON格式序列化到文件中。dumps
- 将Python对象处理成JSON格式的字符串。load
- 将文件中的JSON数据反序列化成对象。loads
- 将字符串的内容反序列化成Python对象。
Python对正则表达式的支持
- 正则表达式基础符号表。
- Python提供的re模块中的核心函数。
函数 | 说明 |
---|---|
compile(pattern, flags=0) | 编译正则表达式返回正则表达式对象 |
match(pattern, string, flags=0) | 用正则表达式匹配字符串 成功返回匹配对象 否则返回None |
search(pattern, string, flags=0) | 搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回None |
split(pattern, string, maxsplit=0, flags=0) | 用正则表达式指定的模式分隔符拆分字符串 返回列表 |
sub(pattern, repl, string, count=0, flags=0) | 用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用count指定替换的次数 |
fullmatch(pattern, string, flags=0) | match函数的完全匹配(从字符串开头到结尾)版本 |
findall(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回字符串的列表 |
finditer(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回一个迭代器 |
purge() | 清除隐式编译的正则表达式的缓存 |
re.I / re.IGNORECASE | 忽略大小写匹配标记 |
re.M / re.MULTILINE | 多行匹配标记 |
实际开发中可以用正则表达式对象的方法替代对这些函数的使用,如果一个正则表达式需要重复的使用,先通过
compile()
函数编译正则表达式并创建出正则表达式对象更好。例子:验证输入用户名和QQ号是否有效并给出对应的提示信息。
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"""
验证输入用户名和QQ号是否有效并给出对应的提示信息
要求:用户名必须由字母、数字或下划线构成且长度在6~20个字符之间,QQ号是5~12的数字且首位不能为0
"""
import re
def main():
username = input('请输入用户名: ')
qq = input('请输入QQ号: ')
# match函数的第一个参数是正则表达式字符串或正则表达式对象
# 第二个参数是要跟正则表达式做匹配的字符串对象
m1 = re.match(r'^[0-9a-zA-Z_]{6,20}$', username) # r'原始字符串
if not m1:
print('请输入有效的用户名.')
m2 = re.match(r'^[1-9]\d{4,11}$', qq)
if not m2:
print('请输入有效的QQ号.')
if m1 and m2:
print('你输入的信息是有效的!')
if __name__ == '__main__':
main()- 上面在书写正则表达式时使用了“原始字符串”的写法(在字符串前面加上了r),“原始字符串”就是字符串中的每个字符都是它原始的意义(声明字符串中没有转义字符)。因为正则表达式中有很多元字符和需要进行转义的地方,如果不使用原始字符串就需要将反斜杠写作
\\
,例如表示数字的\d
得书写成\\d
。