1. 谈一谈OOP
(1)python的类没有访问权限的问题,也就是说所有的变量都是可访问的。
实际上python有私有的机制,就是在属性前加__,但是这种私有机制实际上也是伪私有,因为它其实是用一个别名来保存这个属性。
例如在类A中的self.__a = 4, 实际上__a被修改成了_A__a保持在类中了。
(2)没有static的说法了,类和实例是区分看待的,一个属性或者方法可以属于类也可以属于实例
class A:
i = ‘class var‘
def __init__(self):
self.i = ‘instance var‘
a = A()
print A.i, a.i
(3)类和实例中的属性和方法实际上都是放在类和实例自身的一个字典中,就是__dict__中,所以他们实际上可以看成不同的两个东西。
类本身也是对象,所以类和实例都可以在程序中的任何地方进行添加,修改和删除。对的你甚至可以用del来删除类中的属性和函数。
(4)那么当你需要使用类或者实例中的属性和方法时,python是如何寻找的呢?
python中的所有的类和实例将会根据他们的继承关系组成一个对象属性树。
树的叶子节点是实例,内部节点根据类之间的继承关系来决定。
当使用A.a的方法来查找一个节点中的某个元素时,它将在这棵树上进行搜索,如果当前节点没有该元素则向上继续搜索直到根节点,如果向上的过程中出现两条以上路径(想想为什么有这种情况),则从左边向上走,如果到根节点还没有找到,那么返回到出现分叉的点再往右一条路向上。
这种寻找方法即使出现了多重继承(其实就是上面说的两条以上路径)也不用担心出现C++中的情况。
2 Python多线程(multi-threading)。这是个好主意吗?
进程:是资源分配的最小单位,创建和销毁开销较大;
线程:是CPU调度的最小单位,开销小,切换速度快;
操作系统将CPU时间片分配给多个线程,每个线程在指定放到时间片内完成。操作系统不断从一个线程切换到另一个线程执行,宏观上看就好像是多个线程一起执行。
Python中由于全局锁 (GIL) 的存在导致,同一时间只有一个获得GIL的线程在跑,其他线程则处于等待状态,这导致了多线程只是在做分时切换,并不能利用多核。
如果是IO密集带阻塞的任务,Python的多线程还是很不错的. 如果是CPU密集, 试试多进程好了.
多线程与多进程的区别:
(1)多进程中同一个变量各自有一份拷贝在每个进程中,互不影响;(2)多线程中,所有变量都由所有线程共享,任何一个变量都可被任何一个线程修改。线程之间共享数据的最大危险在于多个线程同时更改一个变量,把内容改乱。
from multiprocessing import Pool #多进程
from multiprocessing.pool import Pool
from threading import Thread, current_thread #多线程
Python并不支持真正意义上的多线程,Python提供了多线程包。
Python中有一个叫Global Interpreter Lock(GIL)的东西,
它能确保你的代码中永远只有一个线程在执行。经过GIL的处理,会增加执行的开销。这就意味着如果你先要提高代码执行效率,使用threading不是一个明智的选择,当然如果你的代码是IO密集型,多线程可以明显提高效率,相反如果你的代码是CPU密集型的这种情况下多线程大部分是鸡肋。
想要深入详细了解多线程,点击这里:详解Python中的多线程编程_python
想了解一下IO密集和CPU密集可以点击这里:CPU-bound(计算密集型) 和I/O bound(I/O密集型)
3 说明os,sys模块不同,并列举常用的模块方法?
官方文档:
- os模板提供了一种方便的使用操作系统函数的方法
- sys模板可供访问由解释器使用或维护的变量和与解释器交互的函数
另一种回答:
os模块负责程序与操作系统的交互,提供了访问操作系统底层的接口。sys模块负责程序与Python解释器的交互,提供了一系列的函数和变量用户操作Python运行时的环境。
5 Python是如何进行内存管理的
1).对象的引用计数机制
Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。
引用计数增加的情况:
- 一个对象分配一个新名称
- 将其放入一个容器中(如列表、元组或字典)
引用计数减少的情况:
- 使用del语句对对象别名显示的销毁
- 引用超出作用域或被重新赋值
2).垃圾回收
当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。
Python中的垃圾回收是以引用计数为主,标记-清除和分代收集为辅。
- 引用计数:Python在内存中存储每个对象的引用计数,如果计数变成0,该对象就会消失,分配给该对象的内存就会释放出来。
- 标记-清除:一些容器对象,比如list、dict、tuple,instance等可能会出现引用循环,对于这些循环,垃圾回收器会定时回收这些循环(对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边)。
- 分代收集:Python把内存根据对象存活时间划分为三代,对象创建之后,垃圾回收器会分配它们所属的代。每个对象都会被分配一个代,而被分配更年轻的代是被优先处理的,因此越晚创建的对象越容易被回收。
如果你想要深入了解Python的GC机制,点击这里:[转载]Python垃圾回收机制--完美讲解!
3).内存池机制
Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统:
- Pymalloc机制:为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
- 对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。
6 写一个函数, 输入一个字符串, 返回倒序排列的结果
输入: string_reverse(‘abcdef’), 返回: ‘fedcba’,写出你能想到的多种方法
1).利用字符串本身的翻转
def string_reverse1(text=‘abcdef‘):
return text[::-1]
2).把字符串变成列表,用列表的reverse函数
3).新建一个列表,从后往前取
4).利用双向列表deque中的extendleft函数
5).递归
7 按升序合并如下两个list, 并去除重复的元素
list1 = [2, 3, 8, 4, 9, 5, 6]
list2 = [5, 6, 10, 17, 11, 2]
1).最简单的方法用set
list3=list1+list2
print set(list3)
2).递归
先选一个中间数,然后一边是小的数字,一边是大的数字,然后再循环递归,排完序(是不是想起了c里面的冒泡)
8 说一说Python自省。
自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型。简单一句话就是运行时能够获得对象的类型。比如:type()、dir()、getattr()、hasattr()、isinstance()
想要完整的理解Python自省,请点击:Python自省(反射)指南
9 inspect模块主要提供了四种用处:
(1). 对是否是模块,框架,函数等进行类型检查。
(2). 获取源码
(3). 获取类或函数的参数的信息
(4). 解析堆栈
10 With
with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
with open("/tmp/foo.txt") as file:
data = file.read()
13 鸭子类型
Python是动态类型是因为我们在使用变量过程中可以不关心引用的真正类型,直到最后我们真正调用时。
一个动态语言的概念“鸭子类型”,正如开篇所提到的那句话“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子”
在鸭子类型中,关注的不是对象的类型本身,而是他是如何使用的。
例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为鸭的对象,并调用它的走和叫方法。
在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的走和叫方法。
11 静态函数, 类函数, 成员函数的区别?
普通方法,静态方法和类方法,
Python中有三种方法,实例方法、类方法(@classmethod)、静态方法(@staticmethod)。
尽管classmethod和staticmethod非常的相似,但是两者在具体的使用上还是有着细微的差别:classmethod必须使用类对象作为第一个参数,而staticmethod则可以不传递任何参数。
类方法的第一个参数是cls,表示该类的一个实例,静态方法基本上和一个全局函数相同
13 文件读写
with open(‘/path/to/file‘, ‘r‘) as f:
for line in f.readlines():
print(line.strip()) # 把末尾的‘\n’删掉
14 如何进行查询和替换一个文本字符串
可以使用sub()方法来进行查询和替换,sub方法的格式为:sub(replacement, string[, count=0])
replacement是被替换成的文本
string是需要被替换的文本
count是一个可选参数,指最大被替换的数量
例子:
import re
p = re.compile(‘(blue|white|red)’)
print(p.sub(‘colour’,‘blue socks and red shoes’))
print(p.sub(‘colour’,‘blue socks and red shoes’, count=1))
输出:
colour socks and colour shoes
colour socks and red shoes
subn()方法执行的效果跟sub()一样,不过它会返回一个二维数组,包括替换后的新的字符串和总共替换的数量
例如:
import re
p = re.compile(‘(blue|white|red)’)
print(p.subn(‘colour’,‘blue socks and red shoes’))
print(p.subn(‘colour’,‘blue socks and red shoes’, count=1))
输出
(‘colour socks and colour shoes’, 2)
(‘colour socks and red shoes’, 1)
Python pickle模块数据对象持久化操作
Python pickle模块作用是持久化的储存数据。
经常遇到在Python程序运行中得到了一些字符串、列表、字典等数据,想要长久的保存下来,方便以后使用,而不是简单的放入内存中关机断电就丢失数据。python模块大全中的Pickle模块就派上用场了,它可以将对象转换为一种可以传输或存储的格式。
Pickle模块将任意一个Python对象转换成一系统字节的这个操作过程叫做串行化对象。在Pickle模块中有2个常用的函数方法,一个叫做dump(),另一个叫做load()。
1 Python 单例模式
使用模块
其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做:
1 2 3 4 5 6 |
# mysingleton.py class My_Singleton(object): def foo(self): pass my_singleton = My_Singleton() |
将上面的代码保存在文件 mysingleton.py 中,然后这样使用:
1 2 3 |
from mysingleton import my_singleton my_singleton.foo() |
使用 new
为了使类只能出现一个实例,我们可以使用 new 来控制实例的创建过程,代码如下:
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, ‘_instance‘):
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
class Foo(Singleton):
pass
foo1 = Foo()
foo2 = Foo()
print foo1 is foo2 # True
使用装饰器
我们知道,装饰器(decorator)可以动态地修改一个类或函数的功能。这里,我们也可以使用装饰器来装饰某个类,使其只能生成一个实例,代码如下:
from functools import wraps
def singleton(cls):
instances = {}
@wraps(cls)
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class Foo(object):
pass
foo1 = Foo()
foo2 = Foo()
print foo1 is foo2 # True
在上面,我们定义了一个装饰器 singleton,它返回了一个内部函数 wrapper,该函数会判断某个类是否在字典 instances 中,如果不存在,则会将 cls 作为 key,cls(args, *kw) 作为 value 存到 instances 中,否则,直接返回 instances[cls]。
使用 metaclass
元类(metaclass)可以控制类的创建过程,它主要做三件事:
拦截类的创建
修改类的定义
返回修改后的类
使用元类实现单例模式的代码如下:
元类(参考: 深刻理解Python中的元类 )是用于创建类对象的类,类对象创建实例对象时需要一定会调用 __call__ 方法,因此在调用 __call__ 时候保证始终只创建一个实例即可, type 是python中的一个元类。
class Singleton(type):
def __call__(cls, *args, **kwargs):
if not hasattr(cls, ‘_instance‘):
cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instance
class Foo(object):
__metaclass__ = Singleton
foo1 = Foo()
foo2 = Foo()
print foo1 is foo2 # True
- Python 的模块是天然的单例模式,这在大部分情况下应该是够用的,当然,我们也可以使用装饰器、元类等方法
2 闭包
闭包可以实现先将一个参数传递给一个函数,而并不立即执行,以达到延迟求值的目的。
满足以下三个条件:
- 必须有一个内嵌函数;
- 内嵌函数必须引用外部函数中变量;
- 外部函数返回值必须是内嵌函数。
def delay_fun(x, y):
def caculator():
return x+y
return caculator
print(‘返回一个求和的函数,并不求和‘)
msum = delay_fun(3,4)
print(‘调用并求和:‘)
print(msum())
高阶函数可以接收函数做参数
什么是lambda表达式?它有什么好处?
在介绍高阶函数的用处之前,还需要了解另一个概念,没错,就是匿名函数。Python语言支持使用lambda 表达式作为匿名函数。
来看重点代码lambda x: x * x,这行代码就是一个完整的函数。使用lambda声明该行代码是一个lambda表达式,紧跟在后面的是函数的参数,多个参数用逗号,分割;然后使用冒号:分割开参数和函数体。
简单来说,lambda表达式通常是当你需要使用一个函数,但是又不想费脑袋去命名一个函数的时候使用,也就是通常所说的匿名函数。
lambda表达式一般的形式是:关键词lambda后面紧接一个或多个参数,紧接一个冒号“:”,紧接一个表达式。lambda表达式是一个表达式不是一个语句。
想更加详细的了解Python中的Lamdba表达式可以点击这里:Lambda 表达式有何用处?如何使用? - Python
3 装饰器
装饰器是一个工厂函数,接受一个函数作为参数,然后返回一个新函数,其闭包中包含被装饰的函数。有了装饰器,可以提取大量函数中与本身功能无关的类似代码 ( 这块在Flask中用于定义路由的@app.route,就是一个很好的例子),达到代码重用的目的。
装饰器本质上是一个Python函数,它可以让其它函数在不作任何变动的情况下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景。比如:插入日志、性能测试、事务处理、缓存、权限校验等。
有了装饰器我们就可以抽离出大量的与函数功能无关的雷同代码进行重用。
有关于具体的装饰器的用法看这里:装饰器 - 廖雪峰的官方网站
from functools import wraps
def deco(func):
@wraps(func)
def warpper(*args, **kwargs):
print(‘start‘)
func(*args, **kwargs)
print(‘end‘)
return warpper
@deco
def myfunc(parameter):
print("run with %s" % parameter)
myfunc("something")
4 迭代器和生成器
迭代器:是访问集合元素的一种方式,从集合的第一个元素开始访问,直到所有元素被访问结束。其优点是不需要事先准备好整个迭代过程中的所有元素,仅在迭代到某个元素时才开始计算该元素。适合遍历比较巨大的集合。__iter__():方法返回迭代器本身, __next__():方法用于返回容器中下一个元素或数据。
生成器:
生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。它不需要再像上面的类一样写__iter__()和__next__()方法了,只需要一个yiled关键字。
带有yield的函数不再是一个普通函数,而是一个生成器。当函数被调用时,返回一个生成器对象。不像一般函数在生成值后退出,生成器函数在生成值后会自动挂起并暂停他们的执行状态。
- 容器是一系列元素的集合,str、list、set、dict、file、sockets对象都可以看作是容器,容器都可以被迭代(用在for,while等语句中),因此他们被称为可迭代对象。
- 可迭代对象实现了__iter__方法,该方法返回一个迭代器对象。
- 迭代器持有一个内部状态的字段,用于记录下次迭代返回值,它实现了__next__和__iter__方法,迭代器不会一次性把所有元素加载到内存,而是需要的时候才生成返回结果。
- 生成器是一种特殊的迭代器,它的返回值不是通过return而是用yield。
‘‘‘迭代器‘‘‘
print(‘for x in iter([1, 2, 3, 4, 5]):‘)
for x in iter([1, 2, 3, 4, 5]):
print(x)
‘‘‘生成器‘‘‘
def myyield(n):
while n>0:
print("开始生成...:")
yield n
print("完成一次...:")
n -= 1
for i in myyield(4):
print("遍历得到的值:",i)
生成器表达式
(i for i in range(5))
// 返回迭代器
<generator object <genexpr> at 0x7ff3e8f0d960>
列表解析,返回list
[i for i in range(5)]//
返回list
[0,
1, 2, 3, 4]
4 生成器
生成器不会把结果保存在一个系列中,而是保存生成器的状态,在每次进行迭代时返回一个值,直到遇到StopIteration异常结束。
生成器语法
首先,用[]推导出来的是迭代器(Iterables)。用()推导出来的是生成器(Generators)。
生成器表达式: 通列表解析语法,只不过把列表解析的[]换成()
生成器表达式能做的事情列表解析基本都能处理,只不过在需要处理的序列比较大时,列表解析比较费内存。
>>> gen = (x**2 for x in range(5))
>>> gen
<generator object <genexpr> at 0x0000000002FB7B40>
>>> for g in gen:
... print(g, end=‘-‘)
...
0-1-4-9-16-
>>> for x in [0,1,2,3,4,5]:
... print(x, end=‘-‘)
...
0-1-2-3-4-5-
生成器函数: 在函数中如果出现了yield关键字,那么该函数就不再是普通函数,而是生成器函数。
但是生成器函数可以生产一个无线的序列,这样列表根本没有办法进行处理。
yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator。
题外话: 生成器是包含有__iter()和next__()方法的,所以可以直接使用for来迭代,而没有包含StopIteration的自编Iter来只能通过手动循环来迭代。
看到上面的结果,现在你可以很有信心的按照Iterator的方式进行循环了吧!
在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
- 按照鸭子模型理论,生成器就是一种迭代器,可以使用for进行迭代。
- 第一次执行next(generator)时,会执行完yield语句后程序进行挂起,所有的参数和状态会进行保存。再一次执行next(generator)时,会从挂起的状态开始往后执行。在遇到程序的结尾或者遇到StopIteration时,循环结束。
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b) # yield b
a, b = b, a + b
n = n + 1
return ‘done’
例如对于大文件内搜索关键词,generator 和 yield 的组合更灵活:
def search(keyword, filename):
# Temporarily read the file
f = open(filename, ‘r‘)
# Loop through the file by line
for line in f:
# Return the line with keyword
if keyword in line:
yield line
f.close()