目录
面向对象高级编程
使用__slots__
动态给实例绑定属性:
class Student(object):
pass
s=Student()
# 动态给实例绑定一个属性
s.name='wangteng'
print(s.name) #wangteng
给实例绑定一个方法:
#定义函数作为实例方法:
def set_age(self,age):
self.age=age
from types import MethodType
#给实例绑定一个方法:
s.set_age=MethodType(set_age,s)
s.set_age(25)
print(s.age) #25
s2=Student()
s2.set_age(25) #AttributeError: 'Student' object has no attribute 'set_age'
print(s2.age)
注意:一个实例绑定的方法,对另一个实例是不起作用的。
为了给所有实例都绑定方法,可以给class绑定方法:
def set_chengji(self,chengji):
self.chengji=chengji
Student.set_chengji=set_chengji
s.set_chengji(100)
print(s.chengji) #100
s2.set_chengji(99)
print(s2.chengji) #99
使用__slots__:
Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class实例能添加的属性:
class xuesheng(object):
__slots__ = ('name','age')
#创建实例
x1=xuesheng()
#绑定属性’name‘
x1.name='wt'
#绑定属性’age‘
x1.age='25'
#绑定属性’chengji‘
x1.chengji=99
'''
输出结果:
Traceback (most recent call last):
File "E:/pythonproject/pythonProject/面向对象高级编程/01使用__slots__.py", line 40, in <module>
x1.chengji=99
AttributeError: 'xuesheng' object has no attribute 'chengji'
'''
注意:__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
class xuesheng1(xuesheng):
pass
xx1=xuesheng1()
xx1.chengji=9999
print(xx1.chengji) #9999
除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。
使用@property
class changfangxing:
def __init__(self,length,width):
self.length=length
self.width=width
self.mianji=self.width*self.length
cfx=changfangxing(5,2)
print(cfx.width) #2
print(cfx.length) #5
print(cfx.mianji) #10
cfx.mianji=100
print(cfx.mianji) #100
此时的面积暴露在外面,可以任意更改。例如面积更改为100.
可以采用get方法但是太麻烦,因为是一个方法,不能当成一个属性进行调用:
class changfangxing:
def __init__(self,length,width):
self.length=length
self.width=width
def get_mianji(self):
return self.width*self.length
cfx=changfangxing(5,2)
print(cfx.width) #2
print(cfx.length) #5
print(cfx.get_mianji()) #10
一个类里定义的方法一旦被@property修饰,可以像使用属性一样去使用这个方法:
class changfangxing:
def __init__(self,length,width):
self.length=length
self.width=width
@property
def mianji(self):
return self.width*self.length
cfx=changfangxing(5,2)
print(cfx.width) #2
print(cfx.length) #5
print(cfx.mianji) #10
mianj虽然是一个方法,但是可以当成属性来使用,调用的时候后面不用加(),也不用担心对其赋值,因为赋值会报错:
class changfangxing:
def __init__(self,length,width):
self.length=length
self.width=width
@property
def mianji(self):
return self.width*self.length
cfx=changfangxing(5,2)
cfx.mianji=100
报错:
Traceback (most recent call last):
File "E:/pythonproject/pythonProject/面向对象高级编程/02 使用@property.py", line 40, in <module>
cfx.mianji=100
AttributeError: can't set attribute
多重继承
继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。
实例:C继承了A,B:
class A(object):
pass
class B(object):
pass
class C(A,B):
pass
多重继承这个名词一般用来形容继承链条可以很长,多个层次。多继承则指一个类可以有多个基类,相反则是单继承。任何面向对象编程语言都支持多重继承,但像java这种只能通过接口实现有限程度的多继承。
mixin设计模式:
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,苹果 继承自 水果。但是,如果需要“混入”额外的功能(例如是南方水果还是北方水果),通过多重继承就可以实现,比如,让苹果 除了继承自水果外,再同时继承北方水果。这种设计通常称之为MixIn。
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
多继承就是mixin模式。
看不懂可以看一下视频:mixin设计模式的应用(多继承应用场景)
定制类
str()
str()返回用户看到的字符串
class Student(object):
def __init__(self, name):
self.name = name
print(Student('Michael'))
输出结果:
C:\ProgramData\Anaconda3\python.exe "E:/pythonproject/pythonProject/面向对象高级编程/04 定制类.py"
<__main__.Student object at 0x000002E902E48400>
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
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):
return self # 实例本身就是迭代对象,故返回自己
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():
print(n)
使用枚举类
为月份这样的枚举类型定义一个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():
print(name, '=>', member, ',', member.value)
'''
输出结果:
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12
'''
print(Month.Feb.value) #2
value
属性则是自动赋给成员的int
常量,默认从1
开始计数。
如果需要更精确地控制枚举类型,可以从Enum
派生出自定义类:
#从Enum派生出自定义类:
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
print(Weekday.Sun) #Weekday.Sun
print(Weekday.Sun.value) #0
print(Weekday(2)) #Weekday.Tue
@unique装饰器可以帮助我们检查保证没有重复值。
由此可见,既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。
使用元类
type()
type()函数可以查看一个类型或变量的类型:
class Hello(object):
def hello(self, name='world'):
print('Hello, %s.' % name)
from 面向对象高级编程.Hello import Hello
h=Hello()
print(h.hello('wt'))
'''
输出结果:
Hello, wt.
None
'''
print(type(Hello())) #<class '面向对象高级编程.Hello.Hello'>
print(type(Hello)) #<class 'type'>
print(type(h)) #<class '面向对象高级编程.Hello.Hello'>
Hello是一个class,它的类型是type,h是一个实例,它的类型就是class hello。
type()函数既可以返回一个对象的类型,又可以创建出新的类型
type()函数创建出Hello类:
def fn(self,name='world'): # 先定义函数
print('hello,%s'%name)
Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
h = Hello()
print(h.hello())
'''
输出结果:
hello,world
None
'''
要创建一个class对象,type()
函数依次传入3个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数
fn
绑定到方法名hello。
metaclass
metaclass,直译为元类,一般不会用到。
先定义metaclass,就可以创建类,最后创建实例。
metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
错误、调试和测试
错误处理
try…except…finally…
try:
print('try...')
r = 10 / 0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')
'''
执行结果:
try...
except: division by zero
finally...
END
'''
先执行try的代码段,若try的的代码段里执行出错,后续代码不会执行,直接跳转至错误处理代码,except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。
若try的的代码段里没有错误发生,except语句块不会被执行,但是finally如果有,则一定会被执行(可以没有finally语句)。
Python所有的错误都是从BaseException类派生的,常见的错误类型和继承关系看这里:
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
调用栈
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
bar('0')
main()
'''
执行结果:
Traceback (most recent call last): #错误的跟踪信息
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/01 错误处理.py", line 27, in <module>
main() #调用main()出错了,原因在第27行
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/01 错误处理.py", line 25, in main
bar('0') #调用 bar('0')出错了,原因在25行
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/01 错误处理.py", line 22, in bar
return foo(s) * 2 #return foo(s) * 2 语句出错了,原因在22行
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/01 错误处理.py", line 19, in foo
return 10 / int(s) # return 10 / int(s)出错了,这是错误产生的源头,因为下面打印了:
ZeroDivisionError: division by zero
'''
出错的时候,一定要分析错误的调用栈信息,才能定位错误的位置。
记录错误
Python内置的logging
模块可以非常容易地记录错误信息,同时程序会继续执行下去:
import logging
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
logging.exception(e)
main()
print('END')
'''
输出结果:
ERROR:root:division by zero
Traceback (most recent call last):
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/01 错误处理.py", line 53, in main
bar('0')
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/01 错误处理.py", line 49, in bar
return foo(s) * 2
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/01 错误处理.py", line 46, in foo
return 10 / int(s)
ZeroDivisionError: division by zero
END
'''
抛出错误:
如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise
语句抛出一个错误的实例:
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
foo('0')
'''
执行结果:
Traceback (most recent call last):
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/01 错误处理.py", line 81, in <module>
foo('0')
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/01 错误处理.py", line 78, in foo
raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0
'''
调试
用print(),把有可能的变量打印出来,坏处是要删除很多print(),运行结果包含很多垃圾信息。
断言:
凡是用print()
来辅助查看的地方,都可以用断言(assert)来替代:
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
foo('0')
'''
运行结果:
Traceback (most recent call last):
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/02 调试.py", line 6, in <module>
foo('0')
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/02 调试.py", line 3, in foo
assert n != 0, 'n is zero!'
AssertionError: n is zero!
'''
如果断言失败,assert语句本身就会抛出AssertionError。
程序中如果到处充斥着assert
,和print()
相比也好不到哪去。不过,启动Python解释器时可以用-O
参数来关闭assert:
$ python -O err.py
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
注意:断言的开关“-O”是英文大写字母O,不是数字0。
logging
logging不会抛出错误,而且可以输出到文件:
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
'''
输出结果:
INFO:root:n = 0
Traceback (most recent call last):
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/02 调试.py", line 24, in <module>
print(10 / n)
ZeroDivisionError: division by zero
'''
logging的好处,它允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=
info时,logging.
debug就不起作用了。同理,指定level=
warning后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。
虽然用IDE调试起来比较方便,但是最后你会发现,logging才是终极武器。
单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
小结:
- 单元测试可以有效地测试某个程序模块的行为,是未来重构代码的信心保证。
- 单元测试的测试用例要覆盖常用的输入组合、边界条件和异常。
- 单元测试代码要非常简单,如果测试代码太复杂,那么测试代码本身就可能有bug。
- 单元测试通过了并不意味着程序就没有bug了,但是不通过程序肯定有bug。
文档测试
文档测试(doctest)模块可以直接提取注释中的代码并执行测试,只有在命令行下才能直接运行。
用测试文档来测试编写的Dict类:
class Dict(dict):
'''
Simple dict but also support access as x.y style.
>>> d1 = Dict()
>>> d1['x'] = 100
>>> d1.x
100
>>> d1.y = 200
>>> d1['y']
200
>>> d2 = Dict(a=1, b=2, c='3')
>>> d2.c
'3'
>>> d2['empty']
Traceback (most recent call last):
...
KeyError: 'empty'
>>> d2.empty
Traceback (most recent call last):
...
AttributeError: 'Dict' object has no attribute 'empty'
'''
def __init__(self, **kw):
super(Dict, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
if __name__=='__main__':
import doctest
doctest.testmod()
没有输出,说明编写的是正确的,如果程序有问腿,会报错,例如:把__getatter()__方法注释,会报以下错误:
<re.Match object; span=(3, 6), match='def'>
def
**********************************************************************
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/04 文档测试.py", line 13, in __main__.Dict
Failed example:
d1.x
Exception raised:
Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\doctest.py", line 1336, in __run
exec(compile(example.source, filename, "single",
File "<doctest __main__.Dict[2]>", line 1, in <module>
d1.x
AttributeError: 'Dict' object has no attribute 'x'
**********************************************************************
File "E:/pythonproject/pythonProject/python/07 错误、调试和测试/04 文档测试.py", line 19, in __main__.Dict
Failed example:
d2.c
Exception raised:
Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\doctest.py", line 1336, in __run
exec(compile(example.source, filename, "single",
File "<doctest __main__.Dict[6]>", line 1, in <module>
d2.c
AttributeError: 'Dict' object has no attribute 'c'
**********************************************************************
1 items had failures:
2 of 9 in __main__.Dict
***Test Failed*** 2 failures.
当模块正常导入时,doctest不会被执行。只有在命令行直接运行时,才执行doctest。所以,不必担心doctest会在非测试环境下执行。
IO编程
文件读写
读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。
读文件
open() 函数用于创建或打开指定文件,该函数的常用语法格式如下:
file = open(file_name [, mode='r' [ , buffering=-1 [ , encoding = None ]]])
此格式中,用 [] 括起来的部分为可选参数,即可以使用也可以省略。其中,各个参数所代表的含义如下:
- file:表示要创建的文件对象。
- file_name:要创建或打开文件的文件名称,该名称要用引号(单引号或双引号都可以)括起来。需要注意的是,如果要打开的文件和当前执行的代码文件位于同一目录,则直接写文件名即可;否则,此参数需要指定打开文件所在的完整路径。
- mode:可选参数,用于指定文件的打开模式。可选的打开模式如表 1 所示。如果不写,则默认以只读(r)模式打开文件。
- buffering:可选参数,用于指定对文件做读写操作时,是否使用缓冲区(本节后续会详细介绍)。
- encoding:手动设定打开文件时所使用的编码格式,不同平台的 ecoding 参数值也不同,以 Windows 为例,其默认为 cp936(实际上就是 GBK 编码)。
open() 函数支持的文件打开模式如表 1 所示。
表 1 open 函数支持的文件打开模式
模式 | 意义 | 注意事项 |
---|---|---|
r | 只读模式打开文件,读文件内容的指针会放在文件的开头。 | 操作的文件必须存在。 |
rb | 以二进制格式、采用只读模式打开文件,读文件内容的指针位于文件的开头,一般用于非文本文件,如图片文件、音频文件等。 | |
r+ | 打开文件后,既可以从头读取文件内容,也可以从开头向文件中写入新的内容,写入的新内容会覆盖文件中等长度的原有内容。 | |
rb+ | 以二进制格式、采用读写模式打开文件,读写文件的指针会放在文件的开头,通常针对非文本文件(如音频文件)。 | |
w | 以只写模式打开文件,若该文件存在,打开时会清空文件中原有的内容。 | 若文件存在,会清空其原有内容(覆盖文件);反之,则创建新文件。 |
wb | 以二进制格式、只写模式打开文件,一般用于非文本文件(如音频文件) | |
w+ | 打开文件后,会对原有内容进行清空,并对该文件有读写权限。 | |
wb+ | 以二进制格式、读写模式打开文件,一般用于非文本文件 | |
a | 以追加模式打开一个文件,对文件只有写入权限,如果文件已经存在,文件指针将放在文件的末尾(即新写入内容会位于已有内容之后);反之,则会创建新文件。 | |
ab | 以二进制格式打开文件,并采用追加模式,对文件只有写权限。如果该文件已存在,文件指针位于文件末尾(新写入文件会位于已有内容之后);反之,则创建新文件。 | |
a+ | 以读写模式打开文件;如果文件存在,文件指针放在文件的末尾(新写入文件会位于已有内容之后);反之,则创建新文件。 | |
ab+ | 以二进制模式打开文件,并采用追加模式,对文件具有读写权限,如果文件存在,则文件指针位于文件的末尾(新写入文件会位于已有内容之后);反之,则创建新文件。 |
调用close()
方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的。
Python引入了with
语句来自动帮我们调用close()
方法:
with open('/path/to/file', 'r') as f:
print(f.read())
按行读写:
for line in f.readlines():
print(line.strip()) # 把末尾的'\n'删掉
二进制文件:
要读取二进制文件,比如图片、视频等等,用'rb'
模式打开文件即可:
f = open('/Users/michael/test.jpg', 'rb')
f.read()
'''
输出结果:
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节
'''
遇到有些编码不规范的文件,你可能会遇到UnicodeDecodeError
,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()
函数还接收一个errors
参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:
f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')
写文件:
写文件和读文件是一样的,唯一区别是调用open()
函数时,传入标识符'w'
或者'wb'
表示写文本文件或写二进制文件:
f = open('/Users/michael/test.txt', 'w')
f.write('Hello, world!')
f.close()
#用with:
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')
以'w'
模式写入文件时,如果文件已存在,会直接覆盖(相当于删掉后新写入一个文件)。如果我们希望追加到文件末尾怎么办?可以传入'a'
以追加(append)模式写入。
StringIO和BytesIO
StringIO:
StringIO顾名思义就是在内存中读写str。
要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:
from io import StringIO
f=StringIO()
f.write('hello')
f.write(' ')
f.write('world')
print(f.getvalue())
#输出结果:
hello world
#方法二:像读文件一样读取:
from io import StringIO
f=StringIO('hello!\nhi!\nwangteng!')
while True:
s=f.readline()
if s=='':
break
print(s.strip())
#输出结果:
hello!
hi!
wangteng!
getvalue()方法用于获得写入后的str。
BytesIO:
StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。
from io import BytesIO
f=BytesIO()
f.write('中文'.encode('utf-8'))
print(f.getvalue())
#输出结果:b'\xe4\xb8\xad\xe6\x96\x87'
#方法二:像读取文件一样读取:
from io import BytesIO
f=BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
f.read()
#输出结果:b'\xe4\xb8\xad\xe6\x96\x87'
注意:写入的不是str,而是经过UTF-8编码的bytes。
操作文件和目录
import os
print(os.name) #操作系统类型
#输出结果:nt 代表是windows系统
环境变量
在操作系统中定义的环境变量,全部保存在os.environ这个变量中,可以直接查看:
print(os.environ)
#输出结果:
#environ({'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\13108\\AppData\\Roaming', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 'COMPUTERNAME': '豚豚', 'COMSPEC': 'C:\\Windows\\system32\\cmd.exe', 'CONDA_DEFAULT_ENV': 'base', 'CONDA_PREFIX': 'C:\\ProgramData\\Anaconda3', 'CONDA_PROMPT_MODIFIER': '(base) ', 'CONDA_SHLVL': '1', 'DRIVERDATA': 'C:\\Windows\\System32\\Drivers\\DriverData', 'FPS_BROWSER_APP_PROFILE_STRING': 'Internet Explorer', 'FPS_BROWSER_USER_PROFILE_STRING': 'Default', 'HOMEDRIVE': 'C:', 'HOMEPATH': '\\Users\\13108', 'IDEA_INITIAL_DIRECTORY': 'C:\\Users\\13108\\Desktop', 'LOCALAPPDATA': 'C:\\Users\\13108\\AppData\\Local', 'LOGONSERVER': '\\\\豚豚', 'NUMBER_OF_PROCESSORS': '12', 'ONEDRIVE': 'C:\\Users\\13108\\OneDrive', 'OS': 'Windows_NT', 'PATH': 'C:\\ProgramData\\Anaconda3;C:\\ProgramData\\Anaconda3\\Library\\mingw-w64\\bin;C:\\ProgramData\\Anaconda3\\Library\\usr\\bin;C:\\ProgramData\\Anaconda3\\Library\\bin;C:\\ProgramData\\Anaconda3\\Scripts;C:\\ProgramData\\Anaconda3\\bin;C:\\ProgramData\\Anaconda3\\condabin;C:\\ProgramData\\Anaconda3;C:\\ProgramData\\Anaconda3\\Library\\mingw-w64\\bin;C:\\ProgramData\\Anaconda3\\Library\\usr\\bin;C:\\ProgramData\\Anaconda3\\Library\\bin;C:\\ProgramData\\Anaconda3\\Scripts;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0;C:\\Windows\\System32\\OpenSSH;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;C:\\Program Files\\NVIDIA Corporation\\NVIDIA NvDLISR;D:\\杞\ue219欢\\Bandizip;C:\\Users\\13108\\AppData\\Local\\Microsoft\\WindowsApps;.;C:\\Users\\13108\\AppData\\Local\\Programs\\Microsoft VS Code\\bin', 'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC', 'PROCESSOR_ARCHITECTURE': 'AMD64', 'PROCESSOR_IDENTIFIER': 'AMD64 Family 25 Model 80 Stepping 0, AuthenticAMD', 'PROCESSOR_LEVEL': '25', 'PROCESSOR_REVISION': '5000', 'PROGRAMDATA': 'C:\\ProgramData', 'PROGRAMFILES': 'C:\\Program Files', 'PROGRAMFILES(X86)': 'C:\\Program Files (x86)', 'PROGRAMW6432': 'C:\\Program Files', 'PROMPT': '(base) $P$G', 'PSMODULEPATH': 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules', 'PUBLIC': 'C:\\Users\\Public', 'PYCHARM_HOSTED': '1', 'PYTHONIOENCODING': 'UTF-8', 'PYTHONPATH': 'E:\\pythonproject\\pythonProject', 'PYTHONUNBUFFERED': '1', 'SESSIONNAME': 'Console', 'SYSTEMDRIVE': 'C:', 'SYSTEMROOT': 'C:\\Windows', 'TEMP': 'C:\\Users\\13108\\AppData\\Local\\Temp', 'TMP': 'C:\\Users\\13108\\AppData\\Local\\Temp', 'USERDOMAIN': '豚豚', 'USERDOMAIN_ROAMINGPROFILE': '豚豚', 'USERNAME': '13108', 'USERPROFILE': 'C:\\Users\\13108', 'WINDIR': 'C:\\Windows'})
获取某个环境变量的值:
print(os.environ.get('PATH'))
#输出结果:
#C:\ProgramData\Anaconda3;C:\ProgramData\Anaconda3\Library\mingw-w64\bin;C:\ProgramData\Anaconda3\Library\usr\bin;C:\ProgramData\Anaconda3\Library\bin;C:\ProgramData\Anaconda3\Scripts;C:\ProgramData\Anaconda3\bin;C:\ProgramData\Anaconda3\condabin;C:\ProgramData\Anaconda3;C:\ProgramData\Anaconda3\Library\mingw-w64\bin;C:\ProgramData\Anaconda3\Library\usr\bin;C:\ProgramData\Anaconda3\Library\bin;C:\ProgramData\Anaconda3\Scripts;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Windows\System32\OpenSSH;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;D:\杞欢\Bandizip;C:\Users\13108\AppData\Local\Microsoft\WindowsApps;.;C:\Users\13108\AppData\Local\Programs\Microsoft VS Code\bin
print(os.environ.get('x','default'))
# 输出结果:default
操作文件和目录
#查看当前目录的绝对路径:
print(os.path.abspath('.')) #E:\pythonproject\pythonProject\python\08 IO编程
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
print(os.path.join('E:/pythonproject/pythonProject/python/08 IO编程','testdir'))
#输出结果:E:/pythonproject/pythonProject/python/08 IO编程\testdir
# 然后创建一个目录:
os.mkdir('E:/pythonproject/pythonProject/python/08 IO编程/testdir')
#删除一个目录:
os.rmdir('E:/pythonproject/pythonProject/python/08 IO编程/testdir')
#拆分路径:
print(os.path.split('E:/pythonproject/pythonProject/python/08 IO编程/testdir/file.txt'))
#输出结果:('E:/pythonproject/pythonProject/python/08 IO编程/testdir', 'file.txt')
#得到文件扩展名:
print(os.path.splitext('E:/pythonproject/pythonProject/python/08 IO编程/testdir/file.txt'))
#输出结果:('E:/pythonproject/pythonProject/python/08 IO编程/testdir/file', '.txt')
#对文件重命名:
os.renames('test','test.py')
#删除文件:
os.remove('test.py')
#列出当前目录下的所有目录:
print([x for x in os.listdir('.') if os.path.isdir(x)])
#输出结果:['testdir']
#列出所有的.py文件:
print([x for x in os.listdir('.') if os.path.isfile(x)and os.path.splitext(x)[1]=='.py'])
#输出结果:['01 文件读写.py', '02 StringIo.py', '03 操作文件和目录.py', '__init__.py']
序列化
把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling。
#把一个对象序列化写入文件:
import pickle
d= dict(name='wangteng',age=26 ,score=98)
# pickle.dumps() 方法是把任意对象序列化成一个bytes,然后把bytes写入文件
print(pickle.dumps(d))
#输出结果:b'\x80\x04\x95)\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x04name\x94\x8c\x08wangteng\x94\x8c\x03age\x94K\x1a\x8c\x05score\x94Kbu.'
#另一种方法:pickle.dump()直接把对象序列化后写入一个file-like Object:
f=open('dump.text','wb')
pickle.dump(d,f)
f.close()
f=open('dump.text','rb')
d=pickle.load(f) #反序列化出对象
f.close()
print(d)
#输出结果:{'name': 'wangteng', 'age': 26, 'score': 98}
#将python对象编程JSON:
import json
d=dict(name='wangteng',age=20,chengji=89)
print(json.dumps(d))
#输出结果:{"name": "wangteng", "age": 20, "chengji": 89}
#将JSON反序列化为python对象:
json_str='{"name":"wangteng","age":20,"chengji":89}'
print(json.loads(json_str))
#输出结果:{'name': 'wangteng', 'age': 20, 'chengji': 89}
#json的进阶:
import json
class student(object):
def __init__(self,name,age,chengji):
self.name=name
self.age=age
self.chengji=chengji
s=student('wangteng',20,88)
#print(json.dumps(s))
def student2dict(std):
return {
'name':std.name,
'age':std.age,
'chengji':std.chengji
}
print(json.dumps(s,default=student2dict))
#输出结果:{"name": "wangteng", "age": 20, "chengji": 88}
进程和线程
进程:是计算机资源分配的最小单位(进程为线程提供资源)。
线程:是计算机中可以被cpu调度的最小单位(真正在工作)。
一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源。
- 计算密集型:用多进程,例如:大量的数据计算。
- IO密集型:用多线程,例如:文件读写,网络数据传输。
多进程
多线程
多线程和多进程的时间比较:
import time
import requests
url_list=[
("线程:死锁.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=7&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca"),
("线程:总结和作业.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=10&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca"),
("进程:今日概要.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=12&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca")
]
print(time.time())
for file_name,url in url_list:
res = requests.get(url)
with open(file_name,mode='wb') as f:
f.write(res.content)
print(file_name,time.time())
'''
1655949667.968719
线程:死锁.mp4 1655949668.308015
线程:总结和作业.mp4 1655949668.9305954
进程:今日概要.mp4 1655949669.2900176
'''
#使用多线程:
import threading,time,requests
url_list=[
("线程:死锁.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=7&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca"),
("线程:总结和作业.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=10&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca"),
("进程:今日概要.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=12&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca")
]
def task(file_name,url):
res=requests.get(url)
with open(file_name,'wb') as f:
f.write(res.content)
print(time.time())
print(time.time())
for name,url in url_list:
# 创建线程,让每个线程都去执行task函数(参数不同)
t=threading.Thread(target=task,args=(name,url))
t.start()
'''
输出结果:
1655971437.7154496
1655971438.0637615
1655971438.0908408
1655971438.095749
'''
#使用多进程:
import time,requests,multiprocessing
url_list=[
("线程:死锁.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=7&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca"),
("线程:总结和作业.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=10&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca"),
("进程:今日概要.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=12&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca")
]
def task(file_name,url):
res=requests.get(url)
with open(file_name,'wb') as f:
f.write(res.content)
print(time.time())
if __name__ == '__main__':
print(time.time())
for name,url in url_list:
# 创建进程
#Linux系统fork;win:spawn;mac支持:fork,spawn(python3.8默认设置spawn)
t=multiprocessing.Process(target=task,args=(name,url))
t.start()
'''
输出结果:
1655971844.1602507
1655971844.7430584
1655971844.7451484
1655971844.776326
'''
线程常见的方法:
- t.start(),当前进程准备就绪(等待cpu调度,具体时间由cpu决定)。
import threading
loop=10000
number=0
def _add(count):
global number
for i in range(count):
number+=1
t = threading.Thread(target=_add,args=(loop,))
t.start()
print(number)
'''
输出结果:
10000
'''
- t.join(),等待当前进程的任务执行完毕后再向下继续执行。
import threading
number=0
def _add():
global number
for i in range(1000000):
number+=1
def _sub():
global number
for i in range(1000000):
number-=1
t1=threading.Thread(target=_add)
t2=threading.Thread(target=_sub)
t1.start()
t1.join() #t1线程执行完毕后,才继续往后走
t2.start()
t2.join() #t2线程执行完毕后,才继续往后走
print(number)
'''
输出结果:0
'''
import threading
loop =10000000
number=0
def _add(count):
global number
for i in range(count):
number+=1
def _sub(count):
global number
for i in range(count):
number-=1
t1=threading.Thread(target=_add,args=(loop,))
t2=threading.Thread(target=_sub,args=(loop,))
t1.start()
t2.start()
t1.join() #t1线程执行完毕后,才继续往后走
t2.join() #t2线程执行完毕后,才继续往后走
print(number)
'''
输出结果:每次都不是确定的。原因是t1.start和t2.start的执行是按时间片轮换来执行的,可能会出现t1调用_add时此时计算到应该+1时,时间片用完了,执行t2的_sub时计算完—1时间片用完,此时的number和t
1调用_add的上次时间片末的number不一样,就会出现问题。
'''
出现上述问题的解决方法:加锁(只有一把锁)
import threading
lock_obj=threading.RLock()
loop =10000000
number=0
def _add(count):
lock_obj.acquire() #加锁
global number
for i in range(count):
number+=1
lock_obj.release() #释放锁
def _sub(count):
lock_obj.acquire() #申请锁(若锁没有被占用则加锁,若被占用则等待释放后再占用)
global number
for i in range(count):
number-=1
lock_obj.release() #释放锁
t1=threading.Thread(target=_add,args=(loop,))
t2=threading.Thread(target=_sub,args=(loop,))
t1.start()
t2.start()
t1.join() #t1线程执行完毕后,才继续往后走
t2.join() #t2线程执行完毕后,才继续往后走
print(number) #0
一般python使用的都是RLock,注意死锁的情况。
- t.setDaemon(布尔值):守护线程(必须放在start之前)。
- t.setDaemon(True):设置为守护线程,主线程执行完毕后,子线程也自动关闭。
- t.setDaemon(False):设置为非守护线程,主线程等待子线程,子线程执行完毕后,主线程才结束。(默认)
import threading,time
def task(arg):
time.sleep(5)
print('任务')
t=threading.Thread(target=task,args=(11,))
t.setDaemon(True)
t.start()
print('END')
'''
输出结果:END
'''
- 线程名称的设置和获取
import threading
def task(arg):
#获取当前执行此代码的线程
name=threading.current_thread().getName()
print(name)
for i in range(5):
t=threading.Thread(target=task,args=(1,))
t.setName('王腾—{}'.format(i))
t.start()
'''
运行结果:
王腾—0
王腾—1
王腾—2
王腾—3
王腾—4
'''
- 自定义线程类,直接将线程需要做的事情写到run方法中。
import threading
class MyThread(threading.Thread):
def run(self):
print('执行此线程',self._args)
t=MyThread(args=(100,))
t.start()
'''
输出结果:
执行此线程 (100,)
'''
import requests,threading
class xiazaiThread(threading.Thread):
def run(self):
file_name,video_url=self._args
res=requests.get(video_url)
with open(file_name,'wb') as f:
f.write(res.content)
url_list=[
("线程:死锁.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=7&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca"),
("线程:总结和作业.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=10&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca"),
("进程:今日概要.mp4","https://www.bilibili.com/video/BV1Ev411G7i3?p=12&vd_source=43a8b5ecb97eee8636fd8ac7ae8151ca")
]
for item in url_list:
t=xiazaiThread(args=(item[0],item[1]))
t.start()
输出结果:
- 线程池:
线程池的创建:
from concurrent.futures import ThreadPoolExecutor pool=ThreadPoolExecutor(100) pool.submit(函数名,参数1,参数2,参数...)
import time
from concurrent.futures import ThreadPoolExecutor
def task(vi_url,num):
print('开始执行任务',vi_url)
time.sleep(4)
#创建线程池,最多维护5个线程:
pool=ThreadPoolExecutor(5)
url_list=['www.xxxx_{}.com'.format(i) for i in range(11)]
for url in url_list:
#在线程池中提交一个任务,线程池中如果有空闲线程,则分配一个线程去执行,执行完毕后再将线程返还给线程池,如果没有空闲线程,则等待。
pool.submit(task,url,2)
print('END')
'''
运行结果:
开始执行任务 www.xxxx_0.com
开始执行任务 www.xxxx_1.com
开始执行任务 www.xxxx_2.com
开始执行任务 www.xxxx_3.com
开始执行任务 www.xxxx_4.com
END
开始执行任务开始执行任务 www.xxxx_6.com开始执行任务 www.xxxx_7.com开始执行任务 开始执行任务
www.xxxx_9.com
www.xxxx_5.com
www.xxxx_8.com
开始执行任务 www.xxxx_10.com
'''