python基础
1.python基础
1.1 列表常用方法
#1. append用于在列表末尾追加新的对象
a = [1,2,3]
a.append(4) #the result : [1, 2, 3, 4]
#2. count方法统计某个元素在列表中出现的次数
a = ['aa','bb','cc','aa','aa']
print(a.count('aa')) #the result : 3
#3. extend方法可以在列表的末尾一次性追加另一个序列中的多个值
a = [1,2,3]
b = [4,5,6]
a.extend(b) #the result :[1, 2, 3, 4, 5, 6]
#4. index函数用于从列表中找出某个值第一个匹配项的索引位置
a = [1,2,3,1]
print(a.index(1)) #the result : 0
#5. insert方法用于将对象插入到列表中
a = [1,2,3]
a.insert(0,'aa') #the result : ['aa', 1, 2, 3]
#6. pop方法会移除列表中的一个元素(默认是最后一个),并且返回该元素的值
a = [1,2,3]
a.pop() #the result : [1, 2]
a.pop(0)
#7. remove方法用于移除列表中某个值的第一个匹配项
a = ['aa','bb','cc','aa']
a.remove('aa') #the result : ['bb', 'cc', 'aa']
#8. reverse方法将列表中的元素反向存放
a = ['a','b','c']
a.reverse() #the result : ['c', 'b', 'a']
#9. sort方法用于在原位置对列表进行排序,意味着改变原来的列表,让其中的元素按一定顺序排列
a = ['a','b','c',1,2,3]
a.sort() #the result :[1, 2, 3, 'a', 'b', 'c']
#10. enumrate
li = [11,22,33]
for k,v in enumerate(li, 1):
print(k,v)
1.2字符串常用方法
#1. find方法可以在一个较长的字符串中查找子串,他返回子串所在位置的最左端索引,如果没有找到则返回-1
a = 'abcdefghijk'
print(a.find('abc')) #the result : 0
print(a.find('abc',10,100)) #the result : 11 指定查找的起始和结束查找位置
#2. join方法是非常重要的字符串方法,他是split方法的逆方法,用来连接序列中的元素,并且需要被连接的元素都必须是字符串。
a = ['1','2','3']
print('+'.join(a)) #the result : 1+2+3
#3. split方法,是一个非常重要的字符串,它是join的逆方法,用来将字符串分割成序列
print('1+2+3+4'.split('+')) #the result : ['1', '2', '3', '4']
#4. strip 方法返回去除首位空格(不包括内部)的字符串
print(" test test ".strip()) #the result :“test test”
#5. replace方法返回某字符串所有匹配项均被替换之后得到字符串
print("This is a test".replace('is','is_test')) #the result : This_test is_test a test
1.3字典常用方法
#1. clear方法清除字典中所有的项,这是一个原地操作,所以无返回值(或则说返回None)
d = {'name':"tom"}
d.clear()
print(d) #the result : {}
#2. fromkeys方法使用给定的键建立新的字典,每个键都对应一个默认的值None
print({}.fromkeys(['name','age'])) #the result : {'age': None, 'name': None}
#3. get方法是个更宽松的访问字典项的方法,如果试图访问字典中不存在的项时不会报错仅会 返回:None
d = {'Tom':8777,'Jack':8888,'Fly':6666}
print(d.get('Tom')) #the result : 8777
print(d.get('not_exist')) #the result : None
#4. for循环字典的三种方法
d = {'Tom':8777,'Jack':8888,'Fly':6666}
for k,v in d.items():
print(k,v)
for k in d.values():
print(k)
for k in d.keys():
print(k)
#5. pop方法用于获得对应与给定键的值,然后将这个”键-值”对从字典中移除
d = {'Tom':8777,'Jack':8888,'Fly':6666}
v = d.pop('Tom')
print(v) #8777
#6. setdefault方法在某种程度上类似于get方法,能够获得与给定键相关联的值,除此之外,setdefault还能在字典中不含有给定键的情况下设定相应的键值
d = {'Tom':8777,'Jack':8888,'Fly':6666}
d.setdefault('Tom') #the result : 8777
print(d.setdefault('Test')) #the result : None
print(d) #{'Fly': 6666, 'Jack': 8888, 'Tom': 8777, 'Test': None}
#7. update方法可以利用一个字典项更新另一个字典,提供的字典中的项会被添加到旧的字典中,如有相同的键则会被覆盖
d = {'Tom':8777,'Jack':8888,'Fly':6666}
a = {'Tom':110,'Test':119}
d.update(a)
print(d) #the result :{'Fly': 6666, 'Test': 119, 'Jack': 8888, 'Tom': 110}
#8. 将两个列表组合成字典
keys = ['a', 'b']
values = [1, 2]
print(dict(zip(keys,values))) # {'a': 1, 'b': 2}
1.4集合常用方法
list_1 = [1,2,3,4,5,1,2]
#1、去重(去除list_1中重复元素1,2)
list_1 = set(list_1) #去重: {1, 2, 3, 4, 5}
print(list_1)
list_2 = set([4,5,6,7,8])
#2、交集(在list_1和list_2中都有的元素4,5)
print(list_1.intersection(list_2)) #交集: {4, 5}
#3、并集(在list_1和list_2中的元素全部打印出来,重复元素仅打印一次)
print(list_1.union(list_2)) #并集: {1, 2, 3, 4, 5, 6, 7, 8}
#4、差集
print(list_1.difference(list_2)) #差集:在list_1中有在list_2中没有: {1, 2, 3}
print(list_2.difference(list_1)) #差集:在list_1中有在list_2中没有: {8, 6, 7}
1.5进程
1. 进程定义
1. 进程是资源分配最小单位
2. 当一个可执行程序被系统执行(分配内存等资源)就变成了一个进程
进程定义拓展回答内容
1. 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,这种执行的程序就称之为进程
2. 程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念
3. 在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。
4. 进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
5. 进程之间有自己独立的内存,各进程之间不能相互访问
6. 创建一个新线程很简单,创建新进程需要对父进程进行复制
多道编程概念
多道编程: 在计算机内存中同时存放几道相互独立的程序,他们共享系统资源,相互穿插运行
单道编程: 计算机内存中只允许一个的程序运行
2. 有了进程为什么还要线程?
1. 进程优点:
提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率
2. 进程的两个重要缺点
a. 第一点:进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
b. 第二点:进程在执行的过程中如果阻塞,即使进程中有些工作不依赖于输入的数据,也将无法执行(例如等待输入,整个进程就会挂起)。
c. 例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息
d. 你会说,操作系统不是有分时么?分时是指在不同进程间的分时呀
e. 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀
3. 进程间互相访问数据的四种方法
注:不同进程间内存是不共享的,所以互相之间不能访问对方数据
法1: 利用Queues实现父进程到子进程(或子进程间)的数据传递
法2: 使用管道pipe实现两个进程间数据传递
法3: Managers实现很多进程间数据共享
法4:借助redis中间件进行数据共享
4. 进程池
from multiprocessing import Process,Pool
import time,os
def foo(i):
time.sleep(2)
print("in the process",os.getpid()) #打印子进程的pid
return i+100
def call(arg):
print('-->exec done:',arg,os.getpid())
if __name__ == '__main__':
pool = Pool(3) #进程池最多允许5个进程放入进程池
print("主进程pid:",os.getpid()) #打印父进程的pid
for i in range(10):
#用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数
pool.apply_async(func=foo, args=(i,),callback=call)
#用法2 串行 启动进程不在用Process而是直接用pool.apply()
# pool.apply(func=foo, args=(i,))
print('end')
pool.close() #关闭pool
pool.join() #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
5. 进程和程序的区别
1. 程序只是一个普通文件,是一个机器代码指令和数据的集合,所以,程序是一个静态的实体
2. 而进程是程序运行在数据集上的动态过程,进程是一个动态实体,它应创建而产生,应调度执行因等待资源或事件而被处于等待状态,因完成任务而被撤消
3. 进程是系统进行资源分配和调度的一个独立单位
4.一个程序对应多个进程,一个进程为多个程序服务(两者之间是多对多的关系)
5. 一个程序执行在不同的数据集上就成为不同的进程,可以用进程控制块来唯一地标识每个进程
1.6线程
1. 线程定义
线程是操作系统调度的最小单位
它被包含在进程之中,是进程中的实际运作单位
进程本身是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合
线程定义拓展回答内容
1. 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
2. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
3. 无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
4. 进程本身是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合
5. 所有在同一个进程里的线程是共享同一块内存空间的,不同进程间内存空间不同
6. 同一个进程中的各线程可以相互访问资源,线程可以操作同进程中的其他线程,但进程仅能操作子进程
7. 两个进程想通信,必须要通过一个中间代理
8. 对主线程的修改可能回影响其他子线程,对主进程修改不会影响其他进程因为进程间内存相互独立,但是同一进程下的线程共享内存
2. 进程和线程的区别
1、进程包含线程
2、线程共享内存空间
3、进程内存是独立的(不可互相访问)
4、进程可以生成子进程,子进程之间互相不能互相访问(相当于在父级进程克隆两个子进程)
5、在一个进程里面线程之间可以交流。两个进程想通信,必须通过一个中间代理来实现
6、创建新线程很简单,创建新进程需要对其父进程进行克隆。
7、一个线程可以控制或操作同一个进程里面的其它线程。但进程只能操作子进程。
8、父进程可以修改不影响子进程,但不能修改。
9、线程可以帮助应用程序同时做几件事
3. for循环同时启动多个线程
import threading
import time
def sayhi(num): #定义每个线程要运行的函数
print("running on number:%s" %num)
time.sleep(3)
for i in range(50):
t = threading.Thread(target=sayhi,args=('t-%s'%i,))
t.start()
4. t.join(): 实现所有线程都执行结束后再执行主线程
import threading
import time
start_time = time.time()
def sayhi(num): #定义每个线程要运行的函数
print("running on number:%s" %num)
time.sleep(3)
t_objs = [] #将进程实例对象存储在这个列表中
for i in range(50):
t = threading.Thread(target=sayhi,args=('t-%s'%i,))
t.start() #启动一个线程,程序不会阻塞
t_objs.append(t)
print(threading.active_count()) #打印当前活跃进程数量
for t in t_objs: #利用for循环等待上面50个进程全部结束
t.join() #阻塞某个程序
print(threading.current_thread()) #打印执行这个命令进程
print("----------------all threads has finished.....")
print(threading.active_count())
print('cost time:',time.time() - start_time)
5. setDaemon(): 守护线程,主线程退出时,需要子线程随主线程退出
import threading
import time
start_time = time.time()
def sayhi(num): #定义每个线程要运行的函数
print("running on number:%s" %num)
time.sleep(3)
for i in range(50):
t = threading.Thread(target=sayhi,args=('t-%s'%i,))
t.setDaemon(True) #把当前线程变成守护线程,必须在t.start()前设置
t.start() #启动一个线程,程序不会阻塞
print('cost time:',time.time() - start_time)
6. GIL全局解释器锁:保证同一时间仅有一个线程对资源有操作权限
作用:在一个进程内,同一时刻只能有一个线程执行
说明:python多线程中GIL锁只是在CPU操作时(如:计算)才是串行的,其他都是并行的,所以比串行快很多
1)为了解决不同线程同时访问同一资源时,数据保护问题,而产生了GIL
2)GIL在解释器的层面限制了程序在同一时间只有一个线程被CPU实际执行,而不管你的程序里实际开了多少条线程
3)为了解决这个问题,CPython自己定义了一个全局解释器锁,同一时间仅仅有一个线程可以拿到这个数据
4)python之所以会产生这种不好的状况是因为python启用一个线程是调用操作系统原生线程,就是C接口
5)但是这仅仅是CPython这个版本的问题,在PyPy,中就没有这种缺陷
7. 线程锁
1)当一个线程对某个资源进行CPU计算的操作时加一个线程锁,只有当前线程计算完成主动释放锁,其他线程才能对其操作
2)这样就可以防止还未计算完成,释放GIL锁后其他线程对这个资源操作导致混乱问题
用户锁使用举例
import time
import threading
lock = threading.Lock() #1 生成全局锁
def addNum():
global num #2 在每个线程中都获取这个全局变量
print('--get num:',num )
time.sleep(1)
lock.acquire() #3 修改数据前加锁
num -= 1 #4 对此公共变量进行-1操作
lock.release() #5 修改后释放
8. Semaphore(信号量)
1. 互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据
2. 比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去
3. 作用就是同一时刻允许运行的线程数量
9. 线程池实现并发
import requests
from concurrent.futures import ThreadPoolExecutor
def fetch_request(url):
result = requests.get(url)
print(result.text)
url_list = [
'https://www.baidu.com',
'https://www.google.com/', #google页面会卡住,知道页面超时后这个进程才结束
'http://dig.chouti.com/', #chouti页面内容会直接返回,不会等待Google页面的返回
]
pool = ThreadPoolExecutor(10) # 创建一个线程池,最多开10个线程
for url in url_list:
pool.submit(fetch_request,url) # 去线程池中获取一个线程,线程去执行fetch_request方法
pool.shutdown(True) # 主线程自己关闭,让子线程自己拿任务执行
1.7协程
1. 什么是协程(进入上一次调用的状态)
1. 协程,又称微线程,纤程,协程是一种用户态的轻量级线程。
2. 线程的切换会保存到CPU的栈里,协程拥有自己的寄存器上下文和栈,
3. 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈
4. 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态
5. 协程最主要的作用是在单线程的条件下实现并发的效果,但实际上还是串行的(像yield一样)
2. 协程缺点(无法利用多核资源)
协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上线程阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
3. 协程为何能处理大并发1:Greenlet遇到I/O手动切换
1. 协程之所以快是因为遇到I/O操作就切换(最后只有CPU运算)
2. 这里先演示用greenlet实现手动的对各个协程之间切换
3. 其实Gevent模块仅仅是对greenlet的再封装,将I/O间的手动切换变成自动切换
4. 协程为何能处理大并发2:Gevent遇到I/O自动切换
1. Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程
2. 在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程
3. Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
4. Gevent原理是只要遇到I/O操作就会自动切换到下一个协程
5. 使用协程处理并发
注:Gevent只用起一个线程,当请求发出去后gevent就不管,永远就只有一个线程工作,谁先回来先处理
import gevent
import requests
from gevent import monkey
monkey.patch_all()
# 这些请求谁先回来就先处理谁
def fetch_async(method, url, req_kwargs):
response = requests.request(method=method, url=url, **req_kwargs)
print(response.url, response.content)
# ##### 发送请求 #####
gevent.joinall([
gevent.spawn(fetch_async, method='get', url='https://www.python.org/', req_kwargs={}),
gevent.spawn(fetch_async, method='get', url='https://www.google.com/', req_kwargs={}),
gevent.spawn(fetch_async, method='get', url='https://github.com/', req_kwargs={}),
])
1.8 sellect、poll、epoll
1. select (能监控数量有限,不能告诉用户程序具体哪个连接有数据)
1. select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点
2. select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024
3. select监控socket连接时不能准确告诉用户是哪个,比如:现在用socket监控10000链接,如果其中有一个链接有数据了,select就会告诉用户程序,你有socket来数据了,那样就只能自己循环10000次判断哪个活跃
2. poll(和select一样,仅仅去除了最大监控数量)
1. poll和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制
2. 可以理解为poll是一个过渡阶段,大家也都不用他
3. epoll (不仅没有最大监控数量限制,还能告诉用户程序哪个连接有活跃)
注:epoll被认为是linux下性能最好的多路io就绪通知方法
1. epoll直到Linux2.6(centos6以后)才出现了由内核直接支持
2.Epoll没有最大文件描述符数量限制
3. epoll最重要的优点是他可以直接告诉用户程序哪一个,比如现在用epoll去监控10000个socket链接,交给内核去监测,现在有一个连接有数据了,在有有一个连接有数据了,epoll会直接高数用户程序哪个连接有数据了
4. epoll能实现高并发原理
1. epoll() 中内核则维护一个链表,epoll_wait 直接检查链表是不是空就知道是否有文件描述符准备好了。
2. 在内核实现中 epoll 是根据每个 sockfd 上面的与设备驱动程序建立起来的回调函数实现的。
3. 某个 sockfd 上的事件发生时,与它对应的回调函数就会被调用,把这个 sockfd 加入链表。
4. epoll上面链表中获取文件描述,这里使用内存映射(mmap)技术, 避免了复制大量文件描述符带来的开销
内存映射(mmap):内存映射文件,是由一个文件到一块内存的映射,将不必再对文件执行I/O操作
1.9装饰器
1. 装饰器定义
不能修改被装饰函数的源代码,不能修改被装饰函数的调用方式,为其他函数添加其他功能
2. 使用高阶函数模拟装饰器
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import time
def timer(func):
start_time = time.time()
func()
print '函数执行时间为', time.time() - start_time
def test():
print '开始执行test'
time.sleep(3)
print 'test执行结束'
timer(test)
'''
开始执行test
test执行结束
函数执行时间为 3.00332999229
'''
3. 计算运行时间装饰器
import time
def timer(func): #timer(test1) func=test1
def deco(*args,**kwargs):
start_time = time.time()
func(*args,**kwargs) #run test1
stop_time = time.time()
print("running time is %s"%(stop_time-start_time))
return deco
@timer # test1=timer(test1)
def test1():
time.sleep(3)
print("in the test1")
test1()
4. 装饰器使用场景
授权:装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中
日志:在记录日志的地方添加装饰器
缓存:通过装饰器获取缓存中的值
1.10生成器
1. 生成器定义
生成器可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他数据类型需要调用自己的内置iter方法)
在Python中,一边循环,一边计算的机制,称为生成器。
2. 生成器的作用
1. 通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的。
2. 而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
3. 所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?
4. 这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
3. 生成器工作原理
1)生成器是这样一个函数,它记住上一次返回时在函数体中的位置。
2)对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
3)生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造中的位置。
4)生成器是一个函数,而且函数的参数都会保留。
5)迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的
4. yield生成器运行机制
在Python中,yield就是这样的一个生成器。
1) 当你问生成器要一个数时,生成器会执行,直至出现 yield 语句,生成器把yield 的参数给你,之后生成器就不会往下继续运行。
2) 当你问他要下一个数时,他会从上次的状态开始运行,直至出现yield语句,把参数给你,之后停下。如此反复
3) 在python中,当你定义一个函数,使用了yield关键字时,这个函数就是一个生成器
4) 它的执行会和其他普通的函数有很多不同,函数返回的是一个对象,而不是你平常所用return语句那样,能得到结果值。如果想取得值,那得调用next()函数
5) 每当调用一次迭代器的next函数,生成器函数运行到yield之处,返回yield后面的值且在这个地方暂停,所有的状态都会被保持住,直到下次next函数被调用,或者碰到异常循环退出。
# yield实现fib数
def fib(max_num):
a,b = 1,1
while a < max_num:
yield b
a,b=b,a+b
g = fib(10) #生成一个生成器:[1,2, 3, 5, 8, 13]
print(g.__next__()) #第一次调用返回:1
print(list(g)) #把剩下元素变成列表:[2, 3, 5, 8, 13]
1.11迭代器
1. 迭代器定义
1. 迭代器是访问集合内元素的方式,迭代器对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束
2. 迭代器仅是一容器对象,它有两个基本方法
1)next方法:返回容器的下一个元素
2)__iter__方法:返回迭代器自身
a = iter([1,2,]) #生成一个迭代器
print(a.__next__())
print(a.__next__())
print(a.__next__()) #在这一步会引发 “StopIteration” 的异常
2. 生成器和迭代器之间的区别
在使用生成器时,我们创建一个函数;在使用迭代器时,我们使用内置函数iter()和next()。 在生成器中,我们使用关键字‘yield’来每次生成/返回一个对象。 生成器中有多少‘yield’语句,你可以自定义。 每次‘yield’暂停循环时,生成器会保存本地变量的状态。而迭代器并不会使用局部变量,它只需要一个可迭代对象进行迭代。 使用类可以实现你自己的迭代器,但无法实现生成器。 生成器运行速度快,语法简洁,更简单。 迭代器更能节约内存。
1.12面向对象
1.12.1 面向对象三大特性: 封装,继承,多态
1. 封装
1.在类中对数据的赋值、内部调用对外部用户是透明的
2. 这使类变成了一个胶囊或容器,里面包含着类的数据和方法
3. 作用:
1)防止数据被随意修改
2)使外部程序不需要关注对象内部的构造,只需要通过对外提供的接口进行直接访问
2.Inheritance 继承(代码重用)
1. 一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
2. 比如CS中的警察和恐怖分子,可以将两个角色的相同点写到一个父类中,然后同时去继承它
3. 使用经典类: Person.__init__(self,name,age) 并重写写父类Person的构造方法,实现,先覆盖,再继承,再重构
3. Polymorphism 多态(接口重用)
1.多态是面向对象的重要特性,简单点说:“一个接口,多种实现”
2. 指一个基类中派生出了不同的子类,且每个子类在继承同样的方法名的同时又对父类的方法做了不同的实现
3. 这就是同一种事物表现出的多种形态
4. 比如黄种人继承了人talk这个功能,但是他说的是中文,而美国人的talk是英文,但是他们是同样的talk
作用:简单的讲就是允许父类调用子类的方法
1.12.2 静态方法、类方法、属性方法
1. 静态方法
1. 作用:静态方法可以更好的组织代码,防止代码变大后变得比较混乱。
2. 特性: 静态方法只是名义上归类管理,实际上在静态方法里访问不了类或则实例中的任何属性
3. 静态方法使用场景:
1)我们要写一个只在类中运行而不在实例中运行的方法.
2)经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法.
3)比如更改环境变量或者修改其他类的属性等能用到静态方法.
4)这种情况可以直接用函数解决, 但这样同样会扩散类内部的代码,造成维护困难.
4. 调用方式: 既可以被类直接调用,也可以通过实例调用
class Dog(object):
def __init__(self,name):
self.name = name
@staticmethod
def eat():
print("I am a static method")
d = Dog("ChenRonghua")
d.eat() #方法1:使用实例调用
Dog.eat() #方法2:使用类直接调用
2. 类方法
1. 作用:无需实例化直接被类调用
2. 特性: 类方法只能访问类变量,不能访问实例变量
3. 类方法使用场景: 当我们还未创建实例,但是需要调用类中的方法
4. 调用方式: 既可以被类直接调用,也可以通过实例调用
class Dog(object):
name = '类变量' #在这里如果不定义类变量仅定义实例变量依然报错
def __init__(self,name):
self.name = '实例变量'
self.name = name
@classmethod
def eat(self,food):
print("%s is eating %s"%(self.name,food))
Dog.eat('baozi') #方法1:使用类直接调用
d = Dog("ChenRonghua")
d.eat("包子") #方法2:使用实例d调用
3. 属性方法
作用:属性方法把一个方法变成一个属性,隐藏了实现细节,调用时不必加括号直接d.eat即可调用self.eat()方法
class Dog(object):
def __init__(self, name):
self.name = name
@property
def eat(self):
print(" %s is eating" % self.name)
d = Dog("ChenRonghua")
d.eat()
# 调用会出以下错误, 说NoneType is not callable, 因为eat此时已经变成一个静态属性了,
# 不是方法了, 想调用已经不需要加()号了,直接d.eat就可以了
1.12.3 魔法方法
1. type生成类调用顺序
__new__ : 先于__init__方法,每生成一个实例执行一次,__new__ 类方法创建实例对象
__init__ : __init__方法每生成一个实例就会执行一次,初始化实例对象
__call__ : 后与__init__方法,C()() 使用类再加一个括号调用, C为类名称
__del__ : 析构方法,删除无用的内存对象(当程序结束会自动自行析构方法)
2. 类实例化时魔法方法调用顺序
class Student(object):
def __new__(cls, *args, **kwargs):
print('__new__')
return object.__new__(cls) # 必须返回父类的__new__方法,否则不不执行__init__方法,无法创建实例
def __init__(self,name):
print('__init__')
self.name = name
def __str__(self): # 作用:打印实例时显示指定字符串,而不是内存地址
print('__str__')
return self.name
def __call__(self, *args, **kwargs): # 当执行C()(*args) 或者 s1(*args) 就会执行__call__
print('__call__',*args)
def __del__(self): # 作用:清除无用的实例对内存的暂用
print('__del__')
#1、实例化时机会执行__new__、__init__
s1 = Student('tom')
#2、执行 实例() 就会执行__call__ 方法,并将参数传递给__call__函数
s1('call01')
#3、当打印实例时就会执行 __str__ 方法下返回的字符串(默认返回的实例地址)
print(s1)
#4、析构方法:当删除实例时就会调用 __del__ 方法
del s1
# 析构方法作用:在程序结束后会自动执行析构方法删除所有实例
# 但是在程序运行时有很多实例是无用的,但是python内存回收机制却不会自动删除他们,这样就浪费内存
# 我们可以执行 del s1 ,那么在程序运行时,python内存回收机制会检测到这些实例时无用的,才会删除
# 其实我们执行del s1,并没有回收内存,只不过是摘除门牌号,python内存回收机制发现没有门牌号后会自动回收内存
1.12.4 反射: hasattr、getattr、setattr 和 delattr
1. hasattr(ogj,name_str) 判断一个对象里是否有对应的字符串方法
class Dog(object):
def eat(self,food):
print("eat method!!!")
d = Dog()
#hasattr判断对象d是否有eat方法,有返回True,没有返回False
print(hasattr(d,'eat')) #True
print(hasattr(d,'cat')) #False
2. getattr(obj,name_str) 根据字符串去获取obj对象里的对应的方法的内存地址
class Dog(object):
def eat(self):
print("eat method!!!")
d = Dog()
if hasattr(d,'eat'): # hasattr判断实例是否有eat方法
func = getattr(d, 'eat') # getattr获取实例d的eat方法内存地址
func() # 执行实例d的eat方法
#运行结果: eat method!!!
3. 使用stattr给类实例对象动态添加一个新的方法
4. delattr删除实例属性
1.13深拷贝和浅拷贝
1. 预备知识一——python的变量及其存储
1. python的一切变量都是对象,变量的存储,采用了引用语义的方式,存储的只是一个变量的值所在的内存地址,而不是这个变量的只本身
2. 不管多么复杂的数据结构,浅拷贝都只会copy一层。
理解:两个人公用一张桌子,只要桌子不变,桌子上的菜发生了变化两个人是共同感受的。
2. 浅copy与deepcopy
1. 浅copy: 不管多么复杂的数据结构,浅拷贝都只会copy一层
2. deepcopy : 深拷贝会完全复制原变量相关的所有数据,在内存中生成一套完全一样的内容,我们对这两个变量中任意一个修改都不会影响其他变量
import copy
sourceList = [1,2,3,[4,5,6]]
copyList = copy.copy(sourceList)
deepcopyList = copy.deepcopy(sourceList)
sourceList[3][0]=100
print(sourceList) # [1, 2, 3, [100, 5, 6]]
print(copyList) # [1, 2, 3, [100, 5, 6]]
print(deepcopyList) # [1, 2, 3, [4, 5, 6]]
1.14 python垃圾回收机制
1. 引用计数
1)当一个对象的引用被创建或者复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1.
2)当对象的引用计数减少为0时,就意味着对象已经再没有被使用了,可以将其内存释放掉。
2. 标记-清除
1)它分为两个阶段:第一阶段是标记阶段,GC会把所有的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收。
2)对象之间通过引用(指针)连在一起,构成一个有向图
3)从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象,根对象就是全局变量、调用栈、寄存器。
注:像是PyIntObject、PyStringObject这些不可变对象是不可能产生循环引用的,因为它们内部不可能持有其它对象的引用。
1.?在上图中,可以从程序变量直接访问块1,并且可以间接访问块2和3,程序无法访问块4和5
2.?第一步将标记块1,并记住块2和3以供稍后处理。
3.?第二步将标记块2,第三步将标记块3,但不记得块2,因为它已被标记。
4.?扫描阶段将忽略块1,2和3,因为它们已被标记,但会回收块4和5。
3. 分代回收
1. 分代回收是建立在标记清除技术基础之上的,是一种以空间换时间的操作方式。
2. Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代)
3. 他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。
4. 新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发
5. 把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推
6. 老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
1.15 上下文管理
1. 什么是with语句
1. with是一种上下文管理协议,目的在于从流程图中把 try,except 和finally 关键字和资源分配释放相关代码统统去掉,简化try….except….finlally的处理流程。
2. 所以使用with处理的对象必须有enter()和exit()这两个方法
1)with通过enter方法初始化(enter方法在语句体执行之前进入运行)
2)然后在exit中做善后以及处理异常(exit()方法在语句体执行完毕退出后运行)
2. with语句使用场景
1. with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源
2. 比如文件使用后自动关闭、线程中锁的自动获取和释放等。
3.with处理文件操作的实例
with open('/etc/passwd') as f:
for line in f:
print(line)
# 这段代码的作用:打开一个文件,如果一切正常,把文件对象赋值给f,然后用迭代器遍历文件中每一行,当完成时,关闭文件;
# 而无论在这段代码的任何地方,如果发生异常,此时文件仍会被关闭。
1.16高阶函数
1. lambda基本使用
1. lambda只是一个表达式,函数体比def简单很多。
2. lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
3. lambda表达式是起到一个函数速写的作用。允许在代码内嵌入一个函数的定义。
4. 格式:lambda的一般形式是关键字lambda后面跟一个或多个参数,紧跟一个冒号,之后是一个表达式。
f = lambda x,y,z:x+y+z
print(f(1,2,3)) # 6
my_lambda = lambda arg : arg + 1
print(my_lambda(10)) # 11
2. 三元运算
1. 三元运算格式: result=值1 if x<y else 值2 if条件成立result=1,否则result=2
2. 作用:三元运算,又称三目运算,主要作用是减少代码量,是对简单的条件语句的缩写
name = 'Tom' if 1 == 1 else 'fly'
print(name)
# 运行结果: Tom
f = lambda x:x if x % 2 != 0 else x + 100
print(f(10)) # 110
3. filter()函数可以对序列做过滤处理
利用 filter、lambda表达式 获取l1中元素小于33的所有元素 l1 = [11, 22, 33, 44, 55]
l1= [11,22,33,44,55]
a = filter(lambda x: x<33, l1)
print(list(a))
4. Map是对序列根据设定条件进行操作后返回他设置的是操作方法
?利用map,lambda表达式将所有偶数元素加100
l1= [11,22,33,44,55]
ret = map(lambda x:x if x % 2 != 0 else x + 100,l1)
print(list(ret))
# 运行结果: [11, 122, 33, 144, 55]
5. reduce函数
使用reduce进行求和运算
1.?reduce()函数即为化简函数,它的执行过程为:每一次迭代,都将上一次的迭代结果与下一个元素一同传入二元func函数中去执行。
2.?在reduce()函数中,init是可选的,如果指定,则作为第一次迭代的第一个元素使用,如果没有指定,就取seq中的第一个元素。
from functools import reduce
def f(x, y):
return x + y
print(reduce(f, [1, 3, 5, 7, 9])) # 25
# 1、先计算头两个元素:f(1, 3),结果为4;
# 2、再把结果和第3个元素计算:f(4, 5),结果为9;
# 3、再把结果和第4个元素计算:f(9, 7),结果为16;
# 4、再把结果和第5个元素计算:f(16, 9),结果为25;
# 5、由于没有更多的元素了,计算结束,返回结果25。
print( reduce(lambda x, y: x + y, [1, 3, 5, 7, 9]) ) # 25
6. sorted函数
sorted对字典排序
d = {'k1':1, 'k3': 3, 'k2':2}
# d.items() = [('k1', 1), ('k3', 3), ('k2', 2)]
a = sorted(d.items(), key=lambda x: x[1])
print(a) # [('k1', 1), ('k2', 2), ('k3', 3)]