_
的组合,且不能用数字开头=
是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量
Python中,通常用全部大写的变量名表示常量
PI
仍然是一个变量,Python根本没有任何机制保证PI
不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法字符串是以单引号'
或双引号"
括起来的任意文本
如果字符串内部既包含'
又包含"
怎么办?可以用转义字符\
来标识
\
可以转义很多字符,比如\n
表示换行,\t
表示制表符,字符\
本身也要转义,所以\\
表示的字符就是\
r''
表示''
内部的字符串默认不转义Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
ASCII编码是1个字节,而Unicode编码通常是2个字节。
UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言
ord()
函数获取字符的整数表示,chr()
函数把编码转换为对应的字符>>> ord('A') 65 >>> ord('中') 20013
Python对bytes
类型的数据用带b
前缀的单引号或双引号表示:
x = b'ABC'
纯英文的str
可以用ASCII
编码为bytes
,内容是一样的,含有中文的str
可以用UTF-8
编码为bytes
,用encode()方法
读到的数据就是bytes
。要把bytes
变为str
,就需要用decode()
方法
True
、False
两种值空值是Python里一个特殊的值,用None
表示。None
不能理解为0
,因为0
是有意义的,而None
是一个特殊的空值。
函数 | 说明 |
---|---|
int(x [,base ]) | 将x转换为一个整数 |
float(x ) | 将x转换为一个浮点数 |
complex(real [,imag ]) | 创建一个复数,real为实部,imag为虚部 |
str(x ) | 将对象 x 转换为字符串 |
repr(x ) | 将对象 x 转换为表达式字符串 |
eval(str ) | 用来计算在字符串中的有效Python表达式,并返回一个对象 |
tuple(s ) | 将序列 s 转换为一个元组 |
list(s ) | 将序列 s 转换为一个列表 |
chr(x ) | 将一个整数转换为一个Unicode字符 |
ord(x ) | 将一个字符转换为它的ASCII整数值 |
hex(x ) | 将一个整数转换为一个十六进制字符串 |
oct(x ) | 将一个整数转换为一个八进制字符串 |
bin(x ) | 将一个整数转换为一个二进制字符串 |
运算符 | 描述 | 实例 |
---|---|---|
+ | 加 | 两个对象相加 a + b 输出结果 30 |
- | 减 | 得到负数或是一个数减去另一个数 a - b 输出结果 -10 |
* | 乘 | 两个数相乘或是返回一个被重复若干次的字符串 a * b 输出结果 200 |
/ | 除 | b / a 输出结果 2 |
// | 取整除 | 返回商的整数部分 9//2 输出结果 4 , 9.0//2.0 输出结果 4.0 |
% | 取余 | 返回除法的余数 b % a 输出结果 0 |
** | 指数 | a**b 为10的20次方, 输出结果 100000000000000000000 |
运算符 | 描述 | 实例 |
---|---|---|
+= | 加法赋值运算符 | c += a 等效于 c = c + a |
-= | 减法赋值运算符 | c -= a 等效于 c = c - a |
*= | 乘法赋值运算符 | c *= a 等效于 c = c * a |
/= | 除法赋值运算符 | c /= a 等效于 c = c / a |
%= | 取模赋值运算符 | c %= a 等效于 c = c % a |
**= | 幂赋值运算符 | c **= a 等效于 c = c ** a |
//= | 取整除赋值运算符 | c //= a 等效于 c = c // a |
格式符号 | 转换 |
---|---|
%c | 字符 |
%s | 字符串 |
%d | 有符号十进制整数 |
%u | 无符号十进制整数 |
%o | 八进制整数 |
%x | 十六进制整数(小写字母0x) |
%X | 十六进制整数(大写字母0X) |
%f | 浮点数 |
%e | 科学计数法(小写’e’) |
%E | 科学计数法(大写“E”) |
%g | %f和%e 的简写 |
%G | %f和%E的简写 |
age = 18 name = "xiaohua" print("我的姓名是%s, 年龄是%d" % (name, age))
f-strings 以字母 ‘f’ 或 ‘F’ 为前缀, 格式化字符串使用一对单引号、双引号、三单引号、三双引号
name = '峰哥' age = 33 format_string1 = f'我的名字是 {name}, 我的年龄是 {age}'
使用字符串的format()
方法,它会用传入的参数依次替换字符串内的占位符{0}
、{1}
……
>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125) 'Hello, 小明, 成绩提升了 17.1%'
如有字符串mystr = 'hello world itcast and itcastcpp'
检测 str 是否包含在 mystr中,如果是返回开始的索引值,否则返回-1
mystr.find(str, start=0, end=len(mystr)) mystr.find('it', 0, 10)
跟find()方法一样,只不过如果str不在 mystr中会报一个异常.
mystr.index(str, start=0, end=len(mystr))
返回 str在start和end之间 在 mystr里面出现的次数
mystr.count(str, start=0, end=len(mystr))
把 mystr 中的 str1 替换成 str2,如果 count 指定,则替换不超过 count 次.
mystr.replace(str1, str2, mystr.count(str1))
把字符串的第一个字符大写
mystr.capitalize()
把字符串的每个单词首字母大写
>>> a = "hello itcast" >>> a.title() 'Hello Itcast'
转换 mystr 中所有大写字符为小写
mystr.lower()
转换 mystr 中的小写字母为大写
mystr.upper()
检查字符串是否是以 “hello” 开头, 是则返回 True,否则返回 False
mystr.startswith("hello")
检查字符串是否以"world"结束,如果是返回True,否则返回 False.
mystr.endswith("world")
返回一个原字符串左对齐,并使用空格填充至长度 width 的新字符串
mystr.ljust(width)
返回一个原字符串居中,并使用空格填充至长度 width 的新字符串
mystr.center(width)
删除 mystr 左边/右边/两边的空白字符或指定字符
mystr.lstrip() mystr.lstrip(“#”)
以 str 为分隔符切片 mystr,如果 maxsplit有指定值,则仅分隔 maxsplit 个子字符串
mystr.split(" ", 2)
按照行分隔,返回一个包含各行作为元素的列表
mystr.splitlines()
把mystr以str分割成三部分,str前,str和str后
mystr.partition(str)
alist 中每个元素用str连接,构造出一个新的字符串
str = "_" alist = ['1', '2', '3'] str.join(alist) >>> ‘1_2_3’
如果 mystr 所有字符都是字母/数字/字母或数字/空格 则返回 True,否则返回 False
mystr.isalpha()
Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。
所谓的查找,就是看看指定的元素是否存在
python中查找的常用方法为:
index查找(与字符串查找相同)
>>> a = ['a', 'b', 'c', 'a', 'b'] >>> a.index('a', 1, 3) # 注意是左闭右开区间
del:根据下标进行删除
del li[i] # 删除列表li中索引为i元素
pop():按索引删除一个元素,删除时会返回被删除的元素
li.pop() # 删除最后一个元素 li.pop(i) # 删除列表li中索引为i元素
remove:根据元素的值进行删除,删除第一个符合条件的值
>>> str=[1,2,3,4,5,2,6] >>> str.remove(2) >>> str [1, 3, 4, 5, 2, 6]
用len()
函数可以获得list元素的个数
sort方法是将list按特定顺序重新排列,默认为由小到大,参数reverse=True可改为倒序,由大到小。
使用for循环
namesList = ['xiaoWang','xiaoZhang','xiaoHua'] for name in namesList: print(name)
使用while循环
namesList = ['xiaoWang','xiaoZhang','xiaoHua'] i = 0 while i < len(namesList): print(namesList[i]) i+ = 1
t = (1,)
元组修改元素
将元组转换为列表并更改值
通过现有字符串的片段在构造一个新的字符串的方式来等同于更新元组操作
tuple_1=(1,2,3,"ewang","demo") #通过索引更新 tuple_1=tuple_1[0],tuple_1[2],tuple_1[4] print tuple_1 #通过切片更新 tuple_1=tuple_1[0:2] print tuple_1 # 添加元素 tuple_1 = tuple_1 + (4,)
列表和元组区别
切片是指对操作的对象截取其中一部分的操作。
字符串、列表、元组都支持切片操作。
s = "abcdefghijk" print(s[0:5:1]) print(s[0:5:2]) print(s[3:6]) # 默认步长可以不写,默认为1 print(s[:5]) # 开始索引也可以不写,默认从头开始 print(s[5:]) # 结束也可以不写,默认到最后 print(s[:]) # 全默认,默认截取整串 print(s) print(s[10:20]) # 切片时不会出现下标越界错误 # 切片的下标还可是以负数 # 负数是,是从右向左切片,起始下标为 -1 print(s[-1:-5]) print(s[-1:-5:-1]) # 特殊需要记住的切片方式 # 使用切片实现字符串逆序 print(s[::-1])
使用键-值(key-value)存储,具有极快的查找速度。
通过键访问值
di['key'] = value # 若访问不存在的键,则会报错
在我们不确定字典中是否存在某个键而又想获取其值时,可以使用get方法,还可以设置默认值
>>> age = info.get('age') >>> age #'age'键不存在,所以age为None >>> type(age) <type 'NoneType'> >>> age = info.get('age', 18) # 若info中不存在'age'这个键,就返回默认值18 >>> age 18
字典的每个元素中的数据是可以修改的,只要通过key找到,即可修改
di['a'] = x # 把键为a的值修改为x
在使用 变量名[‘键’] = 数据 时,这个“键”在字典中,不存在,那么就会新增这个元素
di['newkey'] = newvalue
pop(‘key’):按键删除一个元素,删除时会返回被删除元素的值
del di[‘key’]:删除指定键
del di:删除整个字典对象
clear():清空整个字典内容
di.clear() >>>{}
len(di)
返回一个包含字典所有KEY的列表
返回一个包含字典所有value的列表
返回一个包含所有(键,值)元祖的列表
di = {'Michael': 95, 'Bob': 75, 'Tracy': 85} di.keys() >>> ['Michael', 'Bob', 'Tracy'] di.values() >>> [95, 75, 85] di.items() >>> [('Michael', 95), ('Bob', 75), ('Tracy', 85)]
di = {'Michael': 95, 'Bob': 75} for key in di.keys(): print(key) >>> Michael Bob
di = {'Michael': 95, 'Bob': 75} for value in di.values(): print(value) >>> 95 75
di = {'Michael': 95, 'Bob': 75} for item in di.items(): print(item) >>> ('Michael', 95) ('Bob', 75)
di = {'Michael': 95, 'Bob': 75} for key,value in di.items(): print('%s, %s'% (key,value)) >>> Michael, 95 Bob, 75
dict内部存放的顺序和key放入的顺序是没有关系的。
和list比较,dict有以下几个特点:
而list相反:
所以,dict是用空间来换取时间的一种方法。
dict的key必须是不可变对象。
set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
通过add(key)
方法可以添加元素到set中,可以重复添加,但不会有效果
s.add(key)
通过remove(key)
方法可以删除元素
s.remove(key)
clear()清空元素
交集指的是两个不同的集合中相同的集合打印出来
>>> a = set('abc') >>> b = set('cdef') >>> a & b set(['c'])
将两个集合中所有元素合并到一起
>>> a = set('abc') >>> b = set('cdef') >>> a | b set(['a', 'c', 'b', 'e', 'd', 'f'])
差集指的是两个没有集合中不同的元素,前面的集合为准
>>> a = set('abc') >>> b = set('cdef') >>> a - b set(['a', 'b'])
集合A与集合B中所有不属于A∩B的元素的集合
>>> a = set('abc') >>> b = set('cdef') >>> a ^ b set(['a', 'b', 'd' 'e', 'd', 'f'])
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PGejd946-1620306934941)(assets/image-20191012180144081.png)]
>>> [x * x for x in range(1, 11)] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] # for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方: >>> [x * x for x in range(1, 11) if x % 2 == 0] [4, 16, 36, 64, 100] # 还可以使用两层循环,可以生成全排列 >>> [m + n for m in 'ABC' for n in 'XYZ'] ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ'] # 把一个list中所有的字符串变成小写 >>> L = ['Hello', 'World', 'IBM', 'Apple'] >>> [s.lower() for s in L] ['hello', 'world', 'ibm', 'apple']
迭代是访问集合元素的一种方式,如果给定一个list或tuple,我们可以通过for
循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
可以直接作用于for
循环的对象统称为可迭代对象:Iterable
。
可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。它们表示一个惰性计算的序列
生成器都是Iterator
对象,但list
、dict
、str
虽然是可迭代对象Iterable
,却不是迭代器Iterator
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数
可以直接作用于for
循环的数据类型有以下几种:
一类是集合数据类型,如list
、tuple
、dict
、set
、str
等;
一类是generator
,包括生成器和带yield
的generator function。
这些可以直接作用于for
循环的对象统称为可迭代对象:Iterable
。
可以使用isinstance()
判断一个对象是否是Iterable
对象
根据程序员制定的规则循环生成数据,当条件不成立时则生成数据结束。数据不是一次性全部生成出来,而是使用一个,再生成一个,可以节约大量的内存。
生成器推导式
只要把一个列表生成式的[]
改成()
,就创建了一个generator
>>> g = (x * x for x in range(10)) >>> g <generator object <genexpr> at 0x1022ef630> >>> next(g) 0 >>> next(g) 1 …… >>> next(g) 81 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
generator保存的是算法,每次调用next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。
也可使用for
循环,因为generator也是可迭代对象;我们创建了一个generator后,基本上永远不会调用next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。
yield 关键字
只要在def函数里面看到有 yield 关键字那么就是生成器
def get_value(n): for i in range(n): print('生成第一个值') # yield 关键字的作用是将这个函数变成一个生成器对象 # 执行时,解释器遇到 yield 后会中断代码的执行,并返回yield后的数据, # 下一次再执行时,会恢复前面yield中断的状态,继续执行 yield i print('第一个生成完成') g = get_value(4) value = next(g) print(value) value = next(g) print(value)
代码执行到 yield 会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
yield 就是保存当前程序执行状态
斐波那契数列
def fibonacci(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done' fib = fibonacci(5) # 遍历生成的数据 for value in fib: print(value)
(1)生成器:
生成器本质上就是一个函数,它记住了上一次返回时在函数体中的位置。
对生成器函数的第二次(或第n次)调用,跳转到函数上一次挂起的位置。
而且记录了程序执行的上下文。
生成器不仅“记住”了它的数据状态,生成还记住了程序执行的位置。(2)迭代器
迭代器是一种支持next()操作的对象。它包含了一组元素,当执行next()操作时,返回其中一个元素。
当所有元素都被返回后,再执行next()报异常—StopIteration
生成器一定是可迭代的,也一定是迭代器对象(3)区别:
①生成器是生成元素的,迭代器是访问集合元素的一中方式
②迭代输出生成器的内容
③迭代器是一种支持next()操作的对象
④迭代器(iterator):其中iterator对象表示的是一个数据流,可以把它看做一个有序序列,但我们不能提前知道序列的长度,只有通过nex()函数实现需要计算的下一个数据。可以看做生成器的一个子集。
如果在开发程序时,需要某块代码多次,但是为了提高编写的效率以及代码的重用,所以把具有独立功能的代码块组织为一个小模块,这就是函数
局部变量
全局变量
在函数外边定义的变量叫做全局变量
全局变量能够在所有的函数中进行访问
当函数内出现局部变量和全局变量相同名字时,函数内部中的 变量名 = 数据
此时理解为定义了一个局部变量,而不是修改全局变量的值
如果在函数中出现global 全局变量的名字
那么这个函数中即使出现和全局变量名相同的变量名 = 数据
也理解为对全局变量进行修改,而不是定义局部变量
# 可以使用一次global对多个全局变量进行声明 global a, b # 还可以用多次global声明都是可以的 # global a # global b
定义一个函数要使用def
语句,依次写出函数名、括号、括号中的参数和冒号:
,然后,在缩进块中编写函数体,函数的返回值用return
语句返回。
函数名也是变量
通过 函数名() 即可完成调用
定义时小括号中的参数,用来接收参数用的,称为 “形参”
调用时小括号中的参数,用来传递给函数用的,称为 “实参”
当全局变量和局部变量同名时,在函数内使用变量,优先使用局部变量;局部变量优先级高于全局变量
参数定义的顺序必须是:必选参数、默认参数、不定长位置参数和不定长关键字参数。
def f1(a, b, c=0, *args, **kwargs): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
对于任意函数,都可以通过类似func(*args, **kw)
的形式调用它,无论它的参数是如何定义的。
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
>>> def f(x): ... return x * x ... >>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> list(r) [1, 4, 9, 16, 25, 36, 49, 64, 81]
把这个list所有数字转为字符串:
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])) ['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数
reduce
把结果继续和序列的下一个元素做累积计算:
import functools my_list = [1, 2, 3, 4, 5] def f(x1, x2): return x1 + x2 result = functools.reduce(f, my_list) print(result) >>>15
当然求和运算可以直接用Python内建函数sum()
,没必要动用reduce
。
如果要把序列[1, 3, 5, 7, 9]
变换成整数13579
:
>>> from functools import reduce >>> def fn(x, y): ... return x * 10 + y ... >>> reduce(fn, [1, 3, 5, 7, 9]) 13579
和map()
类似,filter()
也接收一个函数和一个序列,不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
def is_odd(n): return n % 2 == 1 list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])) # 结果: [1, 5, 9, 15]
注意到filter()
函数返回的是一个Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()
函数获得所有结果并返回list。
Python内置的sorted()
函数就可以对list进行排序
此外,sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序,例如按绝对值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs) [5, 9, -12, -21, 36]
给sorted
传入key函数,即可实现忽略大小写的排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) ['about', 'bob', 'Credit', 'Zoo']
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
定义的函数没有名字,这样的函数叫做匿名函数.
格式:lambda [形参1], [形参2], … : [单行表达式] 或 [函数调用]
lambda定义和普通函数的区别:
return
,返回值就是该表达式的结果。定义简单的单行函数
my_function = lambda a, b: a + b
作为函数的参数进行传递
解决目标: 1、提高函数的通用性 2、减少代码量
例子
#现有字典 d={‘a’:24,‘g’:52,‘i’:12,‘k’:33}请按字典中的 value 值进行排序? sorted(d.items(),key = lambda x:x[1]) #一句话解决阶乘函数 reduce(lambda x,y: x*y, range(1,n+1))
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
# 定义一个外部函数 def func_out(num1): # 定义一个内部函数 def func_inner(num2): # 内部函数使用了外部函数的变量(num1) result = num1 + num2 print("结果是:", result) # 外部函数返回了内部函数,这里返回的内部函数就是闭包 return func_inner # 创建闭包实例 f = func_out(1) # 执行闭包 f(2) f(3) >>> 结果是: 3 结果是: 4
闭包可以保存外部函数内的变量,不会随着外部函数调用完而销毁。
闭包不仅可以保存外部函数的变量还可以提高代码的可重用行。
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
在内部函数中修改外部变量使用nonlocal 关键字
nonlocal num1 # 告诉解释器,此处使用的是 外部变量a # 修改外部变量num1 num1 = 10
给已有函数增加额外功能的函数,它本质上就是一个闭包函数。
# 装饰器 def decorator(fn): # fn:被装饰的目标函数. def inner(): '''执行函数之前''' fn() # 执行被装饰的目标函数 '''执行函数之后''' return inner
例子
# 添加一个登录验证的功能 def check(fn): print("装饰器函数执行了") def inner(): print("请先登录....") fn() return inner # 使用语法糖方式来装饰函数 @check def comment(): print("发表评论") comment() >>> 请先登录.... 发表评论
装饰带有参数和返回值的函数
# 添加输出日志的功能 def logging(fn): def inner(num1, num2): print("--正在努力计算--") result = fn(num1, num2) return result return inner # 使用装饰器装饰函数 @logging def sum_num(a, b): result = a + b return result result = sum_num(1, 2) print(result) >>> --正在努力计算-- 3
# 通用装饰器 def logging(fn): def inner(*args, **kwargs): print("--正在努力计算--") result = fn(*args, **kwargs) return result return inner
def make_div(func): """对被装饰的函数的返回值 div标签""" def inner(): return "<div>" + func() + "</div>" return inner def make_p(func): """对被装饰的函数的返回值 p标签""" def inner(): return "<p>" + func() + "</p>" return inner # 装饰过程: 1 content = make_p(content) 2 content = make_div(content) # content = make_div(make_p(content)) @make_div @make_p def content(): return "人生苦短" result = content() print(result) >>> <div><p>人生苦短</p></div>
使用装饰器装饰函数的时候可以传入指定参数,语法格式: @装饰器(参数,…)
写法:
在装饰器外面再包裹上一个函数,让最外面的函数接收参数,返回的是装饰器,因为@符号后面必须是装饰器实例。
# 添加输出日志的功能 def logging(flag): def decorator(fn): def inner(num1, num2): if flag == "+": print("--正在努力加法计算--") elif flag == "-": print("--正在努力减法计算--") result = fn(num1, num2) return result return inner # 返回装饰器 return decorator # 使用装饰器装饰函数 @logging("+") def add(a, b): result = a + b return result result = add(1, 2) print(result)
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
三大特征有:封装性、继承性、多态性。
面向对象编程的2个非常重要的概念:类和对象
类是抽象的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
类的构成
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
class Hero(object): """定义了一个英雄类,可以移动和攻击""" def move(self): """实例方法""" print("正在前往事发地点...") def attack(self): """实例方法""" print("发出了一招强力的普通攻击...") def info(self): """在类的实例方法中,通过self获取该对象的属性""" print("英雄 %s 的生命值 :%d" % (self.name, self.hp)) print("英雄 %s 的攻击力 :%d" % (self.name, self.atk)) print("英雄 %s 的护甲值 :%d" % (self.name, self.armor)) # 实例化了一个英雄对象 泰达米尔 taidamier = Hero() # 给对象添加属性,以及对应的属性值 taidamier.name = "泰达米尔" # 姓名 taidamier.hp = 2600 # 生命值 taidamier.atk = 450 # 攻击力 taidamier.armor = 200 # 护甲值 # 通过.成员选择运算符,获取对象的实例方法 taidamier.info() # 只需要调用实例方法info(),即可获取英雄的属性 taidamier.move() taidamier.attack()
两个下划线开始,两个下划线结束的方法,就是魔法方法,__init__()就是一个魔法方法,通常用来做属性初始化 或 赋值 操作。
__init__()
方法,在创建一个对象时默认被调用,不需要手动调用__init__(self)
中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去。如果在创建对象时传递了2个实参,那么__init__(self)
中出了self作为第一个形参外还需要2个形参,例如__init__(self,x,y)
用来显示信息,该方法需要 return 一个数据,并且只有self一个参数,当在类的外部 print(对象)
则打印这个数据
def __str__(self): return "英雄 <%s> 数据: 生命值 %d" % (self.name, self.hp)
__str__(self)
方法,那么就会打印从在这个方法中 return
的数据__str__
方法通常返回一个字符串,作为这个对象的描述信息__del__()
方法当删除对象时,python解释器也会默认调用
在属性名和方法名 前面 加上两个下划线 __
定义一个可以调用的公有方法,在这个公有方法内访问修改。
通常会定义get_xxx()方法和set_xxx()方法来获取和修改私有属性值。
set/get方法对私有属性操作时的好处:
class Master(object): def __init__(self): # 私有属性,可以在类内部通过self调用,但不能通过对象访问 self.__money = 10000 # 返回私有属性的值 def get_money(self): return self.__money # 接收参数,修改私有属性的值 def set_money(self, num): self.__money = num
class 子类名(父类名): pass
当发生继承后,子类会继承父类中的属性和方法,可以直接 使用
在子类中不能直接使用父类中的私有方法;通过继承得到的父类的公有方法,间接 执行父类的私有方法
因为子类提供了 init 方法后,那么在使用子类实例对象时,就会调用 子类自己 init 方法;如果想父类中的属性可以得到,需要执行父类中的init方法
格式:父类名.__init__(self,父类中需要属性参数列表)
子类继承父类,父类继承爷爷类,这就是多层继承
子类继承多个父类
如果子类和父类的方法名和属性名相同,则默认使用子类的
注意:如果多个父类中有同名的 属性和方法,则默认使用第一个父类的属性和方法(根据类的魔法属性mro的顺序来查找)
在多继承时,如果继承的多个类同时继承同一个父类,那么这时会出现初始化问题,这个共同父类会被初始化多次.
super()执行过程:
在 self 这个对象的所属类中,通过 mro 找到方法解析顺序
在顺序中,找当前类名的下一个类来初始化或查找方法
类名.__mro__
得到了一个元组,元组中的元素是当前类在继承关系上的一个顺序;
这个顺序不是我们确定的,是由在确定某个类的继承关系关系后,由解释器来确定这个顺序
多继承调用指定父类中方法
父类名.方法() super().方法() # 方法2. super() 带参数版本,只支持新式类 super(Prentice, self).__init__() # 执行父类的 __init__方法 # super(Prentice, self).make_cake() # self.make_cake() # 方法3. super()的简化版,只支持新式类 super().__init__() # 执行父类的 __init__方法 super().make_cake() # 执行父类的 实例方法 self.make_cake() # 执行本类的实例方法
在设计类的继承关系时,通常,主线都是单一继承下来的,但是,如果需要“混入”额外的功能,通过多重继承就可以实现。这种设计通常称之为MixIn。
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPServer, ForkingMixIn): pass
在需要使用父类对象的地方,也可以使用子类对象, 这种情况就叫多态.
比如, 在函数中,我需要调用 某一个父类对象的方法, 那么我们也可以在这个地方调用子类对象的方法.
类属性就是类对象
所拥有的属性,它被所有类对象
的实例对象
所共有,类对象和实例对象均可访问,在内存中只存在一个副本
类属性可以使用实例对象来引用,但是不能修改
一般情况下:类属性 都只使用 类对象 来调用
class People(object): name = 'Tom' # 公有的类属性 __age = 12 # 私有的类属性 p = People() print(p.name) # 正确 print(People.name) # 正确 print(p.__age) # 错误,不能在类外通过实例对象访问私有的类属性 print(People.__age) # 错误,不能在类外通过类对象访问私有的类属性
由于Python是动态语言,根据类创建的实例可以任意绑定属性。
给实例绑定属性的方法是通过实例变量,或者通过self
变量
class Student(object): def __init__(self, name): self.name = name s = Student('Bob') s.score = 90
是类对象所拥有的方法,需要用修饰器@classmethod
来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls
作为第一个参数
定义格式: @classmethod def 方法名(cls,...): pass 调用格式: 类对象.类方法名
注意:在类方法中,不能使用self,但是可以使用 cls,该参数用来表示 当前类对象,这个参数也是自动传递的
@classmethod 是一个装饰 器,用来修饰一个方法成为类方法,当在执行该 类方法时,解释 会自动 将类对象传递到参数 cls中
类方法还有一个用途就是可以对类属性进行修改
class People(object): country = 'china' #类方法,用classmethod来进行修饰 @classmethod def get_country(cls): return cls.country @classmethod def set_country(cls,country): cls.country = country
通过修饰器@staticmethod
来进行修饰,静态方法不需要多定义参数,可以通过对象和类来访问
格式: @staticmethod def 方法名(参数列表....): pass 调用方式: 同类方法 类对象.静态方法名()
property属性就是负责把一个方法当做属性进行使用,这样做可以简化代码使用。
定义property属性有两种方式
class Person(object): def __init__(self): self.__age = 0 # 装饰器方式的property, 把age方法当做属性使用, 表示当获取属性时会执行下面修饰的方法 @property def age(self): return self.__age # 把age方法当做属性使用, 表示当设置属性时会执行下面修饰的方法 @age.setter def age(self, new_age): if new_age >= 150: print("成精了") else: self.__age = new_age # 创建person p = Person() print(p.age) p.age = 100 print(p.age) p.age = 1000 >>> 0 100 成精了
@property
的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property
就可以了,此时,@property
本身又创建了另一个装饰器@age.setter
,负责把一个setter方法变成属性赋值
class Person(object): def __init__(self): self.__age = 0 def get_age(self): """当获取age属性的时候会执行该方法""" return self.__age def set_age(self, new_age): """当设置age属性的时候会执行该方法""" if new_age >= 150: print("成精了") else: self.__age = new_age # 类属性方式的property属性 age = property(get_age, set_age) # 创建person p = Person() print(p.age) p.age = 100 print(p.age) p.age = 1000 >>> 0 100 成精了
property的参数说明:
# 数据 (属性) # 区别: 类的每个对象(实例) 对于这个数据是独有的还是共享的,是不同的还是相同的 # 1. 对象属性 (实例属性) # 类的每个对象 这个名字的属性数据 是独有的 ,每个对象不同 # 2. 类属性 # 类的每个对象 这个名字的属性数据 是共享,每个对象都相同 # 函数(方法) # 区别:方法中能够直接使用的属性数据不同 # 1. 对象方法(实例方法) 可以直接读写对象属性,可以直接读类属性 # def obj_func(self): # self. # 2. 类方法 # @classmethod 可以直接读写类属性 # def class_func(cls, ..): # cls. # 3. 静态方法 # @staticmethod 虽然可以通过类名操作类属性,但是我们可以认为 不是直接操作属性 # def static_func(): # 类名. # 选择:如果定义一个函数,这个函数中需要使用对象属性,定义对象方法 # 如果定义一个函数,这个函数中仅需要使用类属性,定义类方法 # 如果定义一个函数,这个函数不需要使用类的任何属性,从逻辑的角度考虑 应该是类中的一个处理方法,此时定义静态方法即可
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
r -> read text; file = open('a.txt','r') 以文本方式打开文件读,文件存在,打开成功,文件不存在,打开失败 w -> write text; file = open('a.txt','w') 以文本方式打开文件写,不管文件是否存在,都会新创建一个新文件 a -> append text ;file = open('a.txt','w') 以文件方式打开文件追加,文件不存在,创建文件,文件存在,那么打开文件然后将光标移动到文件的最后
rb 以二进制形式打开文件读取 wb 以二进制形式打开文件写入 ab 以二进制形式打开文件追加
with语句
# 1、以写的方式打开文件 with open("1.txt", "w") as f: # 2、读取文件内容 f.write("hello world")
file = open('a.txt','rt') content = file.read() file.close()
while True: # 读取 content = file.read(4096) #从文件中读取的数据的长度(单位是字节) # 如果在文件读取时,读取的结果为空串,说明文件读取完毕 # 根据这个条件 可以设置读取文件的结束条件 if content == '': break print(content,end='') # 关闭文件 file.close()
write()可以完成向文件写入数据
f = open('test.txt', 'w') f.write('hello world, i am here!') f.close()
如果文件不存在那么创建
导入 os 模块
os.rename(需要修改的文件名, 新的文件名)
import os os.rename("毕业论文.txt", "毕业论文-最终版.txt")
os.remove(待删除的文件名)
os.mkdir(“张三”)创建文件夹;如果当前目录 存在,会报错
os.getcwd()获取当前目录
os.chdir("…/指定路径")改变当前目录 到指定 的路径 上去
os.listdir("./")获取目录下的文件名称,存在列表中
file_list = os.listdir('.') print(file_list) for file in file_list: print(file)
os.rmdir(‘路径’) 删除一个空文件夹,当目录文件夹不为空,不能删除
模块是一组Python代码的集合,可以使用其他模块,也可以被其他模块使用。
在Python中用关键字import
来引入某个模块,比如要引用模块math,就可以在文件最开始的地方用import math来引入
在调用math模块中的函数时,必须这样引用:
模块名.函数名
Python的from语句让你从模块中导入一个指定的部分到当前命名空间中,此时可以用下面方法实现:
from 模块名 import 函数名1,函数名2....
编写一个hello
的模块:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- ' a test module ' __author__ = 'Michael Liao' import sys def test(): args = sys.argv if len(args)==1: print('Hello, world!') elif len(args)==2: print('Hello, %s!' % args[1]) else: print('Too many arguments!') if __name__=='__main__': test()
第1行和第2行是标准注释,第1行注释可以让这个hello.py
文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用__author__
变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。
当你导入一个模块,Python解析器对模块位置的搜索顺序是:
在Python中,安装第三方模块,是通过包管理工具pip完成的。
第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者pypi上搜索
当Python检测到一个错误时,解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的"异常"
try: 可能会出现异常问题的代码 except Exception as e: 当出现异常时,解决异常的代码 else: 当没有出现异常时,正常执行的代码 finally: 无论是否出现异常,都会执行这里的代码
格式: class 异常名Error(Exception): def __init__(self,msg=''): self.__msg = msg def __str__(self): return self.__msg class CustomError(Exception): pass
断言就是判断一个函数或对象的一个方法所产生的结果是否符合你期望的那个结果。 python中assert断言是声明布尔值为真的判定,如果表达式为假会发生异常。单元测试中,一般使用assert来断言结果。
def foo(s): n = int(s) assert n != 0, 'n is zero!' # 表达式为假,逗号后为自定义异常;也可不定义 return 10 / n def main(): foo('0')
如果断言失败,assert
语句本身就会抛出AssertionError
:
程序中如果到处充斥着assert
,和print()
相比也好不到哪去。不过,启动Python解释器时可以用-O(是字母O)
参数来关闭assert,关闭后,你可以把所有的assert语句当成pass来看。
:
$ python -O err.py
常用的断言方法:
常用的断言方法: assertEqual 如果两个值相等,则pass assertNotEqual 如果两个值不相等,则pass assertTrue 判断bool值为True,则pass assertFalse 判断bool值为False,则pass assertIsNone 不存在,则pass assertIsNotNone 存在,则pass
logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等;相比print,具备如下优点:
import logging logging.basicConfig(level=logging.INFO) s = '0' n = int(s) logging.info('n = %d' % n) print(10 / n)
记录信息的级别
logging.basicConfig函数各参数:
filename:指定日志文件名;
filemode:和file函数意义相同,指定日志文件的打开模式,‘w’或者’a’;
format:指定输出的格式和内容,format可以输出很多有用的信息,
参数:作用 %(levelno)s:打印日志级别的数值 %(levelname)s:打印日志级别的名称 %(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0] %(filename)s:打印当前执行程序名 %(funcName)s:打印日志的当前函数 %(lineno)d:打印日志的当前行号 %(asctime)s:打印日志的时间 %(thread)d:打印线程ID %(threadName)s:打印线程名称 %(process)d:打印进程ID %(message)s:打印日志信息
输出日志
import logging # 引入logging模块 import os.path import time # 第一步,创建一个logger logger = logging.getLogger() logger.setLevel(logging.INFO) # Log等级总开关 # 第二步,创建一个handler,用于写入日志文件 rq = time.strftime('%Y%m%d%H%M', time.localtime(time.time())) log_path = os.path.dirname(os.getcwd()) + '/Logs/' log_name = log_path + rq + '.log' logfile = log_name fh = logging.FileHandler(logfile, mode='w') fh.setLevel(logging.DEBUG) # 输出到file的log等级的开关 # 第三步,定义handler的输出格式 formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s") fh.setFormatter(formatter) # 第四步,将logger添加到handler里面 logger.addHandler(fh) # 日志 logger.debug('this is a logger debug message') logger.info('this is a logger info message') logger.warning('this is a logger warning message') logger.error('this is a logger error message') logger.critical('this is a logger critical message')
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
import unittest class TestClass(unittest.TestCase): #该方法会首先执行,相当于做测试前的准备工作 def setUp(self): pass #该方法会在测试代码执行完后执行,相当于做测试后的扫尾工作 def tearDown(self): pass #测试代码 def test_app_exists(self): pass
运行单元测试
一旦编写好单元测试,我们就可以运行单元测试。最简单的运行方式是在mydict_test.py
的最后加上两行代码:
if __name__ == '__main__': unittest.main()
这样就可以把mydict_test.py
当做正常的python脚本运行:
$ python mydict_test.py
另一种方法是在命令行通过参数-m unittest
直接运行单元测试,这是推荐的做法:
$ python -m unittest mydict_test ..... ---------------------------------------------------------------------- Ran 5 tests in 0.000s OK