面向对象
类:对象
- python中的可变对象和不可变对象?
不可变对象,该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当 于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。 可变对象,该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接 发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。 Python 中,数值类型(int 和float)、字符串str、元组tuple 都是不可变类型。而列表list、字典dict、集合 set 是可变类型。
- python中is和==的区别?
is 判断的是a 对象是否就是b 对象,是通过id 来判断的。 ==判断的是a 对象的值是否和b 对象的值相等,是通过value 来判断的。
- python的魔法方法?
魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现(重载)了这些方法中的某一个,那么这个 方法就会在特殊的情况下被Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。它们经常是 两个下划线包围来命名的(比如__init__,__lt__),Python 的魔法方法是非常强大的,所以了解其使用方法也变得 尤为重要! __init__ 构造器,当一个实例被创建的时候初始化的方法。但是它并不是实例化调用的第一个方法。 __new__才是实例化对象调用的第一个方法,它只取下cls 参数,并把其他参数传给__init__。__new__很少使 用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。 __call__ 允许一个类的实例像函数一样被调用。 __getitem__ 定义获取容器中指定元素的行为,相当于self[key] 。 __getattr__ 定义当用户试图访问一个不存在属性的时候的行为。 __setattr__ 定义当一个属性被设置的时候的行为。 __getattribute__ 定义当一个属性被访问的时候的行为。
- 面向对象中怎么实现只读属性?
将对象私有化,通过共有方法提供一个读取数据的接口。 1.class person: 2. def __init__(self,x): 3. self.__age = 10; 4. def age(self): 5. return self.__age; 6.t = person(22) 7.# t.__age = 100 8.print(t.age()) 最好的方法 9.class MyCls(object): 10. __weight = 50 11. 12. @property #以访问属性的方式来访问weight 方法 13. def weight(self): 14. return self.__weight 15. 16.if __name__ == '__main__': 17. obj = MyCls() 18. print(obj.weight) 19. obj.weight = 12 1.Traceback (most recent call last): 2.50 3. File "C:/PythonTest/test.py", line 11, in <module> 4. obj.weight = 12 5.AttributeError: can't set attribute
- 谈谈你对面向对象的理解?
面向对象是相对于面向过程而言的。面向过程语言是一种基于功能分析的、以算法为中心的程序设计方法;而面 向对象是一种基于结构分析的、以数据为中心的程序设计思想。在面向对象语言中有一个有很重要东西,叫做类。 面向对象有三大特性:封装、继承、多态。
正则表达式
- python里match与search的区别?
match()函数只检测RE 是不是在string 的开始位置匹配, search()会扫描整个string 查找匹配; 也就是说match()只有在0 位置匹配成功的话才有返回, 如果不是开始位置匹配成功的话,match()就返回none。
- python字符串查找和替换?
1. re.findall(r’目的字符串’,’原有字符串’) #查询 2. re.findall(r'cast','itcast.cn')[0] 3. re.sub(r‘要替换原字符’,’要替换新字符’,’原始字符串’) 4. re.sub(r'cast','heima','itcast.cn'
- python匹配HTML g tag的时候,<.*>和<.*?>有什么区别?
<.*>是贪婪匹配,会从第一个“<”开始匹配,直到最后一个“>”中间所有的字符都会匹配到,中间可能会包含 “<>”。 <.*?>是非贪婪匹配,从第一个“<”开始往后,遇到第一个“>”结束匹配,这中间的字符串都会匹配到,但是 不会有“<>”。
<.*>是贪婪匹配,会从第一个“<”开始匹配,直到最后一个“>”中间所有的字符都会匹配到,中间可能会包含 “<>”。 <.*?>是非贪婪匹配,从第一个“<”开始往后,遇到第一个“>”结束匹配,这中间的字符串都会匹配到,但是 不会有“<>”。
- 请写出下列正则关键字的含义?
语法 说明 表达式实例 完整匹配的字符串 一般字符 匹配自身 abc abc . 匹配任意换行“\n”外的字符,在DOTALL模式中也能匹配换行符 a.c abc \ 转义字符,使后一个字符改变原来的意思,如果字符串中有字符*需要匹配,可使用\*或者【*】 a\.c
a\\c
a. c
a\c
[...] 字符集(字符类)。对应的位置可以是
字符集中任意字符。字符集中的字
符可以逐个列出,也可以给出范围,
如[abc]或[a-c]。第一个字符如果是^
则表示取反,如[^abc]表示不是abc
的其他字符。
所有的特殊字符在字符集中都失去
其原有的特殊含义。在字符集中如
果要使用]、-或^,可以在前面加上反
斜杠,或把]、-放在第一个字符,把^
放在非第—个字符。a[bcd]e abe
ace
ade\d 数字:[0-9] a\dc a1c \D 非数字:[^\d] a\Dc abc \s 空白字符:[<空格>\ t\r\n\f\v] a\sc ac \S 非空白字符:[^\s] a\Sc abc \w 单词字符:[A-Za-z0-9_] a\wc abc \W 非单词字符:[^\W] a\Wc ac * 匹配前一个字符0 或无限次 abc* ab
abccc+ 匹配前一个字符1 次或无限次 abc+ abc
abccc? 匹配前一个字符0 次或1 次。 abc? Ab
abc【m】 匹配前一个字符m 次 ab{2}c abbc
系统编程
- 进程总结
进程:程序运行在操作系统上的一个实例,就称之为进程。进程需要相应的系统资源:内存、时间 片、pid。 创建进程: 1.首先要导入multiprocessing 中的Process; 2.创建一个Process 对象; 3.创建Process 对象时,可以传递参数; 1.p = Process(target=XXX, args=(元组,) , kwargs={key:value}) 2.target = XXX 指定的任务函数,不用加() 3.args=(元组,) , kwargs={key:value} 给任务函数传递的参数 4.使用start()启动进程; 5.结束进程。 Process 语法结构: Process([group [, target [, name [, args [, kwargs]]]]]) target:如果传递了函数的引用,可以让这个子进程就执行函数中的代码 args:给target 指定的函数传递的参数,以元组的形式进行传递 kwargs:给target 指定的函数传递参数,以字典的形式进行传递 name:给进程设定一个名字,可以省略 group:指定进程组,大多数情况下用不到 Process 创建的实例对象的常用方法有: start():启动子进程实例(创建子进程) is_alive():判断进程子进程是否还在活着 join(timeout):是否等待子进程执行结束,或者等待多少秒 terminate():不管任务是否完成,立即终止子进程 Process 创建的实例对象的常用属性: name:当前进程的别名,默认为Process-N,N 为从1 开始递增的整数 pid:当前进程的pid(进程号)
给子进程指定函数传递参数Demo:
1.import osfrom multiprocessing import Process 2.import time 3. 4.def pro_func(name, age, **kwargs): 5. for i in range(5): 6. print("子进程正在运行中,name=%s, age=%d, pid=%d" %(name, age, os.getpid())) 7. print(kwargs) 8. time.sleep(0.2) 9. 10.if __name__ == '__main__': 11. # 创建Process 对象 12. p = Process(target=pro_func, args=('小明',18), kwargs={'m': 20}) 13. # 启动进程 14. p.start() 15. time.sleep(1) 16. # 1 秒钟之后,立刻结束子进程 17. p.terminate() 18. p.join() 注意:进程间不共享全局变量。 进程之间的通信-Queue 在初始化Queue()对象时,(例如q=Queue(),若在括号中没有指定最大可接受的消息数量,或数 量为负值时,那么就代表可接受的消息数量没有上限-直到内存的尽头) Queue.qsize():返回当前队列包含的消息数量。 Queue.empty():如果队列为空,返回True,反之False。 Queue.full():如果队列满了,返回True,反之False。 Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block 默认值为 True。 如果block 使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞 (停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout 秒,若还 没读取到任何消息,则抛出"Queue.Empty"异常; 如果block 值为False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常; Queue.get_nowait():相当Queue.get(False); Queue.put(item,[block[, timeout]]):将item 消息写入队列,block 默认值为True; 如果block 使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此 时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待 timeout 秒,若还没空间,则抛出"Queue.Full"异常; 如果block 值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常; Queue.put_nowait(item):相当Queue.put(item, False);
进程之间通信Demo:
1.from multiprocessing import Process, Queueimport os, time, random 2.# 写数据进程执行的代码:def write(q): 3. for value in ['A', 'B', 'C']: 4. print('Put %s to queue...' % value) 5. q.put(value) 6. time.sleep(random.random()) 7.# 读数据进程执行的代码:def read(q): 8. while True: 9. if not q.empty(): 10. value = q.get(True) 11. print('Get %s from queue.' % value) 12. time.sleep(random.random()) 13. else: 14. break 15.if __name__=='__main__': 16. # 父进程创建Queue,并传给各个子进程: 17. q = Queue() 18. pw = Process(target=write, args=(q,)) 19. pr = Process(target=read, args=(q,)) 20. # 启动子进程pw,写入: 21. pw.start() 22. # 等待pw 结束: 23. pw.join() 24. # 启动子进程pr,读取: 25. pr.start() 26. pr.join() 27. # pr 进程里是死循环,无法等待其结束,只能强行终止: 28. print('') 29. print('所有数据都写入并且读完')
进程池Pool:
1.# -*- coding:utf-8 -*- 2.from multiprocessing import Poolimport os, time, random 3.def worker(msg): 4. t_start = time.time() 5. print("%s 开始执行,进程号为%d" % (msg,os.getpid())) 6. # random.random()随机生成0~1 之间的浮点数 7. time.sleep(random.random()*2) 8. t_stop = time.time() 9. print(msg,"执行完毕,耗时%0.2f" % (t_stop-t_start)) 10. 11.po = Pool(3) # 定义一个进程池,最大进程数3 12.for i in range(0,10): 13. # Pool().apply_async(要调用的目标,(传递给目标的参数元祖,)) 14. # 每次循环将会用空闲出来的子进程去调用目标 15. po.apply_async(worker,(i,)) 16. 17.print("----start----") 18.po.close() # 关闭进程池,关闭后po 不再接收新的请求 19.po.join() # 等待po 中所有子进程执行完成,必须放在close 语句之后 20.print("-----end-----")
multiprocessing.Pool 常用函数解析: apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待 上一个进程退出才能执行下一个进程),args 为传递给func 的参数列表,kwds 为传递给func 的关键字参数列表; close():关闭Pool,使其不再接受新的任务; terminate():不管任务是否完成,立即终止; join():主进程阻塞,等待子进程的退出, 必须在close 或terminate 之后使用; 进程池中使用Queue 如果要使用Pool 创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是 multiprocessing.Queue(),否则会得到一条如下的错误信息: RuntimeError: Queue objects should only be shared between processes through inheritance. 1.from multiprocessing import Manager,Poolimport os,time,random 2.def reader(q): 3. print("reader 启动(%s),父进程为(%s)" % (os.getpid(), os.getppid())) 4. for i in range(q.qsize()): 5. print("reader 从Queue 获取到消息:%s" % q.get(True)) 6.def writer(q): 7. print("writer 启动(%s),父进程为(%s)" % (os.getpid(), os.getppid())) 8. for i in "itcast": 9. q.put(i) 10.if __name__=="__main__": 11. print("(%s) start" % os.getpid()) 12. q = Manager().Queue() # 使用Manager 中的Queue 13. po = Pool() 14. po.apply_async(writer, (q,)) 15. 16. time.sleep(1) # 先让上面的任务向Queue 存入数据,然后再让下面的任务开始从中取数据 17. 18. po.apply_async(reader, (q,)) 19. po.close() 20. po.join() 21. print("(%s) End" % os.getpid())
- 谈谈你对多进程,多线程以及协程的理解,项目是否用?
这个问题被问的概率相当之大,其实多线程,多进程,在实际开发中用到的很少,除非是那些对项 目性能要求特别高的,有的开发工作几年了,也确实没用过,你可以这么回答,给他扯扯什么是进程, 线程(cpython 中是伪多线程)的概念就行,实在不行你就说你之前写过下载文件时,用过多线程技术, 或者业余时间用过多线程写爬虫,提升效率。 进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最 小单位,进程拥有自己独立的内存空间,所以进程间数据不共享,开销大。 线程: 调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在一个进程至少有一个 线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。 协程:是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和 栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存 器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切 换非常快。
- 什么是对线程竞争?
线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即: 数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全 那么怎么解决多线程竞争问题?-- 锁。 锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下 的原子操作问题。 锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了 锁的致命问题:死锁。
- 解释一些什么是锁?有哪几种锁?
锁(Lock)是Python 提供的对线程控制的对象。有互斥锁、可重入锁、死锁。 - 什么是死锁?
若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁,
互相干等着,程序无法执行下去,这就是死锁。
GIL 锁(有时候,面试官不问,你自己要主动说,增加b 格,尽量别一问一答的尬聊,不然最后等
到的一句话就是:你还有什么想问的么?)
GIL 锁全局解释器锁(只在cpython 里才有)作用:限制多线程同时执行,保证同一时间只有一个线程执行,所以cpython 里的多线程其实是伪
多线程!所以Python 里常常使用协程技术来代替多线程,协程是一种更轻量级的线程,
进程和线程的切换时由系统决定,而协程由我们程序员自己决定,而模块gevent 下切换是遇到了
耗时操作才会切换。
三者的关系:进程里有线程,线程里有协程。 - 什么是线程安全,什么是互斥锁?
每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程
访问该对象。
同一个进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作
尚未结束,另一个线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,
保证每个线程对该对象的操作都得到正确的结果。 - 说说下面几个概念:同步,异步,阻塞,非阻塞?
同步:多个任务之间有先后顺序执行,一个执行完下个才能执行。
异步:多个任务之间没有先后顺序,可以同时执行有时候一个任务可能要在必要的时候获取另一个
同时执行的任务的结果,这个就叫回调!
阻塞:如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。
非阻塞:如果不会卡住,可以继续执行,就是说非阻塞的。
同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。 - 什么是僵尸进程和孤儿进程?怎么避免僵尸进程?
孤儿进程:父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被init 进程(进
程号为1)所收养,并由init 进程对它们完成状态收集工作。
僵尸进程:进程使用fork 创建子进程,如果子进程退出,而父进程并没有调用wait 或waitpid 获
取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。
避免僵尸进程的方法:
1.fork 两次用孙子进程去完成子进程的任务;
2.用wait()函数使父进程阻塞;
3.使用信号量,在signal handler 中调用waitpid,这样父进程不用阻塞。 - python中的进程与线程的使用场景?
多进程适合在CPU 密集型操作(cpu 操作指令比较多,如位数多的浮点运算)。
多线程适合在IO 密集型操作(读写数据操作较多的,比如爬虫)。 - 线程是并发还是并行,进程是并发还是并行?
线程是并发,进程是并行;
进程之间相互独立,是系统分配资源的最小单位,同一个线程中的所有线程共享资源。 - 并行(parallel)和并发(concurrency)?
并行:同一时刻多个任务同时在运行。
并发:在同一时间间隔内多个任务都在运行,但是并不会在同一时刻同时运行,存在交替执行的情
况。实现并行的库有:multiprocessing
实现并发的库有:threading
程序需要执行较多的读写、请求和回复任务的需要大量的IO 操作,IO 密集型操作使用并发更好。
CPU 运算量大的程序程序,使用并行会更好。 - IO 密集型和CPU 密集型区别?
IO 密集型:系统运作,大部分的状况是CPU 在等I/O (硬盘/内存)的读/写。
CPU 密集型:大部份时间用来做计算、逻辑判断等CPU 动作的程序称之CPU 密集型。