因为后期可能会进行机器学习方面相关学习,因此特意在暑假学习了Python基础相关内容,在学习过程中我将自己认为较为重要的一些内容做了简单的笔记,学习过程主要参考廖雪峰老师Python学习官方网站以及B站黑马程序员Python入门,后期在机器学习中如若需要用到各类第三方包时也会将学习笔记记录在此处。
Python语法采用缩进方式,以#开头的语句是注释,当语句以:为结尾时,缩进的语句视为代码块
a=-1;
if a>=0:
print(a);
else: #冒号下相同缩进部分视为在同一个代码块
print(-a);
print("hello");
print("你好");
print('Hello') #单引号引起来是字符串
print('100+200=',100+200) #逗号隔开输出不同内容,同时会自动填上一个空格
#若不想要空格可以在print最后添加参数sep='',默认情况sep=' '
print('100+200=',100+200,sep='')
num=15
print(f"num是{num}") #也可以使用这种方式进行格式化输出
name = input(); #input()是输入函数,输入结果放入name变量,python不需要手动定义类型
name = input('请输入:'); #在提示语句后输入
计算机能处理的远不止数值,还可以处理文本、图形、音频、视频、网页等各种各样的数据,不同的数据,需要定义不同的数据类型。
Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样,例如:1,100,-8080,00x前缀和0-9,a-f表示,例如:0xff00,0xa5b4c3d210000000000,很难数清楚0的个数。Python允许在数字中间以_分隔,因此,写成10_000_000_000和10000000000是完全一样的。十六进制数也可以写成0xa1b2_c3d41.23,3.14,-9.01,等等。但是对于很大或很小的浮点数,就必须用科学计数法表示,把10用e替代,
1.23
∗
1
0
9
1.23*10^9
1.23∗109就是1.23e9,或者12.3e8,0.000012可以写成1.2e-5字符串是以单引号
'或双引号"括起来的任意文本,比如'abc',"xyz"
如果字符串内部既包含单引号'又包含双引号"怎么办?可以用转义字符\来标识,使特殊字符失去特殊含义
'I\'m \"OK\"!'
转义字符\除了使特殊字符失去特殊含义,也能让部分字符得到特殊含义,比如\n表示换行,\t表示制表符,字符\本身也要转义,所以\\表示的字符就是\
如果字符串里面有很多字符都需要转义,就需要加很多\,为了简化,Python还允许用r''表示''内部的字符串默认不转义
print(r'\t') #输出\t
如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用'''...'''的格式表示多行内容
print('''line1
line2
line3''') //输出line1 line2 line3三行
布尔值和布尔代数的表示完全一致,一个布尔值只有
True、False两种值,要么是True,要么是False,在Python中,可以直接用True、False表示布尔值(请注意大小写),也可以通过布尔运算计算出来
布尔值可以用and【与】、or【或】和not【非】计算
print(True and False) #输出False
print(not False) #输出True
print(17 > 18) #输出False
空值是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值
Python是动态语言,其变量本身类型不固定。与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。Java就属于静态语言
常量就是不能变的变量,比如常用的数学常数
π就是一个常量。在Python中,通常用全部大写的变量名表示常量
PI = 3.14159265359
但事实上PI仍然是一个变量,Python根本没有任何机制保证PI不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法
/除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数//,称为地板除,结果只保留整数部分print(9 / 3) #输出为3.0
print(10 // 3) #输出为3
a = 'ABC'
b = a
a = 'XYZ'
print(b) #最终输出ABC
a赋给b时,实际上是令b指向了a此时指向的常量数据ABCb本质指向的是数据,因此随后a指向了新数据后并不会影响b的指向b最终指向的依然是ABC计算机只能处理数字,因此实际上我们看到的字符串是经过编码得到的
又因为计算机最初是美国人发明的,因此,最早只有127个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,如A的编码是65,a的编码是97
同时由于世上有多种语言,因此自然会产生多种编码,由于标准各不相同,不可避免地会出现冲突。结果就是,在多语言混合的文本中,显示出来会有乱码
Unicode字符集应运而生。其把所有语言都统一到一套编码里,这样就不会再有乱码问题。Unicode标准也在不断发展,但最常用的是UCS-16编码,用两个字节表示一个字符【如果要用到非常偏僻的字符,就需要4个字节】。现代操作系统和大多数编程语言都直接支持Unicode
然而如果统一成Unicode编码,乱码问题从此消失了。但是,当你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。所以,本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码
UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节【形同ASKII码】,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间
#ord()函数获取字符的ASKII码
print(ord('A')) #输出65
#chr()函数把编码转换为对应的字符
print(chr(97)) #输出a
#如果知道字符的整数编码,也可以直接用十六进制写
print('\u4e2d\u6587') #Unicode编码对应'中文',因此输出'中文'两个字
Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes
#Python对bytes类型的数据用带b前缀的单引号或双引号表示
x=b'ABC' #此时x存放的是bytes类型
#通过encode()方法可以编码为指定的bytes
print('ABC'.encode('ascii')) #将字符串转换为ASKII码对应的bytes存储
print('中文'.encode('UTF-8')) #将字符串转换为UTF-8码对应的bytes存储
#通过decode()方法可以将bytes按照指定方式解码
print(b'ABC'.decode('ascii')) #以ASKII码的方式解释字节流,输出ABC
#如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节,如下
#只输出'中',因为另一个字节码在utf-8编码情况下不对应任何字符
print(b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore'))
#len()函数计算的是str的字符数【中文同适用】,如果换成bytes,len()函数就计算字节数
print(len('中文')) #输出2,因为有两个字符
print(len('你好'.encode('utf-8'))) #输出3,UTF-8编码一个中文字符通常占3个字节,所以输出6
#将字符串'12'转换为整数
num=int('12')
在操作字符串时,我们经常遇到str和bytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对str和bytes进行转换。
由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就务必需要指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上以下两行
#第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释
#!/usr/bin/env python3
#第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码
# -*- coding: utf-8 -*-
%运算符用来格式化字符串。在字符串内部,%s表示用字符串替换,%d表示用整数替换,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个%?,括号可以省略
如果不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串
print('Hello, %s' % 'world') #输出'Hello, world'
#输出'Hi, Michael, you have $1000000.'
print('Hi, %s, you have $%d.' % ('Michael', 1000000))
字符串里面的%是一个普通字符时需要转义,用%%来表示一个%
Python内置的一种数据类型是list,可以随时添加和删除其中的元素,将其理解为链表即可,不过方法名称与Java相比有所区别,同时Python中的list更加强大,内部可以存放不同类型的元素
classmates=['tom','jack','mike'] #此时classmates是list类型
print(classmates[0]) #用下标进行访问,输出tom
print(classmates[-1]) #负的下标代表倒数第几个,此处输出mike
p = ['asp', 'php']
s = ['python', 'java', p, 'scheme'] #此时s[2][1]等价于p[1]
classmates.append('martin') #append方法将元素追加至末尾
classmates.insert(0,'linda') #insert方法将元素插入指定索引位置
classmates.pop() #pop()方法删除末尾元素,也可删除指定索引元素
len(classmates) #len()方法获取链表长度
range(1, 11) #生成包含1~10的list
tuple一旦初始化就不能修改【所谓“不变”是说,tuple的每个元素指向永远不变】,这是其与list的唯一区别
#当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来
t = (1, 2) #t的类型为tuple
t = () #定义一个空的tuple
#只有1个元素的tuple定义时必须加一个逗号,否则会与数学中的小括号歧义
t = (1,)
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85} #定义key-value
d['Michael'] #取出键值对应的值
d['Adam'] = 67 #放入键值对
#当键值不存在于字典中时会报错,因此取值前最好进行判断
'Thomas' in d #若此时不存在这样的键值则返回False
d.get('Thomas') #若当前键值对不存在则返回None【此时Python交互环境不显示结果】
d.get('Thomas', -1) #通过get方法也许,当键值对不存在时返回-1
d.pop('Bob') #删除
set的原理与dict一致,不过其只存储key值,因此可以理解为集合
s=set() #得到一个空集合
s=set([1, 2, 3]) #set的赋值初始化需要list作为输入集合
s.add(4) #add方法往集合添加元素
s.remove(4) #remove方法移除集合中的元素
s1 & s2 #得到交集
s1 | s2 #得到并集
Python的条件判断是不需要小括号括起来的,同时由于其依靠相同缩进代表同一代码块,因此也无需中括号
age = 3
#只要age是非零数值、非空字符串、非空list等,就判断为True,否则为False
if age >= 18: #不要少写冒号
print('adult')
elif age >= 6: #else if在此处缩写为elif
print('teenager')
else:
print('kid')
nums=[1,2,3] #定义一个数组
sum=0
for x in nums: #for的用法,不要忘了冒号:,while循环同样需要冒号
sum+=x
print(sum)
#range(5)生成的序列是从0开始小于5的整数
l=list(range(5)) # 将序列存放进list中
#如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身
for i, value in enumerate(['A', 'B', 'C']):
print(i, value) # i即此元素在list中的下标
#也可以同时引用多个变量,此时就像是有多个索引进行遍历
for x, y in [(1, 1), (2, 4), (3, 9)]:
print(x, y)
在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回,如果没有return语句,函数执行完毕后也会返回结果,只是结果为None。return None可以简写为return
num=0
def my_abs(x):
global num #num是全局变量,想在函数中使用需要用global关键字
if x >= 0:
return x
else:
return num
如果想定义一个什么事也不做的空函数,可以用pass语句:
def nop():
pass
pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。pass还可以用在其他语句里,如下:
if age >= 18:
pass #此处若缺少了pass,代码运行就会有语法错误
Python可以允许返回多个值,其原理是返回一个tuple,多个变量可以同时接收一个tuple,按位置赋给对应的值
def move(x, y):
nx = x
ny = y
return nx, ny #返回多个值
x, y = move(100, 100) #x,y依次接受move函数返回的nx,ny
当函数有多个参数时可以指定默认参数,不过需要保证必选参数在前,默认参数在后
def power(x, n=2): #使用时当n处不填参数也不会报错,n默认为2
s = 1
while n > 0:
n = n - 1
s = s * x
return s
有多个默认参数时,调用的时候,既可以按顺序提供默认参数,也可以不按顺序提供部分默认参数。
当不按顺序提供部分默认参数时,需要把参数名写上。比如调用enroll('Adam', 'M', city='Tianjin'),意思是,city参数用传进去的值,其他默认参数继续使用默认值
不过使用时需要注意一个陷阱:默认参数必须指向不变对象!
#Python函数在定义的时候,默认参数L的值就被计算出来了
#由于L的默认参数指向的是地址,因此下次使用时会保留上次添加的数据
def add_end(L=[]):
L.append('END')
return L
#修改如下
#由于None是一个不变对象,因此保证每次L指向的内容不发生改变
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
可变参数就是传入的参数个数是可变的,可以是
1个、2个到任意个,还可以是0个
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数
def calc(*numbers):
sum=0
for n in numbers:
sum+=n
return sum
result=calc(2,3,4)
#也可以在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去
nums=[1,2,3]
calc(*nums)
关键字参数允许传入
0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
# kw接收键值对
person('Adam', 45, gender='M', job='Engineer')
extra = {'city': 'Beijing', 'job': 'Engineer'}
#也可以直接传入定义好的dict
person('Jack', 24, **extra)
kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra
当使用了关键字参数时,正常情况下会将所有输入的键值对都进行接收,若我们只想接收指定关键值则需要用到命名关键字参数
#命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数
def person(name, age, *, city, job): #只接受city和job的关键字
print(name, age, city,job)
#调用函数,其中key=value必须写全,不能单写一个value
#命名关键字参数可以有缺省值【即默认参数】,从而简化调用
person('Jack',24, city='Beijing', job='Engineer')
#如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
def person(name, age, *args, city, job): # *args说明是可变参数
print(name, age, args, city, job)
Python中参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数
#a和b对应必选参数,c为默认参数,*开始的位置表示命名关键字【只接收d和kw关键值】
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
#除了挨个传参外,也可以通过一个tuple和dict也调用上述函数,不过需要保证tuple内容可以与参数对上
args = (1, 2, 3)
kw = {'d': 88, 'x': '#'}# kw中d的键值对会对应给d的参数,剩下的内容才会归入参数中的kw
f2(*args, **kw)
虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差
a, b = b, a + b
#上述赋值语句等价于↓,即后边的部分先得到,接着按顺序赋值
t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]
and和or返回第一个能让其确定结果为True或者False的值
print(None and 'a') #输出None,因为None视为False,此时已经可以指定整体输出为False
print('a' and None) #输出None,因为a不为None视为True,此时结果看后半部分,即None
print('a' and 'b') #输出b,理由同上
#空字符串也被视为False
print('a' or 'b') #输出a,因为a不为None视为False,此时整体已可以得知为False
即
Python已经封装好,我们可以直接拿来用的函数
abs(-100) #得到绝对值
max(2, 3, 1, -5) #得到最大值,参数可多个
math.sqrt() #开平方
int('123') #转为整型
float('12.34') #转为浮点型,Python没有double
str(1.23) #转为字符串
bool(1) #转为布尔型,非空即为True
#数据类型检查可以用内置函数isinstance()实现
isinstance(x, (int, float)) #若x属于int型或float型时返回True
Python提供一系列取指定索引范围的操作,形象地称为切片Slice,list,tuple,字符串都可进行切片操作
a[x:y:z]:x表示切片起点,y表示切片终点,z表示步长。如果不指定x和y,则默认开始和最后,如果不指定z,则默认步长为1。当**z为-1时代表倒序且步长为1**,此时x与y对应的是倒序后的下标
L=['tom','jack','jery']
print(L[0:2]) #L[0:2]表示,从索引0开始取,直到索引1为止
#如果第一个索引是0,还可以省略,写成L[:2]
# L[-2:-1]取倒数第二个,但不包括倒数第一个
# L[-2:]从倒数第二个开始往后取至末尾
n='1234'
print(n[::-1]) #输出[4,3,2,1]
print(a[2::-1]) #倒序后从下标2开始取,所以是[3, 2, 1]
print(a[:2:-1]) #倒序后从下标0取到下标2,所以是[5, 4]
如果给定一个
list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代Iteration
在Python中,迭代是通过for ... in来完成的,默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()
创建列表的时可以直接在列表内写表达式,符合表达式的部分成为列表元素
#如下,生成[4, 16, 36, 64, 100]
L = []
for x in range(1, 11):
if x % 2 == 0:
L.append(x*x)
print(L)
#也可以在表达式中直接这样写↓
#写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来
L=[x * x for x in range(1, 11) if x % 2 == 0] #此时if后不能用else
#当if语句放在for前时必须使用else,因为此时if..else是表达式而不是过滤条件
L=[x if x % 2 == 0 else -x for x in range(1, 11)]
#此时L=[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]
Python一边循环一边计算的机制,称为生成器:generator。当我们仅仅需要使用列表生成式中的少量元素时,使用generator可以节省根据需要创建我们需要的元素,从而节约空间
把一个列表生成式的
[]改成(),就创建了一个generator
l = [x * x for x in range(10)] #这里通过列表生成式得到list
g = (x * x for x in range(10)) #这里得到generator
# g内元素如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值
#直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误
next(g) #不过此方法一般不用,因为通过循环同样可以对g进行遍历且不用担心异常
如果一个函数定义中包含
yield关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator
generator的函数会在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行
调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator
def odd():
print('hh')
yield 1 # yield是generator函数的标志
print('jj')
yield(2)
print('kk')
yield(3)
#调用该generator函数时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值
o = odd() #得到一个generator对象
next(o) #输出hh,返回1
next(o) #输出jj,返回2
next(o) #输出kk,返回3
next(o) #此时函数已经执行完毕,报错StopIteration
实际上,遍历generator很少使用next方法,通常能用for解决就用for
# 斐波那契数列
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
for n in fib(6):
print(n) #输出1,1,2,3,5,8
#但是此方法只能拿到yield返回值,无法拿到return返回值
#若需要拿到return返回值则需要进行如下操作
g = fib(6)
while True:
try:
x = next(g)
print('g:', x)
except StopIteration as e: #发生异常后才会执行此代码块
print('Generator return value:', e.value) #e.value即return返回值
break
我们已经知道,可以直接作用于for循环的数据类型有以下几种:
list、tuple、dict、set、str等;generator,包括生成器和带yield的generator function。这些可以直接作用于for循环的对象统称为可迭代对象:Iterable,可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator,可以使用isinstance()判断一个对象是否是Iterable对象
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator
#通过iter()函数可以把Iterable变成Iterator
isinstance(iter([]), Iterator) #将list转变为Iterator对象后进行判断,返回True
为什么list、dict、str等数据类型不是Iterator?
因为
Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的
for循环的对象都是Iterable类型next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列函数式编程是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点是:允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
变量可以指向函数,函数的参数能接收变量,一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数
函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”
a = abs # 变量a指向abs函数
a(-1) # 所以也可以通过a调用abs函数
abs=1; #将1赋给abs,此时abs不再指向绝对值函数
print(abs) #输出1
#一个简单例子
def add(x,y,f): # f作为参数接受的是函数
return f(x) + f(y)
print(add(5,6,abs)) #输出11
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回
def f(x):
return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) # list中的元素依次执行f(x)
r=list(r) #用list装Iterator
print(r) #输出[1, 4, 9, 16, 25, 36, 49, 64, 81]
#将list中的元素都变成字符串
l=list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算
reduce(f, [x1, x2, x3, x4]) 等价于 f(f(f(x1, x2), x3), x4)
#如下简单例子,对序列求和
def add(x, y):
return x + y
reduce(add, [1, 3, 5, 7, 9]) #先f(1,3),再将结果放入问号f(?,5)...
def fn(x, y):
return x * 10 + y
def char2num(s):
digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
return digits[s]
reduce(fn, map(char2num, '13579')) #reduce与map结合使用,实现把字符串转整型
与map()类似,filter()也接收一个函数和一个序列,不过和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素
#一个简单例子
def is_odd(n):
return n % 2 == 1 #结果为True的元素保留
#保留下奇数
l=list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])) # 结果: [1, 5, 9, 15]
def not_empty(s):
#如果s不是None或空串说明为True,此时返回去除多余空格的字符串
#如果s是None或空串说明为False,此时配合filter对这部分数据不保留
return s and s.strip()
# filter返回的是一个Iterator
l=list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
# 结果: ['A', 'B', 'C']
sorted()函数也是一个高阶函数,它可以接收一个key函数来实现自定义的排序
sorted([36, 5, -12, 9, -21]) #默认从小到大排序
sorted([36, 5, -12, 9, -21],reverse=True) #加上参数就是从大到小排序
#key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序,不过只能说是内置函数
sorted([36, 5, -12, 9, -21], key=abs) #根据绝对值大小从小到大排序
#如果需要进行自定义函数排序则需要使用容器内部的sort方法进行操作
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum #返回值是sum()这个函数
#多次调用lazy_sum时返回的函数是独立的
f = lazy_sum(1, 3, 5, 7, 9) #此时得到的是sum()这个函数,并未进行求和
num=f() # 变量f指向sum,调用f()函数时才开始执行
闭包
Closure是一种程序结构,指内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
#此时f()并没有真正执行,但是当count()执行完毕并返回fs后,i已经变成了3
f1, f2, f3 = count()
#调用f1()等函数时f()才真正执行,此时返回 3 * 3 = 9
print(f1(),f2(),f3()) #输出9,9,9
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量
如果一定要引用循环变量怎么办?
可以再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变,缺点是代码较长,可利用
lambda函数缩短代码【后面学】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() print(f1(),f2(),f3()) #此时输出1,4,9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
使用闭包,就是内层函数引用了外层函数的局部变量,如果内层函数直接对外层变量赋值,由于Python解释器会把x当作函数fn()的局部变量,它会报错。原因是x作为局部变量并没有初始化,直接计算x+1是不行的,但我们其实是想引用inc()函数内部的x,所以需要在fn()函数内部加一个nonlocal x的声明
def inc():
x = 0
def fn():
# nonlocal x #加此声明
x = x + 1 #此时解释器会把x当作是外层变量
return x
return fn
f = inc()
print(f()) # 1
print(f()) # 2
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便
关键字lambda表示匿名函数,冒号:前面的x表示函数参数。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
f = lambda x: x * x #传入参数为x,返回值为x*x
print(f(5)) #输出 25
在代码运行期间动态增加功能的方式,称之为“装饰器”
Decorator,本质上,decorator就是一个返回函数的高阶函数
def log(func):
def wrapper(*args, **kw): #可以接受任何参数
print('call %s():' % func.__name__) #每个函数都有__name__属性,其封装了函数名
return func(*args, **kw)
return wrapper
#因为log是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处
@log
def now():
print('2015-3-25')
#此时调用now()的输出如下:
call now():
2015-3-25
#把@log放到now()函数的定义处,相当于执行了语句↓
now = log(now) #相当于wrapper函数赋给now(),所以再次执行now()实际是wrapper,而原本的now早已通过参数func被wrapper获得,因此后续执行仍能定位到原now
#不过由于当前now已经被赋为wrapper了,因此通过now._name_属性我们只能得到wrapper
#因此我们工作仍未完成,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行会出错
#不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下
import functools #导入functools模块
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
#如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute')
def now():
print('2015-3-25')
#此时调用now()的输出如下:
execute now():
2015-3-25
#把@log放到now()函数的定义处,相当于执行了语句↓
now = log('execute')(now) #前半部分返回decorator函数,接着传参now,后续执行如上
通过设定参数的默认值,可以降低函数调用的难度,偏函数也可以做到这一点
# int()默认将字符串当成十进制进行解析,我们通过设置参数默认值使得int2()将字符串当成二进制进行解析
def int2(x, base=2):
return int(x, base)
#functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2
import functools
int2 = functools.partial(int, base=2)
print(int2('1000000')) #输出64
#新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值
print(int2('1000000',base=10)) #输出1000000
#创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数
int2('10010') #上边创建的int2(),等价于↓
kw = { 'base': 2 }
int('10010', **kw)
#当传入*arg的参数时,实际上会被安排到最左边
max2 = functools.partial(max, 10)
max2(5, 6, 7)
#等价于↓
args = (10, 5, 6, 7)
max(*args)
为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在
Python中,一个.py文件就称之为一个模块Module同时,为了避免模块名冲突,
Python又引入了按目录来组织模块的方法,称为包Package。注意:每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块
#!/usr/bin/env python3 #此注释允许此.py文件直接在Unix/Linux/Mac上运行
# -*- coding: utf-8 -*- #此注释表示.py文件本身使用标准UTF-8编码
' a test module ' #表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释
__author__ = 'Michael Liao' # 使用__author__变量把作者写进去
#上面部分是Python模块的标准文件模板
import sys #导入sys模块
def test():
#sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')
#当我们直接运行该模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该模块时,if判断将失败
if __name__=='__main__':
test()
正常的函数和变量名是公开的public,在Python中,是通过_前缀来实现私有private
之所以我们说,
private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量
类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如的__author__,__name__就是特殊变量,我们自己的变量一般不要用这种变量名
外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类Class的概念
#定义一个类,object代表Student是从object继承下来的
class Student(object):
pass
bart = Student() #创建Student实例
#可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性
bart.name = 'Bart Simpson'
#由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去
#通过定义一个特殊的__init__方法【构造函数】,在创建实例的时候,就把name,score等属性绑上去
#__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
#有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去
bart = Student('Bart Simpson', 59)
#如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
class Student(object):
def __init__(self, name, score):
self.__name = name #两个下划线,说明name是私有变量
self.__score = score
#如果外部代码要获取或修改score怎么办?可以给Student类增加get和set这样的方法
def get_score(self):
return self.__score
def set_score(self, score):
self.__score = score
需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名
当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类Subclass,而被继承的class称为基类、父类或超类Base class、Super class
class Animal(object):
def run(self):
print('Animal is running...')
#Dog就是Animal的子类
class Dog(Animal):
pass
#子类可以获得父类的全部功能
#当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态
#传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法【向上转型】
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子
判断对象类型,可以使用type()函数,它返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
#但是对于class的继承关系来说,使用type()就很不方便,所以优先考虑isinstance()
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Animal) # 子类属于父类,因此返回True
True
>>> isinstance([1, 2, 3], (list, tuple)) #判断一个变量是否是某些类型中的一种
True
>>> isinstance(b'a', bytes) # 基本类型也可以用isinstance()判断
True
#如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
#类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
#我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法
仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态
#如果类本身需要绑定一个属性,可以直接在class中定义属性,这种属性是类属性,归该类所有
>>> 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
def set_age(self, age): # 定义一个函数作为实例方法
self.age = age
# 除了可以给实例对象绑定属性外,也可以给实例对象绑定方法
class Student(object):
pass
s = Student()
s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
s.set_age(25) # 调用实例方法
print(s.age) # 测试结果,输出 25
def set_score(self, score):
self.score = score
# 为了给所有实例都绑定方法,可以给 class 绑定方法
# 注意:给实例绑定方法和给类绑定方法写法是不一样的
Student.set_score = set_score
__slots__
Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
s = Student() # 创建新的实例
s.score = 99 # 由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误
使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
如果在子类中也定义__slots__,这样子类实例允许定义的属性就是自身的__slots__加上父类的__slots__
利用
@property可以实现通过属性调用【方法在此被视为属性】智能绑定get方法和set方法
class Student(object):
@property # @proerty绑定get方法,即把get方法变成属性
def score(self):
return self._score
@score.setter # @score.setter使得set方法变为属性
def score(self, value):
if(value >= 60):
self._score = value
else:
print('没有及格')
s=Student()
s.score=61 # score是方法名,但是被注释后成为属性名,赋值操作实则是调用set
print(s.score) #非赋值的调用实则调用get,因此输出 61
特别注意:属性的方法名不要和实例变量重名,否则会造成无线递归!
Python允许子类拥有多个父亲,通过多重继承,一个子类就可以同时获得多个父类的所有功能
class Animal(object):
def run(self):
print('I am Animal')
class Runnable(object):
def run(self):
print('Running...')
class Dog(Runnable,Animal): #同时又两个父类
d=Dog()
d.run() #遇到同名方法时优先调用写在最左边的父类,输出I am Animal
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系
# 每个MixIn都有自己的功能
class RunnableMixIn(object):
def run(self):
print('Running...')
# 我们根据需要将MixIn进行继承,从而获得想要的功能
class Dog(Animal,RunnableMixIn):
pass
我们可以根据需要重写一些类方法从而使其能更好为我们服务
__str__
__str__类似与Java中的toString方法,通过重写此方法,可以使得直接输出类时按照我们想要的格式进行输出
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self): #重写__str__
return 'Student object (name: %s)' % self.name
print(Student('Michael')) #输出 Student object (name: Michael)
__iter__如果一个类想被用于
for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环
class Fib(object): #以斐波那契数列为例
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self): #使用for...in的时候会调用
return self # 实例本身就是迭代对象,故返回自己
def __next__(self): # for循环每进行一次就会调用一次
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值
for n in Fib():
print(n) #此时会打印Fib类中每次__next__的执行结果
__getitem__如果类想像
list那样按照下标取出元素,需要实现__getitem__()方法
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
f = Fib()
print(f[0]) #此时调用__getitem__方法,n为0,输出 1
#list还可以使用切片的方式访问,因此若我们将n定死为整型则无法使用切片访问
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start #开始索引
stop = n.stop #结束索引,但不包括此索引
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start: #过滤掉不需要的部分
L.append(a)
a, b = b, a + b
return L
f = Fib()
print(f[0:5]) #输出[1, 1, 2, 3, 5]
#像上述这样设计仍然有缺陷,比如步数step还未处理等
与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素。总之,通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
__getattr__正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。要避免这个错误,除了可以加上这个属性外,
Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性
class Student(object):
#只有在没有找到属性的情况下才调用__getattr__,已有的属性不会在__getattr__中查找
def __getattr__(self, attr):
if attr=='score': #如果访问的属性是score就返回 99
return 99
__call__一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用
instance.method()来调用,同时任何类只需要定义一个__call__()方法,就可以直接对实例进行调用
class Student(object):
def __init__(self, name): #构造函数
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
s = Student('Michael')
s() # self参数不要传入,输出 My name is Michael.
__call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象。如果把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。
那么,怎么判断一个变量是对象还是函数呢?
其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有__call__()的类实例
>>> callable(max) # max函数可以被调用,所以为True
True
>>> callable([1, 2, 3]) # list不可以被调用,所以为False
False
当我们需要定义常量时,一个办法是用大写变量通过整数来定义,好处是简单,缺点是类型是
int,并且仍然是变量。更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
for name,member in Month.__members__.items():
# value 属性则是自动赋给成员的int常量,默认从1开始计数
print(name,'=>',member,'=>',member.value)
#输出格式类似于:Jan => Month.Jan => 1
# @unique 装饰器可以帮助我们检查保证没有重复值
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
#访问这些枚举类型可以有若干种方法
day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(Weekday(1))
Weekday.Mon
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的
class Hello(object):
def hello(self, name='world'):
print('Hello, %s.' % name)
h = Hello()
h.hello()
当Python解释器载入hello模块时,才会依次执行该模块的所有语句,执行结果就是动态创建出一个Hello的class对象
type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello。我们说
class的定义是运行时动态创建的,而创建class的方法就是使用type()函数
type()函数既可以返回一个对象的类型【比如整数是int,字符串是str,class是type】,又可以创建出新的类型【不通过class定义而直接通过class定义的本质type()函数创建】
def fn(self, name='world'): # 先定义函数
print('Hello, %s.' % name)
#要创建一个class对象,type()函数依次传入3个参数:
# 1.class的名称;
# 2.继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
# 3.class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上
Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class
正常情况下,我们都用class Xxx...来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。
除了使用type()动态创建类以外,要控制类的创建行为【得到类的实例】,需要使用metaclass
metaclass直译为元类,简单的解释就是:当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。可以把类看成是metaclass创建出来的“实例”
正常情况下不会碰到需要使用metaclass的情况,等需要的时候再回来看这部分知识点!
在Python使用open函数,可以打开一个已经存在的文件,或者创建一个新文件
#name:是要打开的目标文件名的字符串(可以包含文件所在的具体路径)
#mode:设置打开文件的模式(访问模式):只读(r)、从头写入(w)、追加(a)等。
#encoding:编码格式(推荐使用UTF-8)
f=open(name, mode, encoding) #返回一个_io.TextIOWrapper对象
f.read() #读取文件所有内容,填写参数时则读取参数长度字节内容,返回str类型
f.readlines() #读取所有内容,每一行为list中的一个元素
f.readline() #调用一次读取一行内容
for line in f: #也可以通过for循环进行读取
print(f"每一行数据是:{line}")
f.close() #关闭文件流,内含flush功能
with open(xxx) as f: #若使用此语句打开文件,则文件流最后会自动关闭
xxxxx
f.write(xxx) #将内容写入文件
f.flush() #刷新后写入的内容才会从缓存中发送到硬盘上
当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕
try:
print('try...')
r = 10 / 0
print('result:', r)
# python所有的错误类型都继承自BaseException,捕获错误时也会把子类一网打尽
# except: 是捕获所有异常
except ZeroDivisionError as e: #可以有多个except捕获不同的错误
# except (xxx,xxx) as e 是捕获多个异常
print('except:', e)
finally: # finally是一定会执行的部分
print('finally...')
print('END')
各异常的层级关系👇
使用try...except捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()调用bar(),bar()调用foo(),结果foo()出现异常了,此时foo()的异常会抛给bar(),bar()再抛给main(),只要main()捕获到了,就可以处理。换句话说,异常是可以向上传递的。
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...')
Python内置的logging模块可以非常容易地记录错误信息,通过配置,logging还可以把错误记录到日志文件里,方便事后排查。
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息
有时捕获错误目的只是记录一下,便于后续追踪。但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。好比一个员工处理不了一个问题时,就把问题抛给他的老板
def foo(s):
n = int(s)
if n==0:
# raise的作用是显式的抛出异常
raise ValueError('invalid value: %s' % s)
return 10 / n
def bar():
try:
foo('0')
except ValueError as e:
print('ValueError!')
raise #主动抛出此异常,说明ValueError这个异常还未被解决,上层依旧可以进行捕获
bar()
Python模块Module,是一个Python 文件,以.py 结尾。模块能定义函数,类和变量,模块里也能包含
可执行的代码。
python中有很多各种不同的模块,每一个模块都可以帮助我们快速的实现一些功能,比如实现和时间相关的功能可以使用time模块等。我们可以认为一个模块就是一个工具包,每一个工具包中都有各种不同的工具供我们使用进而实现各种不同的功能。
#模块在使用前需要先导入,导入语法如下👇
[from模块名]import[模块│类│变量│函数│*][as别名]
#常见的有以下几种形式
import 模块名 #整个模块引入
from 模块名 import 类、变量、方法等 #引入具体的方法
from 模块名 import *
import 模块名 as 别名
from 模块名 import 功能名 as 别名
import time #导入time模块
time.sleep(5) #通过模块调用内部方法
from time import sleep #导入time.sleep()方法
sleep(5) #直接使用方法名就可以完成调用
简单来说就是将自己写的模块导入当前正在处理的
.py文件,类似于Java在一个类中使用另一个文件的类时也需要先导入此类
#当遇到不同模块的重名方法时,后导入的方法会覆盖先导入的方法
from module1 import test
from module2 import test
test() #此时调用的是module2中的方法
在导入模块时,其实Python是将整个模块执行了一遍,而有时模块中有部分测试数据我们不希望他在被调用时执行则需要做以下处理👇
def test(a, b):
return a + b
#只有发起运行的.py文件中的内置变量__name__才是__main__
#因此当此模块被导入时,if下方的语句并不会执行
if __name__ == '__main__':
test(1 + 2)
__all__变量可以控制import *时哪些功能可以被导入
#此时若有程序通过import *导入此模块的方法时只能导到test1
__all__=['test1']
def test1(a, b):
return a - b
def test2(a, b):
return a + b
从物理上看,包就是一个文件夹,在该文件夹下包含了一个
_init_.py文件,该文件夹可用于包含多个模块文件。从逻辑上看,包的本质依然是模块【区别在于是否有_init_.py文件】
在PyCharm中直接可以创建Python Package,创建后会自动生成_init_.py文件,接着我们将自己定义的模块放入此包下就行
#导入包的方法
from my_package import module1 #导入my_package下的module1模块
from my_package import module1,module2 #导入my_package下的module1,module2模块
from my_package.module1 import test #导入my_package下的module模块的test()方法
#还可以通过在__init__.py中设置__all__变量来控制import *的行为
__all__=['module2'] #例如在__init__.py写入此语句
from my_package import * #此时只能导入module2模块
在Python程序的生态中,有许多非常多的第三方包【非Python官方】,可以极大的帮助我们提高开发效率,如:
numpy包pandas包pyspark,apache-flink包matplotlib,pyechartstensorflow但是由于是第三方,所以Python没有内置,所以我们需要安装它们才可以导入使用
# 第三方包的安装非常简单,我们只需要使用Python内置的pip程序即可
pip install 包名称 #此语句在命令提示符中运行
# 由于pip命令默认是从国外服务器下载包,因此速度较慢,我们可以为其设置镜像下载地址
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名称
当然,在PyCharm中也支持安装第三方包


或者在PyCharm中直接使用import语句导入包再按Alt+Enter也会进行自动导包
JSON是一种轻量级的数据交互格式,可以按照JSON指定的格式去组织和封装数据,本质上是一个带有特定格式的字符串,是不同编程语言通信的中转站
//Json格式如下👇
{"name":"tom","age":18} //单个Json的格式与Python中的字典dict类似
[{"name":"tom","age":18},{"name":"tom","age":18}] //多个Json实则是列表+字典
import json #使用Json需要导入内置模块
# date是一个列表字典
data = [{"name": "tom", "age": 18}, {"name": "mike", "age": 20}]
#通过json.dumps将列表字典转变为json对象,ensure_ascii=False确保中文能够正常显示
json_str = json.dumps(data, ensure_ascii=False)
# s是一个长着Json样子的字符串
s = '[{"name": "tom", "age": 18}, {"name": "mike", "age": 20}]'
# 通过loads方法将字符串转换为list对象,当然只有{}包围时也可以转为dict对象
l = json.loads(s)
Echarts是个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而Python是门富有表达力的语言,很适合用于数据处理。当数据分析遇上数据可视化时pyecharts诞生了。官方文档
#折线图实例
from pyecharts.charts import Line
# 得到一个折线图对象
line = Line()
# 确定x轴的取值
line.add_xaxis(["中国", "美国", "俄罗斯"])
# 确定y轴的取值
line.add_yaxis("GDP", [30, 20, 10])
# 展示此折线图,运行会会生成一个render.html
line.render()
全局配置项【不管任何图都会有的配置】可通过 set_global_opts 方法设置,相关属性对应的部分如下图👇

# 部分配置参数如下
line.set_global_opts(
# 设置标题为"GDP展示",距离左边的距离的居中,距离底部的距离是1%【合起来就是标题底部居中】
title_opts=TitleOpts("GDP展示", pos_left="center", pos_bottom="1%"),
# 图例显示【默认会显示】
legend_opts=LegendOpts(is_show=True),
# 工具箱显示
toolbox_opts=ToolboxOpts(is_show=True),
# 视觉映射配置项显示
visualmap_opts=VisualMapOpts(is_show=True)
)
相关数据文件可在下方百度云链接自取👇
链接:https://pan.baidu.com/s/1ZjhKsdr1T1N8hT3okcqsLA
提取码:8e6u
文件打开后,我们发现这是一个很复杂,很多层结构的Json对象,当我们会面对数据较多,结构复杂的Json对象,此时我们可以利用Json格式化工具对Json的结构进行可视化,点击Json即可进入我们想要的界面
我们将整个文件内容复制进格式化工具后将前缀jsonp_1629344292311_69436(和后缀);删除【因为这部分不是Json的格式】,接着点击"视图"可以看到很明显的层级结构
通过Python的文件读取和Json工具以及dict和list相应的读取规则,我们就根据层级结构取出我们想要的数据。但是需要注意一点,在使用Python进行读取时,删除前缀后缀的任务最好交由程序去完成而不是手动删除
import json
from pyecharts.charts import Line
from pyecharts.options import *
# 获取文件对象
us_f = open("D:\\美国.txt", "r", encoding="UTF-8")
ja_f = open("D:\\日本.txt", "r", encoding="UTF-8")
in_f = open("D:\\印度.txt", "r", encoding="UTF-8")
# 读取文件所有内容
us_data = us_f.read()
ja_data = ja_f.read()
in_data = in_f.read()
# 删除前缀
us_data = us_data.replace("jsonp_1629344292311_69436(", "")
ja_data = ja_data.replace("jsonp_1629350871167_29498(", "")
in_data = in_data.replace("jsonp_1629350745930_63180(", "")
# 删除后缀
us_data = us_data[:-2]
ja_data = ja_data[:-2]
in_data = in_data[:-2]
# 使用Json工具将字符串转换为json对象
us_dict = json.loads(us_data)
ja_dict = json.loads(ja_data)
in_dict = json.loads(in_data)
# 根据层级结构进行相应取值
trend_data1 = us_dict['data'][0]['trend']
trend_data2 = ja_dict['data'][0]['trend']
trend_data3 = in_dict['data'][0]['trend']
# 获取日期数据作为x轴,取2020年,三个国家日期,因此取一份就好
x_data1 = trend_data1['updateDate'][:314]
# 获取对应时间的确诊人数作为y轴,取2020年
y_data1 = trend_data1['list'][0]['data'][:314]
y_data2 = trend_data2['list'][0]['data'][:314]
y_data3 = trend_data3['list'][0]['data'][:314]
line = Line()
# 载入x坐标数据
line.add_xaxis(x_data1)
# label_opts设置使y值不显示在折线中
line.add_yaxis("美国确诊人数:", y_data1, label_opts=LabelOpts(is_show=False))
line.add_yaxis("日本确诊人数:", y_data2, label_opts=LabelOpts(is_show=False))
line.add_yaxis("印度确诊人数:", y_data3, label_opts=LabelOpts(is_show=False))
line.set_global_opts(
title_opts=TitleOpts(title="2020年美日印三国确诊人数对比折线图", pos_left="center", pos_bottom="1%")
)
# 展示折线图
line.render()
us_f.close()
ja_f.close()
in_f.close()
pyecharts也提供了画地图相关工具
import json
from pyecharts.charts import Map
from pyecharts.options import *
# 得到一个地图对象
map = Map()
# 为对应省份绑定数值,当鼠标移上去时会显示
data = [
# 省份和确诊人数用元组的方式进行封装
("北京", 99),
("上海", 98),
("广东", 97)]
# 将数据引入地图,同时china表示中国地图,如果需要构建省份直接传入对应中文字符串即可
map.add("测试地图", data, "china")
map.set_global_opts(
# 地图可见选项
visualmap_opts=VisualMapOpts(
is_show=True, #是否显示
is_piecewise=True, #是否分段
# 手动设置相应数据
pieces=[
{"min": 1, "max": 9, "label": "1-9", "color": "#CCFFFF"},
{"min": 10, "max": 99, "label": "10-99", "color": "#FF6666"},
{"min": 100, "max": 500, "label": "100-500", "color": "#990033"}
]
)
)
map.render()
疫情相关数据如下百度云链接自取👇
链接:https://pan.baidu.com/s/1VACKniLTKtkwMYrwFuPcLA
提取码:jrsy
import json
from pyecharts.charts import Map
from pyecharts.options import *
f = open("D:\\疫情.txt", "r", encoding="UTF-8")
data = f.read()
f.close() # 由于没有写回操作,因此获取到文件数据后即可关闭文件流
# 将字符串json转换为字典
data_dict = json.loads(data)
# 从字典中取出省份的数据,取出操作根据json的层级而定
# list就用数字下标访问,dict就用关键字访问
province_data_list = data_dict["areaTree"][0]["children"]
# 绘图需要载入的数据列表
data_list = []
for province_data in province_data_list:
province_name = province_data["name"]
# 省份名称
province_confirm = province_data["total"]["confirm"]
# 确诊人数
data_list.append((province_name, province_confirm))
map = Map()
map.add("各省份确诊人数", data_list, "china")
# 进行全局配置
map.set_global_opts(
title_opts=TitleOpts(title="全国疫情地图"),
visualmap_opts=VisualMapOpts(
is_show=True,
is_piecewise=True,
pieces=[
{"min": 1, "max": 99, "lable": "1~99人", "color": "#CCFFFF"},
{"min": 100, "max": 999, "lable": "100~9999人", "color": "#FFFF99"},
{"min": 1000, "max": 4999, "lable": "1000~4999人", "color": "#FF9966"},
{"min": 5000, "max": 9999, "lable": "5000~99999人", "color ": "#FF6666"},
{"min": 10000, "max": 99999, "lable": "10000~99999人", "color ": "#CC3333"},
{"min": 100000, "lable": "+100000", "color": "#990033"}
]
)
)
map.render()
pyecharts还提供了画柱状图的相关工具
from pyecharts.charts import Bar
from pyecharts.options import *
# 得到一个柱状图对象
bar = Bar()
# 添加x轴元素
bar.add_xaxis(["中国", "英国", "美国"])
# 添加y轴元素并令数值显示在柱型顶部
bar.add_yaxis("GDP", [30, 20, 20], label_opts=LabelOpts(position="right"))
# x轴与y轴反转
bar.reversal_axis()
bar.render()
"""
演示带有时间线的柱状图开发
"""
from pyecharts.charts import Bar, Timeline
from pyecharts.options import LabelOpts
from pyecharts.globals import ThemeType
bar1 = Bar()
bar1.add_xaxis(["中国", "美国", "英国"])
bar1.add_yaxis("GDP", [30, 30, 20], label_opts=LabelOpts(position="right"))
bar1.reversal_axis()
bar2 = Bar()
bar2.add_xaxis(["中国", "美国", "英国"])
bar2.add_yaxis("GDP", [50, 50, 50], label_opts=LabelOpts(position="right"))
bar2.reversal_axis()
bar3 = Bar()
bar3.add_xaxis(["中国", "美国", "英国"])
bar3.add_yaxis("GDP", [70, 60, 60], label_opts=LabelOpts(position="right"))
bar3.reversal_axis()
# 构建时间线对象
# theme对应的是主题【即柱子颜色】
timeline = Timeline({"theme": ThemeType.LIGHT})
# 在时间线内添加柱状图对象
timeline.add(bar1, "点1")
timeline.add(bar2, "点2")
timeline.add(bar3, "点3")
# 自动播放设置
timeline.add_schema(
play_interval=1000, # 自动播放的时间间隔,单位毫秒
is_timeline_show=True, # 是否在自动播放的时候显示时间线
is_auto_play=True, # 是否自动播放
is_loop_play=True # 是否循环播放
)
# 绘图是用时间线对象绘图,而不是bar对象了
timeline.render("基础时间线柱状图.html")
相关数据如下百度云链接自取👇
链接:https://pan.baidu.com/s/1E7Fqjxw3rdCJ1F1yXfRZJQ
提取码:trjt
from pyecharts.charts import Bar, Timeline
from pyecharts.options import *
from pyecharts.globals import ThemeType
# 读取数据
f = open("D:\\1960-2019全球GDP数据.csv", "r", encoding="GB2312")
data_lines = f.readlines()
# 关闭文件
f.close()
# 删除第一条数据
data_lines.pop(0)
# 将数据转换为字典存储,格式为:
# { 年份: [ [国家, gdp], [国家,gdp], ...... ], 年份: [ [国家, gdp], [国家,gdp], ...... ], ...... }
# { 1960: [ [美国, 123], [中国,321], ...... ], 1961: [ [美国, 123], [中国,321], ...... ], ...... }
# 先定义一个字典对象
data_dict = {}
for line in data_lines:
year = int(line.split(",")[0]) # 年份
country = line.split(",")[1] # 国家
gdp = float(line.split(",")[2]) # gdp数据
# 如何判断字典里面有没有指定的key呢?
try:
data_dict[year].append([country, gdp])
except KeyError:
data_dict[year] = []
data_dict[year].append([country, gdp])
# 创建时间线对象
timeline = Timeline({"theme": ThemeType.LIGHT})
# 排序年份
sorted_year_list = sorted(data_dict.keys())
for year in sorted_year_list:
data_dict[year].sort(key=lambda element: element[1], reverse=True)
# 取出本年份前8名的国家
year_data = data_dict[year][0:8]
x_data = []
y_data = []
for country_gdp in year_data:
x_data.append(country_gdp[0]) # x轴添加国家
y_data.append(country_gdp[1] / 100000000) # y轴添加gdp数据
# 构建柱状图
bar = Bar()
x_data.reverse()
y_data.reverse()
bar.add_xaxis(x_data)
bar.add_yaxis("GDP(亿)", y_data, label_opts=LabelOpts(position="right"))
# 反转x轴和y轴
bar.reversal_axis()
# 设置每一年的图表的标题
bar.set_global_opts(
title_opts=TitleOpts(title=f"{year}年全球前8GDP数据")
)
timeline.add(bar, str(year))
# for循环每一年的数据,基于每一年的数据,创建每一年的bar对象
# 在for中,将每一年的bar对象添加到时间线中
# 设置时间线自动播放
timeline.add_schema(
play_interval=1000,
is_timeline_show=True,
is_auto_play=True,
is_loop_play=False
)
# 绘图
timeline.render("1960-2019全球GDP前8国家.html")