Python基础学习笔记【廖雪峰】

1. 入门

Python语法采用缩进方式,以#开头的语句是注释,当语句以:为结尾时,缩进的语句视为代码块

Python是动态语言,其变量本身类型不固定。与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错,Java就属于静态语言

空值NonePython里一个特殊的值,None不能理解为0,因为0是有意义的,而None是一个特殊的空值

a = -1;
if a>=0:
    print(a)
else:       #冒号下相同缩进部分视为在同一个代码块
    print(-a)
    print("hello")
print("你好")

2. 输入输出

# 单引号引起来是字符串
print('Hello')		
# 逗号隔开输出不同内容,同时会自动填上一个空格
print('100+200=',100+200)		
# 若不想要空格可以在print最后添加参数sep='',默认情况sep=' '
print('100+200=',100+200,sep='')

num = 15
# 也可以使用这种方式进行格式化输出
print(f"num是{num}")	
# input()是输入函数,输入结果放入name变量,python不需要手动定义类型
name = input();	
# 在提示语句后输入
name = input('请输入:');	

3. 数据类型和变量

计算机能处理的远不止数值,还可以处理文本、图形、音频、视频、网页等各种各样的数据,不同的数据,需要定义不同的数据类型。

3.1 整数

  1. Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样,例如:1100-80800
  2. 计算机由于使用二进制,所以,有时候用十六进制表示整数比较方便,十六进制用0x前缀和0-9a-f表示,例如:0xff000xa5b4c3d2
  3. 对于很大的数,例如10000000000,很难数清楚0的个数。Python允许在数字中间以_分隔,因此,写成10_000_000_00010000000000是完全一样的。十六进制数也可以写成0xa1b2_c3d4

3.2 浮点数

浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的

比如, 1.23 × 1 0 9 1.23\times 10^9 1.23×109 12.3 × 1 0 8 12.3\times 10^8 12.3×108 是完全相等的。浮点数可以用数学写法,如1.233.14-9.01

  • 对于很大或很小的浮点数,必须用科学计数法表示,把10e替代, 1.23 ∗ 1 0 9 1.23*10^9 1.23109 就是1.23e9,或者12.3e80.000012可以写成1.2e-5
  • 整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的,而浮点数运算则可能会有四舍五入的误差【详情请看计算机组成原理】

3.3 字符串

字符串是以单引号'或双引号"括起来的任意文本,比如'abc'"xyz"

  • 如果字符串内部既包含单引号''又包含双引号""怎么办?可以用转义字符\来标识,使特殊字符失去特殊含义

    s = 'I\'m \"OK\"!'
    # 输出:I'm "OK"!
    print(s)
    
  • 转义字符\除了使特殊字符失去特殊含义,也能让部分字符得到特殊含义,比如\n表示换行,\t表示制表符,字符\本身也要转义,所以\\表示的字符就是\

  • 如果字符串里面有很多字符都需要转义,就需要加很多\,为了简化,Python还允许用r''表示''内部的字符串默认不转义

    # 输出\t
    print(r'\t')	
    
  • 如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用'''...'''的格式表示多行内容

    # 输出 line1 line2 line3三行
    print('''line1
    line2
    line3''')
    

3.4 布尔值

布尔值和布尔代数的表示完全一致,一个布尔值只有TrueFalse两种值,要么是True,要么是False,在Python中,可以直接用TrueFalse表示布尔值,也可以通过布尔运算计算出来

  • 布尔值可以用and【与】or【或】not【非】计算

    # 输出False
    print(True and False)		
    # 输出True
    print(not False)		
    # 输出False
    print(17 > 18)				
    

3.5 常量

常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量

PI = 3.14159265359

但事实上PI仍然是一个变量,Python根本没有任何机制保证PI不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法

  • /除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数
  • 还有一种除法是//,称为地板除,结果只保留整数部分
# 输出为 3.0
print(9 / 3)	
# 输出为 3
print(10 // 3)	

3.6 理解变量在内存中的表示

a = 'ABC'
b = a
a = 'XYZ'
# 最终输出ABC
print(b)		
  • a赋给b时,实际上是b指向了a此时指向的常量数据ABC
  • 由于b本质指向的是数据,因此随后a指向了新数据后并不会影响b的指向
  • 因此b最终指向的依然是ABC

4. 字符串

Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes

4.1 编码

在操作字符串时,我们经常遇到strbytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对strbytes进行转换。

# ord()函数获取字符的Unicode编码
# 输出65【英文字符上Unicode编码与ASKII码一致】
print(ord('A'))		
# chr()函数把编码转换为对应的字符
# 输出a,因为97对应的是a
print(chr(97))		
# 如果知道字符的整数编码,也可以直接用十六进制写
# Unicode编码对应'中文',因此输出'中文'两个字
print('\u4e2d\u6587')	
# Python对bytes类型的数据用带b前缀的单引号或双引号表示
# 此时x存放的是bytes类型
x=b'ABC'	
# 通过encode()方法可以编码为指定的bytes
# 将字符串转换为ASKII码对应的bytes存储
print('ABC'.encode('ascii'))	
# 将字符串转换为UTF-8码对应的bytes存储
print('中文'.encode('UTF-8'))	   
# 通过decode()方法可以将bytes按照指定方式解码
# 以ASKII码的方式解释字节流,输出ABC
print(b'ABC'.decode('ascii'))	
# 如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节,如下
# 只输出'中',因为另一个字节码在utf-8编码情况下不对应任何字符
print(b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore'))

4.2 其他方法

4.3 格式化

%运算符用来格式化字符串。在字符串内部,%s表示用字符串替换,%d表示用整数替换,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个%?,括号可以省略

如果不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串

字符串里面的%是一个普通字符时需要转义,用%%来表示一个%

# 输出'Hello, world'
print('Hello, %s' % 'world')	
# 输出'Hi, Michael, you have $1000000.'
print('Hi, %s, you have $%d.' % ('Michael', 1000000))

5. 数据结构

5.1 链表 list

Python内置的一种数据类型是list,可以随时添加和删除其中的元素,将其理解为链表即可,同时Python中的list更加强大,内部可以存放不同类型的元素

# 创建一个list
classmates=['tom','jack','mike']	
# 用下标进行访问,输出tom
print(classmates[0])	
# 负的下标代表倒数第几个,此处输出mike
print(classmates[-1])	
p = ['asp', 'php']
# 此时s[2][1]等价于p[1]
s = ['python', 'java', p, 'scheme']	

list方法

# append方法将元素追加至末尾
append('martin')	
# insert方法将元素插入指定索引位置
insert(0,'linda')	
# pop()方法删除末尾元素,也可删除指定索引元素
pop()				
# 移除符合要求的第一个值
remove('xxx')	
# 连接两个列表,这是比+运算符连接效率更高的方法
extend(list)		
# 统计num在list中出现的次数
count(num)	

# 某个元素在不在列表中,返回布尔型
element in/not in listname		

list(reversed(range(5)))	# 生成一个倒序列表[4,3,2,1,0]
# 二分搜索以及已排序列表的插值【bisect本身不检查列表是否有序】
import bisect
c=[1,2,2,2,3,4,7]
# 返回元素2在c中应当插入的位置
bisect.bisect(c,2)   
# 把元素2插入应当插入的位置
bisect.insort(c,2)   

5.2 静态链表 tuple

tuple一旦初始化就不能修改【所谓“不变”是说每个元素指向永远不变】,这是其与list的唯一区别

# 当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来
t = (1, 2)	
# 定义一个空的tuple
t = ()			
# 只有1个元素的tuple定义时必须加一个逗号,否则会与数学中的小括号歧义
t = (1,)
# tuple和list还具有拆包特性
seq=(1,2,3,4)		
# 将1赋值给a,将2赋值给b,剩余部分成为列表放入rest
a,b,*rest=seq	

5.3 字典 dict (map)

# 定义key-value
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}	
# 取出键值对应的值
d['Michael']	 
# 放入键值对
d['Adam'] = 67    

dict方法

# 当键值不存在于字典中时会报错,因此取值前最好进行判断
# 若此时不存在这样的键值则返回False
'Thomas' in d	
# 若当前键值对不存在则返回None
d.get('Thomas')		
# 当键值对不存在时返回-1
d.get('Thomas', -1)	
# 删除键值对
d.pop('Bob')		

5.4 集合 set

set的原理与dict一致,不过其只存储key

# 得到一个空集合
s=set()				
# set的赋值初始化需要list作为输入集合
s=set([1, 2, 3])	

set方法

# add方法往集合添加元素
s.add(4)		
# remove方法移除集合中的元素
s.remove(4)		
# 得到交集
s1 & s2			
# 得到并集
s1 | s2			

6. 基础用法

6.1 条件判断

Python的条件判断是不需要小括号括起来的,同时由于其依靠相同缩进代表同一代码块,因此也无需中括号

age = 3
#只要age是非零数值、非空字符串、非空list等,就判断为True,否则为False
if age >= 18:			#不要少写冒号
    print('adult')
elif age >= 6:			#else if在此处缩写为elif
    print('teenager')
else:
    print('kid')

6.2 循环

# 定义一个list
nums=[1,2,3]	
# for的用法,不要忘了冒号:,while循环同样需要冒号
for x in nums:	
    # x是nums中的元素
    print(x)

# Python内置的enumerate函数可以把一个list变成[索引-元素]对,这样就可以在for循环中同时迭代索引和元素本身
for i, value in enumerate(['A', 'B', 'C']):
    # i即此元素在list中的下标
    print(i, value)		
    
# 也可以同时引用多个变量,此时就像是有多个索引进行遍历 
for x, y in [(1, 1), (2, 4), (3, 9)]:
    print(x, y)  

6.3 赋值

# 后边的部分先得到,接着按顺序赋值
a, b = b, a + b
# 上述赋值语句等价于👇
# t是一个tuple
t = (b, a + b) 
a = t[0]
b = t[1]

6.4 与and或or非not

andor返回第一个能让其确定结果为True或者False的值

# 输出None,因为None视为False,此时已经可以指定整体输出为False
print(None and 'a')		
# 输出None,因为a不为None视为True,此时结果看后半部分,即None
print('a' and None)		
# 输出b,理由同上
print('a' and 'b')		
# 空字符串也被视为False
# 输出a,因为a不为None视为False,此时整体已可以得知为False
print('a' or 'b')		

6.5 函数

Python中,定义一个函数要使用关键词def,函数名,括号,括号中的参数以及冒号

在缩进块中编写函数体,函数的返回值用return语句返回,如果没有return语句,函数执行完毕后也会返回结果,只是结果为Nonereturn None可以简写为return

num=0
def my_abs(x):
    # num是全局变量,想在函数中使用需要用global关键字标记
    global num	
    if x >= 0:
        return x
    else:
        return num

6.5.1 空函数

如果想定义一个什么事也不做的空函数,可以用pass语句:

def nop():
    pass

pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。pass还可以用在其他语句里,如下:

if age >= 18:
    # 此处若缺少了pass,代码运行就会有语法错误
    pass		

6.5.2 返回值

Python可以允许返回多个值,其原理是返回一个tuple,多个变量可以同时接收一个tuple按位置赋给对应的值

def move(x, y):
    nx = x 
    ny = y 
    # 返回多个值
    return nx, ny	
# x,y依次接受move函数返回的nx,ny
x, y = move(100, 100)		

6.5.3 默认参数

当函数有多个参数时可以指定默认参数,不过需要保证必选参数在前,默认参数在后

# 使用时当n处不填参数也不会报错,n默认为2
def power(x, 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

6.5.4 可变参数

可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0

定义可变参数和定义一个listtuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数

def calc(*numbers):
    sum=0
    # 将传入的参数全部进行相加
    for n in numbers:
        sum+=n
    return sum
# 得到2,3,4之和
result = calc(2,3,4)

# 也可以在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去
nums = [1,2,3]
calc(*nums)

6.5.5 关键字参数

关键字参数允许传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)
# kw接收键值对
person('Adam', 45, gender='M', job='Engineer')
# 也可以直接传入定义好的dict
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra)

kw获得的dictextra的一份拷贝,对kw的改动不会影响到函数外的extra

命名关键字参数

当使用了关键字参数时,正常情况下会将所有输入的键值对都进行接收,若我们只想接收指定关键值则需要用到命名关键字参数

# 命名关键字参数需要一个特殊分隔符*, *后面的参数被视为命名关键字参数
# 只接受city和job的关键字
def person(name, age, *, city, job):	
    print(name, age, city,job)
# 调用函数,其中key = value必须写全,不能单写一个value    
# 命名关键字参数可以有缺省值【即默认参数】,从而简化调用
person('Jack',24, city='Beijing', job='Engineer')
# 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
# *args说明是可变参数
def person(name, age, *args, city, job):	
    print(name, age, args, city, job)

6.5.6 参数组合

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的键值对会对应给d的参数,剩下的内容才会归入参数中的kw
kw = {'d': 88, 'x': '#'}
f2(*args, **kw)

虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差

7. 基础内置函数

Python已经封装好,我们可以直接拿来用的函数

7.1 数学

# 得到绝对值
abs(-100)
# 得到最大值,参数可多个
max(2, 3, 1, -5)	
# 开平方
math.sqrt()	
# 对num进行四舍五入,保留2位小数
round(num,2)		

7.2 类型转换

# 转为整型
int('123')			
# 转为浮点型,Python没有double
float('12.34')		
# 转为字符串
str(1.23)			
# 转为布尔型,非空即为True
bool(1)				

7.3 数据类型检查isinstance

# 数据类型检查可以用内置函数isinstance()实现
# 若x属于int型或float型时返回True
isinstance(x, (int, float))		

7.4 长度len

# len()函数计算的是str的字符数【中文同适用】,如果换成bytes,len()函数就计算字节数
# 输出2,因为有两个字符
print(len('中文'))	
# 输出3,UTF-8编码一个中文字符通常占3个字节,所以输出6
print(len('你好'.encode('utf-8'))) 
方法说明
range(a,b)生成[a,b)的整数列表

8. 高级特性

8.1 切片

Python提供一系列取指定索引范围的操作,形象地称为切片Slicelisttuple,字符串都可进行切片操作

a[x:y:z]x表示切片起点,y表示切片终点【不包括】,z表示步长。如果不指定xy,则默认开始和最后;如果不指定z,则默认步长为1。当**z-1时代表倒序且步长为1**,此时xy对应的是倒序后的下标

L=['tom','jack','jery']
# L[0:2]表示,从索引0开始取,直到索引1为止
L[0:2]
# 如果第一个索引是0,还可以省略,写成L[:2]
# L[-2:-1]取倒数第二个,但不包括倒数第一个
# L[-2:]从倒数第二个开始往后取至末尾

n='1234'
# -1代表逆序,此时起点是最后一个元素,由后往前取,输出[4,3,2,1]
n[::-1]	
# 从下标2开始往前取,所以是[3, 2, 1]
n[2::-1]	
# 从最后一个元素处取到下标2【不包括】,所以是[4]
n[:2:-1]	

8.2 迭代

如果给定一个listtuple,我们可以通过for循环来遍历这个listtuple,这种遍历我们称为迭代Iteration

Python中,迭代是通过for ... in来完成的,默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values()如果要同时迭代keyvalue,可以用for k, v in d.items()

8.3 列表生成式

创建列表的时可以直接在列表内写表达式,符合表达式的部分成为列表元素

# 如下,生成[4, 16, 36, 64, 100]
L = []
for x in range(1, 11):
    if x % 2 == 0:
        L.append(x*x)
        
# 也可以在表达式中直接这样写👇
# 写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来
# 此时if后不能用else
L=[x * x for x in range(1, 11) if x % 2 == 0]	

# 当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]

8.4 生成器

Python一边循环一边计算的机制,称为生成器:generator。当我们仅仅需要使用列表生成式中的少量元素时,使用generator可以节省根据需要创建我们需要的元素,从而节约空间

8.4.1 创建generator方法 1

把一个列表生成式的[]改成(),就创建了一个generator

# 这里通过列表生成式得到list
l = [x * x for x in range(10)]	
# 这里得到generator
g = (x * x for x in range(10))	

# g内元素如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值
# 直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误
# 不过此方法一般不用,因为通过循环同样可以对g进行遍历且不用担心异常
next(g)		

8.4.2 创建generator方法 2

如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator

generator的函数会在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行

调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator

def odd():
    print('hh')
    # yield是generator函数的标志
    yield 1		
    print('jj')
    yield(2)
    print('kk')
    yield(3)
    
# 调用该generator函数时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值  
# 得到一个generator对象
o = odd()	
# 输出hh,返回1
next(o)		
# 输出jj,返回2
next(o)		
# 输出kk,返回3
next(o)		
# 此时函数已经执行完毕,报错StopIteration
next(o)		

实际上,遍历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):
    # 输出1,1,2,3,5,8
	print(n)	
# 但是此方法只能拿到yield返回值,无法拿到return返回值
g = fib(6)

# 若需要拿到return返回值则需要进行如下操作
while True:
	try:
		x = next(g)
		print('g:', x)
     # 发生异常后才会执行此代码块
	except StopIteration as e:	
         # e.value即return返回值
		print('Generator return value:', e.value) 
		break

8.5 迭代器

我们已经知道,可以直接作用于for循环的数据类型有以下几种:

  • 一类是集合数据类型,如listtupledictsetstr等;
  • 一类是generator,包括生成器和带yieldgenerator function

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable,可以使用isinstance()判断一个对象是否是Iterable对象

可以被next()函数调用并不断返回下一个值的对象称为迭代器Iterator

生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

# 通过iter()函数可以把Iterable变成Iterator
# 将list转变为Iterator对象后进行判断,返回True
isinstance(iter([]), Iterator)	

为什么listdictstr等数据类型不是Iterator

因为PythonIterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的

  • 凡是可作用于for循环的对象都是Iterable类型
  • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列

9. 函数式编程

函数式编程是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用

而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的

函数式编程的一个特点是:允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

9.1 高阶函数

变量可以指向函数,函数的参数能接收变量,一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”

# 变量a指向abs函数
a = abs 
# 所以也可以通过a调用abs函数
a(-1) 	

# 将1赋给abs,此时abs不再指向绝对值函数,而指向数字1
abs=1;	
# 一个简单例子
# f作为参数接受的是函数
def add(x,y,f):		
	return f(x) + f(y)
# 输出11
print(add(5,6,abs))		

9.1.1 map

map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回

def f(x):
    return x * x
# list中的元素依次执行f(x)
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])	
# 用list装Iterator
r=list(r)	
# 输出[1, 4, 9, 16, 25, 36, 49, 64, 81]
print(r)	
# 将list中的元素都变成字符串
l=list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))

9.1.2 reduce

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算

# 👇等价于 f(f(f(x1, x2), x3), x4)
reduce(f, [x1, x2, x3, x4]) 
# 如下简单例子,对序列求和
def add(x, y):
     return x + y
# 先 f(1,3),再将结果放入问号 f(?,5)...
reduce(add, [1, 3, 5, 7, 9])	
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与map结合使用,实现把字符串转整型
reduce(fn, map(char2num, '13579')) 

9.1.3 filter

map()类似,filter()也接收一个函数和一个序列,不过和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素

# 一个简单例子
def is_odd(n):
    # 结果为True的元素保留
    return n % 2 == 1	
# 保留下奇数
# 结果: [1, 5, 9, 15]
l=list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 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']

9.1.4 sorted/sort

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方法进行操作

9.2 返回函数

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    # 返回值是sum()这个函数
    return sum	

# 多次调用lazy_sum时返回的函数是独立的
# 此时得到的是sum()这个函数,并未进行求和
f = lazy_sum(1, 3, 5, 7, 9)	
# 调用f()函数时才开始执行
num=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
# 输出9,9,9
print(f1(),f2(),f3())	

返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量【如上👆】

如果一定要引用循环变量怎么办?

可以再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变,缺点是代码较长,可利用lambda函数缩短代码【9.3学】

def count():
 def f(j):
     def g():
         return j*j
     return g
    
 fs = []
 for i in range(1, 4):
     # f(i)立刻被执行,因此i的当前值被传入f()
     fs.append(f(i)) 
 return fs

f1, f2, f3 = count()	
# 此时输出1,4,9
print(f1(),f2(),f3())	
nonlocal

使用闭包,就是内层函数引用了外层函数的局部变量,如果内层函数直接对外层变量赋值,由于Python解释器会把x当作函数fn()的局部变量,它会报错。原因是x作为局部变量并没有初始化,直接计算x+1是不行的,但我们其实是想引用inc()函数内部的x,所以需要在fn()函数内部加一个nonlocal x的声明

def inc():
    x = 0
    def fn():	
        # nonlocal x		# 加此声明
        # 此时解释器会把x当作是外层变量
        x = x + 1			
        return x
    return fn

f = inc()
# 输出1
print(f()) 
# 输出2
print(f()) 

9.3 匿名函数

当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便

关键字lambda表示匿名函数,冒号:前面的x表示函数参数。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数

# 传入参数为x,返回值为x*x
f = lambda x: x * x	
# 输出 25
print(f(5))		

9.4 装饰器

在代码运行期间动态增加功能的方式,称之为“装饰器”Decorator,本质上,decorator就是一个返回函数的高阶函数

def log(func):
    # 可以接受任何参数
    def wrapper(*args, **kw):	
        # 每个函数都有__name__属性,其封装了函数名
        print('call %s():' % func.__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()函数的定义处,相当于执行了语句👇
# 相当于wrapper函数赋给now(),所以再次执行now()实际是wrapper,而原本的now早已通过参数func被wrapper获得,因此后续执行仍能定位到原now
now = log(now)	

# 不过由于当前now已经被赋为wrapper了,因此通过now._name_属性我们只能得到wrapper
# 因此我们工作仍未完成,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行会出错
# 不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下
# 导入functools模块
import 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()函数的定义处,相当于执行了语句👇
# 前半部分返回decorator函数,接着传参now,后续执行如上
now = log('execute')(now) 

9.5 偏函数

通过设定参数的默认值,可以降低函数调用的难度,偏函数也可以做到这一点

# int()默认将字符串当成十进制进行解析,我们通过设置参数默认值使得int2()将字符串当成二进制进行解析
def int2(x, base=2):
    return int(x, base)

# functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2
import functools
int2 = functools.partial(int, base=2)
# 输出64
print(int2('1000000'))	
# 新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值
# 输出1000000
print(int2('1000000',base=10))	

# 创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数
# 上边创建的int2(),等价于👇	
kw = { 'base': 2 }
int('10010', **kw)

# 当传入*arg的参数时,实际上会被安排到最左边
max2 = functools.partial(max, 10)
max2(5, 6, 7)	
# 等价于👇
args = (10, 5, 6, 7)
max(*args)

10. 模块

为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Python中,一个.py文件就称之为一个模块Module

同时,为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包Package。注意:每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块

# 此注释👇允许此.py文件直接在Unix/Linux/Mac上运行
#!/usr/bin/env python3	
# 此注释👇表示.py文件本身使用标准UTF-8编码 
# -*- coding: utf-8 -*-		

# 表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释
' a test module '		

# 使用__author__变量把作者写进去
__author__ = 'Michael Liao'	

# 上面部分👆是Python模块的标准文件模板
# 导入sys模块
import 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

11. 面对对象编程

Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类Class的概念

11.1 类和实例

# 定义一个类,object代表Student是从object继承下来的
class Student(object):	
    pass

# 创建Student实例
bart = 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)

11.2 访问限制

# 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
class Student(object):
    def __init__(self, name, score):
        # 两个下划线,说明name是私有变量
        self.__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__这样的变量名

11.3 继承和多态

当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类Subclass,而被继承的class称为基类、父类或超类Base classSuper class

class Animal(object):
    def run(self):
        print('Animal is running...')
        
# Dog就是Animal的子类        
class Dog(Animal):
    pass
  • 子类可以获得父类的全部功能
  • 当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子

11.4 获取对象信息

判断对象类型,可以使用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()
# 子类属于父类,因此返回True
>>> isinstance(h, Animal)	
True
# 判断一个变量是否是某些类型中的一种
>>> isinstance([1, 2, 3], (list, tuple))	
True
# 基本类型也可以用isinstance()判断
>>> isinstance(b'a', bytes)	
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(),我们可以直接操作一个对象的状态

11.5 实例对象和类属性

# 如果类本身需要绑定一个属性,可以直接在class中定义属性,这种属性是类属性,归该类所有
>>> class Student(object):
...     name = 'Student'
...
# 创建实例s
>>> s = Student() 
# 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
>>> print(s.name) 
Student
# 打印类的name属性
>>> print(Student.name) 
Student

# 给实例绑定name属性
>>> s.name = 'Michael' 
# 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
>>> print(s.name) 
Michael

# 但是类属性并未消失,用Student.name仍然可以访问
>>> print(Student.name) 
Student

# 如果删除实例的name属性
>>> del s.name 
# 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
>>> print(s.name) 
Student

12. 面对对象高级编程

from types import MethodType
# 定义一个函数作为实例方法
def set_age(self, age): 
    self.age = age
# 除了可以给实例对象绑定属性外,也可以给实例对象绑定方法
class Student(object):
    pass

s = Student()
 # 给实例绑定一个方法【需要借助MethodType】
s.set_age = MethodType(set_age, s)
# 调用实例方法
s.set_age(25) 
# 测试结果,输出 25
print(s.age) 

# 为了给所有实例都绑定方法,可以给 class 绑定方法
# 注意:给实例绑定方法和给类绑定方法写法是不一样的
Student.set_age = set_age

12.1 使用__slots__

Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性

class Student(object):
    # 用tuple定义允许绑定的属性名称
    __slots__ = ('name', 'age') 
    
# 创建新的实例
s = Student() 
# 由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误
s.score = 99  

使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的

如果在子类中也定义__slots__,这样子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

12.2 @property

利用@property可以实现通过属性调用【方法在此被视为属性】智能绑定get方法和set方法

class Student(object):
    
	# @proerty绑定get方法,使得score属性拥有get方法
    @property	
    def score(self):
        return self._score

    # @score.setter使得set方法变为属性,使得score属性拥有set方法
    @score.setter	
    def score(self, value):
        if(value >= 60):
            self._score = value
        else:
            print('没有及格')

s=Student()
# score是方法名,但是被注释后成为属性名,赋值操作实则是调用set
s.score=61	
# 非赋值的调用实则调用get,因此输出 61
print(s.score)	

特别注意:属性的方法名不要和实例变量重名,否则会造成无限递归!

12.3 多重继承

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()
# 遇到同名方法时优先调用写在最左边的父类,输出Running...
d.run()		

MixIn

MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系

# 每个MixIn都有自己的功能
class RunnableMixIn(object):
    def run(self):
        print('Running...')
        
 # 我们根据需要将MixIn进行继承,从而获得想要的功能
class Dog(Animal,RunnableMixIn):
    pass

12.4 定制类

我们可以根据需要重写一些类方法从而使其能更好为我们服务

12.4.1 __str__

__str__类似与Java中的toString方法,通过重写此方法,可以使得直接输出类时按照我们想要的格式进行输出

class Student(object):
	def __init__(self, name):
		self.name = name
    # 重写__str__
	def __str__(self):	
		return 'Student object (name: %s)' % self.name

# 输出 Student object (name: Michael)
print(Student('Michael')) 

12.4.2 __iter__

如果一个类想被用于for ... in循环,类似listtuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Pythonfor循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环

# 以斐波那契数列为例
class Fib(object):	
    def __init__(self):
        # 初始化两个计数器a,b
        self.a, self.b = 0, 1 

    # 使用for...in的时候会调用
    def __iter__(self):	
        return self # 实例本身就是迭代对象,故返回自己

    # for循环每进行一次就会调用一次
    def __next__(self):	
        # 计算下一个值
        self.a, self.b = self.b, self.a + self.b
        # 退出循环的条件
        if self.a > 100000: 
            raise StopIteration()
        # 斐波那契数列当前值
        return self.a 
 
for n in Fib():
    # 此时会打印Fib类中每次__next__的执行结果
	print(n)	

12.4.3 __getitem__

如果类想像list那样按照下标取出元素,需要实现__getitem__()方法

class Fib(object):
    # n为下标值
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a
    
f = Fib()
# 此时调用__getitem__方法,n为0,输出 1
print(f[0])	

# list还可以使用切片的方式访问,因此若我们将n定死为整型则无法使用切片访问
class Fib(object):
    def __getitem__(self, n):
        # n是索引
        if isinstance(n, int): 
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        # n是切片
        if isinstance(n, slice): 
            # 开始索引
            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()
# 输出[1, 1, 2, 3, 5]
print(f[0:5])			
# 像上述👆这样设计仍然有缺陷,比如步数step还未处理等

与之对应的是__setitem__()方法,把对象视作listdict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素。总之,通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。

12.4.4 __getattr__

正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。要避免这个错误,除了可以加上这个属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性

class Student(object):
# 只有在没有找到属性的情况下才调用__getattr__,已有的属性不会在__getattr__中查找
    def __getattr__(self, attr):
        # 如果访问的属性是score就返回 99
        if attr=='score':	
            return 99

12.4.5 __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')
# self参数不要传入,输出 My name is Michael.
s() 

__call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象。如果把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。

那么,怎么判断一个变量是对象还是函数呢?

其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有__call__()的类实例

# max函数可以被调用,所以为True
>>> callable(max)	
True
# list不可以被调用,所以为False
>>> callable([1, 2, 3])		
False

12.5 枚举类

当我们需要定义常量时,一个办法是用大写变量通过整数来定义,好处是简单,缺点是类型是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的value被设定为0
    Sun = 0     
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6
# 访问这些枚举类型可以有若干种方法
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])	
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(Weekday(1))
Weekday.Mon

12.6 使用元类

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的

class Hello(object):
    def hello(self, name='world'):
        print('Hello, %s.' % name)
        
h = Hello()
h.hello()

Python解释器载入hello模块时,才会依次执行该模块的所有语句,执行结果就是动态创建出一个Helloclass对象

type()

type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello

我们说class的定义是运行时动态创建的,而创建class的方法就是使用type()函数

type()函数既可以返回一个对象的类型【比如整数是int,字符串是strclasstype】,又可以创建出新的类型【不通过class定义而直接通过class定义的本质type()函数创建】

# 先定义函数
def fn(self, name='world'): 
	print('Hello, %s.' % name)

# 要创建一个class对象,type()函数依次传入3个参数:
# 1. class的名称;
# 2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
# 3. class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上
# 创建 class Hello
Hello = type('Hello', (object,), dict(hello=fn)) 

通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class

正常情况下,我们都用class Xxx...来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。

除了使用type()动态创建类以外,要控制类的创建行为【得到类的实例】,需要使用metaclass

metaclass

metaclass直译为元类,简单的解释就是:当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。可以把类看成是metaclass创建出来的“实例”

正常情况下不会碰到需要使用metaclass的情况,等需要的时候再回来看这部分知识点!

13. 文件

Python使用open函数,可以打开一个已经存在的文件,或者创建一个新文件

# name:是要打开的目标文件名的字符串(可以包含文件所在的具体路径)
# mode:设置打开文件的模式(访问模式):只读(r)、从头写入(w)、追加(a)等。
# encoding:编码格式(推荐使用UTF-8)
# 返回一个_io.TextIOWrapper对象
f = open(name, mode, encoding) 

# 读取文件所有内容,填写参数时则读取参数长度字节内容,返回str类型
f.read()		
# 读取所有内容,每一行为list中的一个元素
f.readlines()	
# 调用一次读取一行内容
f.readline()	
# 也可以通过for循环进行读取
for line in f:	
	print(f"每一行数据是:{line}")
    
# 关闭文件流,内含flush功能
f.close()	
# 若使用此语句打开文件,则文件流最后会自动关闭
with open(xxx) as f:	
    xxxxx
    
# 将内容写入文件
f.write(xxx)
# 刷新后写入的内容才会从缓存中发送到硬盘上
f.flush()		

14. 异常处理

当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕

try:
    print('try...')
    r = 10 / 0
    print('result:', r)
# python所有的错误类型都继承自BaseException,捕获错误时也会把子类一网打尽
# except: 是捕获所有异常
# 可以有多个except捕获不同的错误
except ZeroDivisionError as e:		
# except (xxx,xxx) as e	是捕获多个异常
    print('except:', e)
# finally是一定会执行的部分
finally:		
    print('finally...')
print('END')

各异常的层级关系👇

image-20220729151135974

使用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!')
        # 主动抛出此异常,说明ValueError这个异常还未被解决,上层依旧可以进行捕获
        raise	
bar()

15. 模块

Python模块Module,是一个Python 文件,以.py 结尾。模块能定义函数,类和变量,模块里也能包含
可执行的代码

python中有很多各种不同的模块,每一个模块都可以帮助我们快速的实现一些功能,比如实现和时间相关的功能可以使用time模块等。我们可以认为一个模块就是一个工具包,每一个工具包中都有各种不同的工具供我们使用进而实现各种不同的功能

# 模块在使用前需要先导入,导入语法如下👇
[from 模块名] import [模块│类│变量│函数│*] [as 别名]

# 常见的有以下👇几种形式
# 整个模块引入
import 模块名		
# 引入具体的方法
from 模块名 import 类、变量、方法等	
from 模块名 import *
import 模块名 as 别名
from 模块名 import 功能名 as 别名

# 导入time模块
import time	
# 通过模块调用内部方法
time.sleep(5)	
# 导入time.sleep()方法
from time import sleep	
# 直接使用方法名就可以完成调用
sleep(5)		

15.1 自定义模块

简单来说就是将自己写的模块导入当前正在处理的.py文件,类似于Java在一个类中使用另一个文件的类时也需要先导入此类

# 当遇到不同模块的重名方法时,后导入的方法会覆盖先导入的方法
from module1 import test
from module2 import test
# 此时调用的是module2中的方法
test()		

在导入模块时,其实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

15.2 自定义Python包

从物理上看,包就是一个文件夹,在该文件夹下包含了一个_init_.py文件,该文件夹可用于包含多个模块文件。从逻辑上看,包的本质依然是模块【区别在于是否有_init_.py文件

PyCharm中直接可以创建Python Package,创建后会自动生成_init_.py文件,接着我们将自己定义的模块放入此包下就行

# 导入包的方法
# 导入my_package下的module1模块
from my_package import module1	
# 导入my_package下的module1,module2模块
from my_package import module1,module2	
# 导入my_package下的module模块的test()方法
from my_package.module1 import test	

# 还可以通过在__init__.py中设置__all__变量来控制import *的行为
# 例如在__init__.py写入此语句
__all__=['module2']		
# 此时只能导入module2模块
from my_package import *	

16. 引入第三方包

Python程序的生态中,有许多非常多的第三方包【非Python官方】,可以极大的帮助我们提高开发效率,如:

  • 科学计算中常用的:numpy
  • 数据分析中常用的:pandas
  • 大数据计算中常用的:pyspark,apache-flink
  • 图形可视化常用的:matplotlib,pyecharts

但是由于是第三方,所以Python没有内置,所以我们需要安装它们才可以导入使用

# 第三方包的安装非常简单,我们只需要使用Python内置的pip程序即可
# 此语句在命令提示符中运行
pip install 包名称		
# 由于pip命令默认是从国外服务器下载包,因此速度较慢,我们可以为其设置镜像下载地址 
pip install xlrd -i https://pypi.tuna.tsinghua.edu.cn/simple openpyxl

当然,在PyCharm中也支持安装第三方包

image-20220812143414982

image-20220812143705510

或者在PyCharm中直接使用import语句导入包再按Alt+Enter也会进行自动导包

17. Json

JSON是一种轻量级的数据交互格式,可以按照JSON指定的格式去组织和封装数据,本质上是一个带有特定格式的字符串,是不同编程语言通信的中转站

// Json格式如下👇
// 单个Json的格式与Python中的字典dict类似
{"name":"tom","age":18}		
// 多个Json实则是列表+字典
[{"name":"tom","age":18},{"name":"tom","age":18}] 
# 使用Json需要导入内置模块
import json		
# data是一个列表字典
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)
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值