Python语言简介
Python由荷兰数学和计算机科学研究学会的吉多·范罗苏姆于1990年代初设计,作为一门叫做ABC语言的替代品。 Python提供了高效的高级数据结构,还能简单有效地面向对象编程。Python语法和动态类型,以及解释型语言的本质,使它成为多数平台上写脚本和快速开发应用的编程语言,随着版本的不断更新和语言新功能的添加,逐渐被用于独立的、大型项目的开发。
Python解释器易于扩展,可以使用C语言或C++(或者其他可以通过C调用的语言)扩展新的功能和数据类型。Python也可用于可定制化软件中的扩展程序语言。Python丰富的标准库,提供了适用于各个主要系统平台的源码或机器码。
自从20世纪90年代初Python语言诞生至2022年,它已被逐渐广泛应用于系统管理任务的处理、科学计算、数据分析、Web编程和人工智能等广泛领域。
Python语言的设计哲学是:对于一个特定的问题,只要有一种最好的方法来解决。
变量
Python是一门超级简单的语言,定义变量如下代码:
# 定义一个变量并赋值
a = 10
# 定义一个变量,先不赋值
b = None
# 已经定义的变量重新赋值
a = 100
打印输出
将程序运行结果打印输出到控制台是非常常见的操作,如下所示:
print(10)
print('what')
第一行输出一个整数10,第二行输出一个字符串what,在Python中,字符串可以用单引号,也可以用双引号,还可以用三引号'''
或"""
,三引号更多的时候用于多行文本。
数据类型
作为一门正宗的编程语言,Python也支持非常丰富的数据类型用于处理各种数据,如下所示:
# 数值
a = 1
# 逻辑值
b = True
# 字符串
c = 'Hello World'
# 列表
d = ['a', 'b', 'c']
# 字典
e = {'name': '张三', 'age': 20, 'location': '北京市'}
# 元组
f = (1, 2, 3)
f1 = (1, 2, 'what')
# 列表和字典支持嵌套
d1 = ['a', ['a1', 'a2', 'a3'], 'b']
e1 = {
'students': {
'001': {
'name': '张三',
'age': 18
},
'002': {
'name': '张三',
'age': 18
}
},
'teachers': {
'class1': {
'name': '语文',
'order': 1
},
'class2': {
'name': '数学',
'order': 2
}
}
}
# 复数
a = 3 + 2j
b = 2 + 5j
有没有发现,Python的数据类型非常丰富?熟悉其他编程语言的同学一定看到了,Python还支持元组和复数,这也是非常有用的。
> Python对科学计算支持的非常强大,因此复数很有用的。
关系运算
关系运算是对两个值进行比较的逻辑结果,它的计算结果有两个值True
和False
,看下面的代码:
a = 1 is 1
print(a)
b = 1 is not 1
print(b)
c = 1 == 1
print(c)
d = 1 != 1
print(c)
e = True
print(e)
print(not e)
a = 1
b = 2
print(a < b)
# 逻辑短路
print(1 < 1 and a < 2)
print(1 == 1 or a < 2)
注意,在Python中,比较运算符==
和is
是不同的,前者是对对象的值进行比较,值相等就为True
,而后者是判断是不是同一个对象(不仅值相等,并且在内存中也是同一个东西,才叫同一个对象)。
数据运算
数据运算是Python的拿手好戏,它支持很大很大的整数的运算和小数(也就是浮点数)的运算,这在许多编程语言中是做不到的。
# 加
print(1 + 1)
# 减
print(1 - 1)
# 乘
print(2 * 2)
# 除
print(10 / 3)
# 小数/浮点数
print(1 / 5)
# 整除
print(10 / 5)
# 取余/取模
print(10 % 6)
# 手动控制运算优先级
print(10 * (2 - 2))
# is not
print(not True)
print(not False)
print(1 is 1)
print('表达式与常量:', 2 is 4 / 2) # 2是个对象,4/2也是个对象,一个是数值对象,一个是表达式,它们是不一样的
print('表达式与表达式1:', 2 / 1 is 4 / 2) # 2/1是个表达式,4/2也是个表达式
print('表达式与表达式2:', 2 / 1 is 6 / 2) # 虽然2/1是个表达式,6/2也是个表达式,但是它们结果不相同,所以是False
# 复数
a = 3 + 2j
b = 2 + 5j
# 复数求和
print(a + b)
# 共轭复数
print('共轭复数:', a.conjugate())
看到没,基础的算术运算,逻辑运算,复数等各种复杂的运算,都支持。
数据运算意外
当我们学会了用Python做一些基础的数据运算的时候,我们却还会遇到一些意想不到的情况,看下面的代码:
# 下面的算式,小数部分是3333333333333333
print(1 / 3)
# 下面的算式,小数部分是3333333333333335而不是3333333333333333
print(10 / 3)
# 下面的算式,小数部分是3333333333333336而不是3333333333333333
print(100 / 3)
# 下面的算式,小数部分是3333333333333333
print(1000 / 3)
# 下面的算式,小数部分是3333333333333335而不是3333333333333333
print(10000 / 3)
# 下面的算式,小数部分是3333332而不是3333333
print(100000000 / 3)
运行上述代码,你一定会发现惊喜!
那么,为什么这些算式的计算结果不是我们数学课堂上所学的那样的?简单地说,这是因为计算机进行数学运算是有限运算,它受各种存储空间、存储位的限制,所以才会出现这种情况。
顺序控制
这个实在太简单了,一行一行往下执行就好了,你写的是什么顺序,计算机执行的时候就是什么顺序
print(1)
print(2)
print(3)
print(4)
print(5)
执行上述代码就会依次输出1、2、3、4、5。实在太简单,没什么可说的了。
分支控制
分支控制和其他编程语言一样,都是用来做出决定,判断到底走哪一条路的。
简单的分支判断
print('start')
a = 3
b = 3
if a is b:
print('yes')
else:
print('no')
print('end')
注意:同一个Python文件中,多行代码中,同一个缩进级别的语句行,缩进要一致。什么意思呢?以上述代码为例,print('yes')
这一行和print('no')
这一行前面的缩进要一样,否则会报语法错误。之所以这么设计,是为了使代码的可读性更好。
稍微复杂的一点的
也就是可以反复判断,多分支
score = 10
if score < 60:
print('不及格')
elif score < 90:
print('良好')
elif score < 100:
print('优秀')
else:
print('分数错误')
注意:Python中没有许多编程语言中的所谓的多分支的switch语句。
循环控制
所谓循环,就是重复做一件事情,Python中的循环很强大。
简单循环
预先知道循环次数的这种:
for i in range(10):
print(i)
和其他编程语言一样,这种循环是右开区间,即:i的值是从0到9,不包括右边界10。
设置循环范围
不是从1开始循环的情况:
for i in range(5, 10):
print(i)
设置循环步进
虽然知道循环方式,但是循环变量每次不是+1,而是可以指定,这是一个+2的例子
for i in range(1, 10, 2):
print(i)
死循环
不知道要循环多少次,那就写个死循环:
while True:
print(1)
当然了,实际中代码不可能真这么写,因为这没有意义,实际上都是在死循环内部,当触发某件事情的时候,用break
语句跳出循环,不可能真让程序永无止境的永远执行下去。
不循环
注意看下面的代码:
#!/usr/bin/python3
sites = ["百度", "谷歌","大伟技术学堂","淘宝网", "京东"]
for site in sites:
if site == "大伟技术学堂":
print("最棒的是???\n\t", end='')
print("循环数据 " + site)
else:
print("没有循环数据!")
print("完成循环!")
函数定义
Python中定义函数非常简单,使用def关键字即可声明一个函数:
def add(n1, n2):
return n1 + n2
print(add(1, 2))
注意:上述代码中,第一行是函数声明部分,又称函数头部,第二行中函数体(当然可以有多行,本例中只有一行而已),第三行不是函数的一部分,因为它没有缩进,它不是函数体。
函数是对一块相对完整的业务逻辑的整合,或者对能够在许多地方都会用到的功能的聚合,这样可以大幅减少代码量,又便于维护。
函数参数
Python中的函数参数不同于其他常见的编程语言,它具有很强大又灵活的功能。
形参与实参
下面的定义中,n1和n2就是形参
def add(n1, n2):
return n1 + n2
# 数字1和2分别会传给n1和n2,数字1和数字2是实参
r = add(1, 2)
print(r)
命名参数
命名参数就是调用函数时给参数指定名称,这样便于一眼看清楚指定的值是传给哪个参数的,代码的可读性非常高。
def out(name):
print('hello ' + name)
out(name='world') # 此时字符串world是个命名参数
def out(a=1, b=2):
print(a + b)
out(a=2, b=3)
out(b=3, a=2)
默认参数,也称可选参数
默认参数在许多编程语言中也存在,它的意思很简单,如果调用函数的人传参数了,就用调用方的,如果没传,就使用函数声明的头部指定的值。
def out(name='world'):
print('hello ' + name)
out()
out(name='xiao wang')
关键字参数
def total(factor, **kw):
r = 0
if 'k' in kw:
r = factor * 2
elif 'j' in kw:
r = factor * 3
else:
r += factor
print(r)
total(2)
total(2, k=2)
total(2, j=3)
关键字参数是Python的独创,它使得一个定义好的函数可以拥有非常强大的参数和数据交互扩展功能,能够让我们在不改变函数声明的情况下,通过一些事先的约定,传入特定数据,进而以最小的改动代价完成软件功能。
函数返回值
使用return显式地声明返回值
def add(n1, n2):
return n1 + n2
print(add(1, 2))
很明显,会得到传入的两个数值的和。
默认返回值
def add(n1, n2):
n1 + n2
print(add(1, 2))
如果没有写return,将会返回函数最后一行语句的值作为返回值(如果有的话)。但是要注意,如果函数体中有分支判断,有可能使得结果发生变化。
多个返回值
有些时候,我们需要多个返回值
def some():
return 1, 2, 3
print(some())
是不很直观?当然了,返回多个值有很多种方法,使得我们前面学过的各种数据结构如列表、字典、元组等都可以做到。
没有明确返回值的函数
我们当然也要允许函数没有返回值,某些时候,函数执行完就是执行完了,确实没有必要返回什么。
def p(n):
print(n)
r = p(10)
print(r)
闭包
在开发中,我们经常会遇到开发阶段还不能确定到底要执行什么操作,比如到底是加法还是减法,这个时候,就可以预定义一个加法和一个减法,然后在程序运行期间动态判断。
def increment(n):
return n + 1
def decrement(n):
return n - 1
def change_num(fn, n):
return fn(n)
# 预定义一个加法和一个减法,然后动态判断
print(change_num(increment, 2))
print(change_num(decrement, 2))
再来看一个应用安全:让学生输入自己的考试成绩,然后对该学生的考试成绩进行评估:
# 如果小于60分,就执行留级函数,如果大于60,就执行毕业函数
def done(student_id):
print('学生 {} 办理毕业手续'.format(student_id))
def keep(student_id):
print('学生 {} 不能毕业,先留一级考察再定'.format(student_id))
def check_student(fn, student_id):
print('对学生 {} 考试成绩开始进行评估'.format(student_id))
fn(student_id)
print('对学生 {} 考试成绩的评估已经完成'.format(student_id))
score = input('请输入考试成绩:')
if int(score) < 60:
check_student(keep, 1)
elif int(score) < 100:
check_student(done, 1)
else:
print('神回答')
对上述代码,我们作一些解释:
- 代码print('学生 {} 办理毕业手续'.format(student_id))
是格式化输出,字符串中的空花括号是占位符,会由后面的format函数的参数填充
- 代码score = input('请输入考试成绩:')
是先输出括号中的字符串作为提示信息,然后等用户输入之后,保存到score变量中
- 上述代码的动态性主要体现在调用check_student
上,这个函数的第一个参数本身就是一具函数对象,所以,外部调用check_student
时传不同的函数,就可以做不同的事情
- 这种实现机制,相信熟悉OOP(面向对象编程)的同学一定会有感觉,具有多态的能力。
lambda表达式
lambda表达式在许多编程语言中都存在,本站的Java教程中也有涉及。简单地说,lambda表达式就是将一段代码包装成一个语句,可以一次执行。如下所示:
my_sum = lambda x, y: x + y
print(my_sum)
print(my_sum(10, 20))
上面代码的第一行,就是一个求和的lambda表达式声明,然后第二行是打印输出这个lambda表达式对象,第三行是使用它,可以给它传两个参数以求得一个和。
lambda表达式对于简化代码是非常有用的。
函数替换
函数替换,是Python中非常有趣的一种应用方法,它可以让我们在程序运行期间动态决定执行哪个流程。
请看代码:
import random
def total_1(n1, n2):
return n1 + n2
total_2 = lambda n1, n2: n1 + n2
for i in range(1, 5):
n1 = random.randint(1, 10)
n2 = random.randint(1, 10)
choice = random.randint(0, 3)
if choice is 0:
print('FUNCTION {} + {} = {}'.format(n1, n2, total_1(n1, n2)))
elif choice is 1:
print('LAMBDA {} + {} = {}'.format(n1, n2, total_2(n1, n2)))
else:
print('what???')
有可能执行函数total_1,也有可能执行lambda表达式total_2,这完全取决于外部条件。
列表与切片
列表和切片是Python中非常常见的数据类型,直接看代码吧:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 遍历
# for n in numbers:
# print(n)
# 添加
numbers.append(11)
print(numbers)
numbers.append('a') # 可以添加不同类型的数据
print(numbers)
# 删除
numbers.pop() # 删除最后一个
numbers.pop(1) # 删除指定索引位,后面的数据会往前移动
print(numbers)
# 修改
numbers[2] = 100
print(numbers)
# 切片:无论下标是正数还是负数,都是从0开始,正数从左至右,使用右开区间,负数从右至左,使用左开区间
# 即:n:m,如果n和m都是正数,切片区间为右开区间:[n,m),如果n和m都是负数,切片区间为左开区间:(n,m]
print('3: ==>', numbers[3:]) # 取下标为3的及之后的所有数据
print(':3 ==>', numbers[:3]) # 取下标为3的之前的数据
print(':-3 ==>', numbers[:-3]) # 取下标为-3的及之前的所有数据
print('2:5 ==>', numbers[2:5]) # 取下标为2的及其到下标为5的所有数据,不含下标为5的数据,右开区间
print('-3:-1 ==>', numbers[-3:-1]) # 取下标为-3到下标为-1之间的数据,不含下标为-3的数据,左开区间
那么,到底什么是切片呢?其实切片就是对列表的截取操作,比如对一个列表,取出前3个元素,这就是个切片。
字典
字典是一种键值对的映射数据类型,在实践中也是非常常用的。
字典定义
# 简单的
info = {
'name': 'wang',
'age': 18,
'in': '2019-10-12'
}
# 支持嵌套
student = {
'name': 'wang',
'class': {
'level': 1,
'score': 120,
'teacher': 'Mr. li',
}
}
# 复杂类型
students = [
{
'name': 's_1',
'lesson': 'Language',
'score': 100,
},
{
'name': 's_2',
'lesson': 'Language',
'score': 110,
}
]
树状数据结构
这个经常用于菜单:
menuTree = [
{
'name': '首页',
'link': '/',
'icon': './img/dashboard.png'
},
{
'name': '客户管理',
'link': '/customer.html',
'icon': './img/customer_list.png'
},
{
'name': '系统设置',
'link': None,
'icon': './img/settings.png',
'children': [
{
'name': '角色信息',
'link': '/role.html',
'icon': './img/role_list.png'
},
{
'name': '系统资源',
'link': '/resource.html',
'icon': './img/resource_list.png'
}
]
}
]
遍历字典
# 遍历Key和Value
for k, v in info.items():
print('KEY {} => VALUE {}'.format(k, v))
# 另外一种遍历Key和Value的方式
for k in info:
print(k, '\t', info[k])
# 仅遍历Key
for k in info.keys():
print(k)
# 仅遍历Value
for _, v in info.items():
print(v)
# 仅遍历Value的另一种方式
for v in info.values():
print(v)
文件处理
Python读写文本时,有一个读写模式,它的C语言一脉相承,如下所示:
读写模式 |
是否可读 |
是否可写 |
文件不存在时 |
r |
是 |
否 |
报错 |
r+ |
是 |
是,覆盖写入 |
报错 |
w |
否 |
是,清空原内容 |
创建新文件 |
w+ |
是 |
是,清空原内容 |
创建新文件 |
a |
否 |
是,末尾追加写入 |
创建新文件 |
a+ |
是 |
是,末尾追加写入 |
创建新文件 |
操作方式有两种
with方式:
with open(file_name, 'r') as f:
f.read()
print(f.tell()) # f.tell() 显示当前光标位置
f.seek(0) # 移动光标到0的位置
f.seek(10) # 移动光标到10的位置
这种方式操作打开的文件f后,不必手动关闭,但是我个人的感觉是如果代码比较长,会看起来比较费劲
传统方式:
f = open(file_name,'w')
f.write("这是写入的内容。")
#这种写法需要手动关闭文件后再查看文件,才能看到内容
f.close()
这种操作方式,要将打开的文件关闭,否则会造成一系列的问题。但是,释放打开的资源,是程序员的必备技能,不应该忘记。
⚠注意:如果使用open函数读写文件时出现了中文编码问题,就要手动指定编码:open(file_name,'r',encoding='utf-8')
。
读写文本文件
使用open函数和read、write函数,默认就是文本模式。
代码很简单:
text = '我爱python'
f = open("a.txt", 'w', encoding='utf8')
f.write(text)
# 将数据写入磁盘文件
f.close()
读文件:
f = open('file.txt', 'r')
res = f.read()
print(res)
f.close()
其实的read()
函数可以指定一个整数,读多少个字符(文本模式下),如果未指定,就是读取所有内容,所以可以经常要逐行处理:
f = open('a.txt', 'r')
all_line = f_in.read().splitlines()
for line in all_line:
print(line)
f_in.close()
读写二进制文件
二进制文件操作方法和文本文件完全一致,唯一的区别是在模式参数上加一个b,如下所示:
f = open("ss.jpg", "rb")
data = f.read()
for i in range (len(data)):
print(hex(data[i]), end=" ")
f.close()
操作文件要要诀
二进制文件读写方式,对于任何文件文件都可以处理,但是以文本模式读写二进制文件(比如图片、视频)就有可能会出现问题。因此,操作之前,要明确自己操作的到底是什么文件。
类
Python3中类的声明很简单,唯一要注意的是它也可以像其他编程语言一样,给类指定一个构造器完成初始化。
class Person():
def __init(name):
self.name = name
def show_name(self):
print(self.name)
由上述代码可以看出:
- 许多编程语言中的this,在Python中没有,是self
- 类的成员函数要手动添加self为第一个参数,不可省略
- 初始化函数(又叫构造器)init是可选的,不是必须的
还有下面的写法:
class Person(object):
def __init(name):
self.name = name
def show_name(self):
print(self.name)
这表示Person类继承自object类,当然,你可以让它继承自你自己定义的某个类。
注意:
- 没有抽象类
- 也没有抽象方法
可以有类方法(不必实例化类就能使用的方法):
class Game(object):
# 游戏最高分,类属性
top_score = 0
@staticmethod
def show_help():
print("帮助信息:让僵尸走进房间")
@classmethod
def show_top_score(cls):
print("游戏最高分是 %d" % cls.top_score)
def __init__(self, player_name):
self.player_name = player_name
def start_game(self):
print("[%s] 开始游戏..." % self.player_name)
# 使用类名.修改历史最高分
Game.top_score = 999
# 1. 查看游戏帮助
Game.show_help()
# 2. 查看游戏最高分
Game.show_top_score()
# 3. 创建游戏对象,开始游戏
game = Game("小明")
game.start_game()
# 4. 游戏结束,查看游戏最高分
Game.show_top_score()
异常
断言机制
Python3中的断言机制如下图所示:
assert是断言,用于判断一个表达式,如果为False,就会触发异常。
异常机制
完整的异常结构如下所示:
由上图可知:
- try是指正常要执行的代码
- except是出现异常才执行的代码,没异常不执行
- else 是没有异常才执行的代码,有异常不执行(和except相反)
- finally是无论有没有异常都会执行,通常用于关闭文件、释放锁等操作
熟悉Java的同学肯定想到了,Python3中的异常机制比Java要更加灵活和强大。
手动抛出异常
当然了,我们可以手动抛出异常:
x = 10
if x > 5:
raise Exception('x 不能大于 5。')
单元测试
开发中,一些重要功能是离不开单元测试的,Python3自带的单元测试功能非常强大,看下面的代码:
import unittest
class MyTestCase(unittest.TestCase):
def test_number(self):
a = 1
self.assertEqual(a, 1) # add assertion here
def test_string(self):
a = 'abc'
self.assertEqual(a, 'abc', '变量的值不是abc') # add assertion here
def setUp(self):
print('starting setup...')
def tearDown(self):
print('end teardown...')
if __name__ == '__main__':
unittest.main()
就完全搞定了。
注意:一个测试类中有多个测试方法时,测试方法的执行顺序是根据方法名称的ASCII编码排序的,所以,如果你对你的测试代码的执行顺序有要求,就要类似于这样命名:test_1_function_description、test_2_function_description
。当然了,还可以使用unittest.TestSuite()
提供的addTest
方法手动指定。