Python面试题整理
1. 元类
1.1. Python中类方法、类实例方法、静态方法有何区别?
类方法: 是类对象的方法,在定义时需要在上方使用 @classmethod 进行装饰,形参为cls,表示类对象,类对象和实例对象都可调用
类实例方法: 是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身;
静态方法: 是一个任意函数,在其上方使用 @staticmethod 进行装饰,可以用对象直接调用,静态方法实际上跟该类没有太大关系。
class Foo(object): def instance_method(self): print("实例方法") @staticmethod def static_method(): print("是静态方法") @classmethod def class_method(cls): print("是类方法")
foo = Foo()
foo.instance_method()
foo.static_method()
foo.class_method()
print('----------------')
Foo.static_method()
Foo.class_method()
静态方法和类方法可以通过对象或者类名直接调用 ## 1.2. 写一个类,并让它尽可能多的支持操作符? 下面的例子实现了一个自定义容易,可以初始化,求解数据长度,设置数值,返回数值,增加数据,显示数据等操作。 ```python # 自定义容器 class Function: def __init__(self, values=None): if values: self.values = values else: self.values = [] def __len__(self): return len(self.values) def __getitem__(self, key): return self.values[key] def __setitem__(self, key, value): self.values[key] = value def __delitem__(self, key): del self.values[key] def add(self, value): self.values.append(value) def display(self): for item in self.values: print(item) f = Function([1,2,3]) f.display()
1.3. 介绍Cython,Pypy, Numba各有什么缺点
参考here
- Cython: Cython是让Python脚本支持C语言扩展的编译器,Cython能够将Python+C混合编码的.pyx脚本转换为C代码,主要用于优化Python脚本性能或Python调用C函数库。
- Pypy:Pypy最重要的一点就是Pypy集成了JIT。同时针对CPython的缺点进行了各方面的改良,性能得到很大的提升。了解JIT技术的人都应该对Pypy很有好感。Pypy的优点是对纯Python项目兼容性极好,几乎可以直接运行并直接获得性能提升(官方宣称为6.3倍……但是实际上没感觉有这么多);缺点是对很多C语言库支持性不好,Pypy社区一直有相关讨论。
- Numba:Numba是一个库,可以在运行时将Python代码编译为本地机器指令,而不会强制大幅度的改变普通的Python代码。
从通用性、速度、易用性来对比Cython、Pypy和Numba三个方案。
- 通用性:在三个方案中,Cython和Numba的兼容性都非常好,而Pypy对于部分库的支持较差(如Numpy,Scipy)。
- 速度:这三种方案的速度相差不大,通常来说Cython要快于Pypy,尤其是对于部分C扩展。Pypy要快于Numba,但针对于纯数值计算的工作,Numba甚至还要快于Cython。
- 易用性:易用性最好的无疑是Pypy,Pypy是Python的解释器,我们针对纯Python使用Pypy,除了Pypy不支持的部分库外,不需要进行任何改动。然后是Numba,Numba的基本使用方法就是给函数加一个装饰器,易用性也很高,最后是Cython,因为Cython中需要使用Python+C混合编码,如果需要移植,代价会很大。
总结:Pypy是非常理想的Python解释器,最大的瑕疵就是对部分库的兼容问题。Cython是一种Python + C的便利性组合,转为C编译的扩展执行效率非常高,但使用相对麻烦,移植CPython项目代价较高。Numba更适合针对性优化,效率高,并且不会大幅度的改变普通的Python代码。所以三者实在没法说谁最优秀,只是在不同的方向针对性及适用性更高
我的总结:
- Cython对C支持比较好。但是移植代价大。
- Pypy继承了JIT(Just In Time),对纯Python项目兼容性很好。但是对部分库的兼容不好,比如Numpy,Scipy。
- Numba更适合纯数值计算
1.4. 请描述抽象类和接口类的区别和联系
- 接口类中定义了一些接口(就是函数,但这些函数都没有具体的实现),子类继承接口类,并且实现接口中的功能。
- 抽象类和接口类一样是一种规范,规定子类应该具备的功能。在Python中,抽象类和接口类没有明确的界限。若是类中所有的方法都没有实现,则认为这是一个接口,若是有部分方法实现,则认为这是一个抽象类。抽象类和接口类都仅用于被继承,不能被实例化。
1.4.1. 接口类
参考here
python中通过abc模块实现抽象类,python原生仅支持抽象类,当抽象类中所有方法都没有实现的时候,就是一个接口类,如下面的例子:Operate_database是一个接口类。
需要注意:子类需要实现接口中所有的方法,否则在实例化子类的时候会报错。
from abc import ABCMeta,abstractmethod class Operate_database(metaclass=ABCMeta): # 接口类 @abstractmethod def query(self, sql): pass @abstractmethod def update(self, sql): pass class Operate_oracle(Operate_database): # 需要实现update,query这两个方法。 def update(self, sql): print('update oracle : %s' % sql) def query(self, sql): operate_obj.query(sql) oracle = Operate_oracle() # 由于没有实现接口中的所有方法,在这一步就会报错 oracle.update('abccc')
1.4.2. 抽象类
抽象类中的子类也要实现抽象类中所有的抽象方法。
from abc import ABCMeta,abstractmethod class Operate_database(metaclass=ABCMeta): # 抽象类 log_path = '/tmp/db.log' def connect(self): print('connect db ...') @abstractmethod def query(self, sql): pass @abstractmethod def update(self, sql): pass class Operate_oracle(Operate_database): # 没有实现 query 方法 def update(self, sql): print('update oracle : %s' % sql) def query(self, sql): operate_obj.query(sql) oracle = Operate_oracle() # 由于没有实现接口中的所有方法,在这一步就会报错 oracle.connect()
1.5. Python中如何动态获取和设置对象的属性? getattr setattr hasattr
class X: name = 'None' x = X if hasattr(x, 'name'): print(getattr(x, 'name')) setattr(x, 'name', 'mary') print(getattr(x, 'name'))
None mary
2. 内存管理与垃圾回收机制
2.1. 哪些操作会导致Python内存溢出,怎么处理?
参考here
- 如果在循环引用中的对象定义了del,那么python gc不能进行回收,因此,存在内存泄漏的风险。gc模块来查看不能回收掉的对象的详细信息。
- 对象被另一个生命周期特别长的对象所引用。
可以通过gc,objgraph模块来查找内存是如何泄露的。
2.2. 关于Python内存管理,下列说法错误的是 B
A,变量不必事先声明
B,变量无须先创建和赋值而直接使用
C,变量无须指定类型
D,可以使用del释放资源
2.3. Python的内存管理机制及调优手段?
2.3.1. 内存管理机制:引用计数、垃圾回收、内存池
引用计数
当一个python对象被引用的时候,其引用计数增加1.当其不被一个变量引用的时候,则引用计数减1,当引用计数等于0时,该对象被删除。
垃圾回收
Python 中垃圾回收机制: 引用计数(主要), 标记清除、分代收集(辅助)
- 也是一种垃圾收集机制,而且也是一种最直观、最简单的垃圾收集技术。当Python的某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为1,如果引用被删除,对象的引用计数为0,那么该对象就可以被垃圾回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了
- 标记清除和分带回收here
2.3.2. 调优手段
- 手动垃圾回收
- 调高垃圾回收阈值
- 避免循环引用
2.4. 内存泄漏是什么?如何避免?
内存泄漏指由于疏忽或者错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存之后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成内存的浪费。
有del()函数的对象间的循环引用是导致内存泄漏的主凶。不使用一个对象的时候可以使用 del object来删除一个对象的引用计数,就可以有效防止内存泄漏问题。
python的gc模块可以查看不能回收的对象的详细信息。使用sys.getrefcount(obj)来获取对象的引用计数,通过返回值是否为0来判断是否内存泄漏。
3. 函数
3.1. python常见的列表推导式?
[表达式 for 变量 in 列表] 或者 [表达式 for 变量 in 列表 if 条件]
3.2. 简述read、readline、readlines的区别?
read 读取整个文件
readline 读取下一行
readlines 读取整个文件到一个迭代器以供我们遍历。
3.3. 什么是Hash(散列函数)?
hash(散列、杂凑)函数,是将任意长度的数据映射到有限长度的域上。直观解释起来,就是对一串数据m进行杂糅,输出另一段固定长度的数据h,作为这段数据的特征(指纹)。
散列函数(英语:Hash function)又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表
hash() 函数可以应用于数字、字符串和对象,不能直接应用于 list、set、dictionary。在 hash() 对对象使用时,所得的结果不仅和对象的内容有关,还和对象的 id(),也就是内存地址有关。
hash('test') hash(1)
3.4. python为什么不支持函数重载
函数重载主要是为了解决两个问题。 1。可变参数类型。 2。可变参数个数。
函数重载的一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。
那么对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。
那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。
好了,鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。
对于java
class Person{ public void Student(){ System.out.println(***生'); } public void Student(String name){ System.out.println(name) } }
对于python,要实现函数重载,参数个数不同,可以使用缺省函数的方式,比如下面的方式。
def Student(name=None): if name: print(name) else: print(***生') Student() Student('Tom')
3.5. 写一个函数找出一个整数数组中,第二大的数
思路:首先找出最大的数,然后遍历数组,找比最大数小的数中最大的数。
def secondMaxNumber(nums): maxNum = max(nums) secondMaxNum = float('-Inf') for num in nums: if num > secondMaxNum and num < maxNum: secondMaxNum = num return secondMaxNum nums = [1,2,3,4,3,2,1,7,8,9,10,2,3,3] secondMaxNumber(nums)
3.6. 手写一个判断时间的装饰器
import datetime def timeChecker(func): def wrapper(*args, **kwargs): if datetime.datetime.now().year == 2020: func(*args, **kwargs) else: print("时间已过时") return wrapper @timeChecker def test(name): print("hello, new %s" % name) test('aaa')
3.7. filter()
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。
该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新的迭代器中。
以下是 filter() 方法的语法: filter(function, iterable) 参数 function -- 判断函数。 iterable -- 可迭代对象。
list(filter(lambda x: x%2==0, [1,2,3,4,5])) # [2,4]
# lambda x: x%2==0等价于 def is_old(x): return x%2==0
3.8. 编写函数的4个原则
- 函数设计要尽量短小
- 函数声明要做到合理、简单、易于使用
- 函数参数设计应该考虑向下兼容
- 一个函数只做一件事情,尽量保证函数语句粒度的一致性
3.9. 函数调用参数的传递方式是值传递还是引用传递?
python有4中参数传递方式:位置参数、默认参数、可变参数、关键字参数here
函数的传值分为两种:值传递和引用传递。
- 值传递:不可变参数采用值传递,比如整数,字符串等不可变对象。是通过拷贝进行传递的。
- 引用传递:比如列表,字典这样的对象是通过引用传递的。
3.10. 如何在function里面设置一个全局变量
globals() # 返回包含当前作用余全局变量的字典。
global 变量 设置使用全局变量
3.11. 缺省参数
缺省参数指在调用函数的时候没有传入参数的情况下,调用默认的参数;在调用函数的同时赋值时,所传入的参数会替代默认参数。
*args是不定长参数,它可以表示输入参数是不确定的,可以是任意多个。
**kwargs是关键字参数,赋值的时候是以键值对的方式,参数可以是任意多对。在定义函数的时候不确定会有多少参数会传入时,就可以使用两个参数
def Student(name='Tony'): # name就是缺省参数 if name: print(name) else: print(***生') Student() Student('Tom')
3.12. 被装饰的函数带参数的时候
def log(func): # 接受函数作为参数 def wrapper(name, age): print("call %s" % func.__name__) print(name, age) return func() return wrapper # 返回一个函数 @log def now(): print("2020-01-30") now('Tom', 20) # 此时会调用wrapper函数,然后将参数传递进去。接着执行wrapper方法体,然后执行func()函数,然后返回func()函数的返回值
call now Tom 20 2020-01-30
3.13. 为什么函数名字可以当做参数使用?
Python中一切皆对象,函数名是函数在内存中的空间,也是一个对象。
3.14. Python中pass语句的作用是什么
python中pass是空语句,是为了保持程序结构的完整性,pass不做任何事情,一般只用作占位符号。
在编写代码时只写框架思路,具体实现还未编写就可以用pass进行占位,程序不报错,不会进行任何操作。
for i in 'Python': if i == 't': pass print('pass') else: print("当前字母,",i)
当前字母, P 当前字母, y pass 当前字母, h 当前字母, o 当前字母, n
3.15. 有这样一段代码,print c会输出什么,为什么
a = 10 b = 20 c = [a] a = 15 print(c) # [10]
3.16. map() 会根据提供的函数对指定序列做映射。
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
map() 函数语法:map(function, iterable, ...)
参数
function -- 函数 iterable -- 一个或多个序列
list(map(lambda x: x%2, range(7))) list(map(lambda x,y: x+y, [1,3,5],[2,4,6]))
[0, 1, 0, 1, 0, 1, 0] [3, 7, 11]
3.17. reduce
reduce() 函数会对参数序列中元素进行累积。
函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
reduce() 函数语法:reduce(function, iterable[, initializer])
参数
function -- 函数,有两个参数 iterable -- 可迭代对象 initializer -- 可选,初始参数
from functools import reduce reduce(lambda x,y: x+y, [1,2,3,4,5], 10)
在Python 3里,reduce() 函数已经被从全局名字空间里移除了,它现在被放置在fucntools 模块里
3.18. 回调函数,如何通信的?
回调函数是把函数的指针(地址)作为参数传递给另一个参数,将整个参数当做一个对象,赋值给调用的函数。
3.19. python主要的内置数据类型都有哪些?
内建类型:布尔类型,数字,字符串,列表,元组,字典,集合
3.20. print(dir('a'))
输出结果是'a'的内建方法。
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
3.21. hasattr, getattr, setattr
hasattr(object,name)函数:
判断一个对象里面是否有name属性或者name方法,返回bool值,有name属性(方法)返回True,否则返回False。
class function_demo(object): name = 'demo' def run(self): return "hello function" functiondemo = function_demo() res = hasattr(functiondemo, "name") # 判断对象是否有name属性,True res = hasattr(functiondemo, "run") # 判断对象是否有run方法,True res = hasattr(functiondemo, "age") # 判断对象是否有age属性,False print(res)
getattr(object, name[,default])函数:
获取对象object的属性或者方法,如果存在则打印出来,如果不存在,打印默认值,默认值可选。注意:如果返回的是对象的方法,则打印结果是:方法的内存地址,如果需要运行这个方法,可以在后面添加括号().
functiondemo = function_demo() getattr(functiondemo, "name")# 获取name属性,存在就打印出来 --- demo getattr(functiondemo, "run") # 获取run 方法,存在打印出方法的内存地址 getattr(functiondemo, "age") # 获取不存在的属性,报错 getattr(functiondemo, "age", 18)# 获取不存在的属性,返回一个默认值 res = getattr(functiondemo, "run") # 获取run方法的内存地址 res() # 运行这个方法
setattr(object, name, values)函数:给对象的属性赋值,若属性不存在,先创建再赋值
class function_demo(object): name = "demo" def run(self): return "hello function" functiondemo = function_demo() res = hasattr(functiondemo, "age") # 判断age属性是否存在,False print(res) setattr(functiondemo, "age", 18) # 对age属性进行赋值,无返回值 res1 = hasattr(functiondemo, "age") # 再次判断属性是否存在,True
3.22. 一句话解决阶乘函数?
# 计算阶乘n! from functools import reduce reduce(lambda x,y: x*y, range(1, n+1))
3.23. lambda
lambda函数是匿名函数,使用lambda函数能够创建小型匿名函数,省略了def函数声明。
lambda函数是一个可以接受任意多个参数(包括可选参数)并且返回单个表达式值得函数。
- lambda函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在一处使用,连名字都很随意的情况下。
- 匿名函数,一般是用来给filter,map这样的函数式编程服务
- 作为回调函数,传递给某些应用,比如消息处理。
# 求两个数的和 (lambda x,y: x+y)(2,3) # 5
3.24. 递归函数停止的条件
递归的终止条件一般定义在递归函数内部,在递归调用之前要做一个条件判断,根据判断的结果选择是继续调用自身还是return,返回终止递归
终止的条件:1)递归的次数是否达到某一个限定值。2)判断运算的结果是否达到某个范畴,根据设计目的来选择。
3.25. 延迟绑定
首先,看下面一段代码:
def multipliers(): return [lambda x : i*x for i in range(4)] print ([m(2) for m in multipliers()] )
上面的代码相当于
def multipliers(): funcs = [] for i in range(4): def ll(x): return i * x funcs.append(ll) return funcs a = multipliers() def multipliers(): funcs = [] for i in range(4): def ll(x): return i * x funcs.append(ll) return funcs a = multipliers() for m in a: print(m.__closure__[0].cell_contents) # 3 因为i的值此时为3. print(m(2))
运行代码,multipliers()函数返回的是一个列表对象,列表中包含4个元素,每一个是lambda匿名函数。对于ll函数而言,i这个变量在执行multipliers的时候仍然存在于内存中,并且经过4次循环之后,i的值变成了3.此时运行嵌套的ll函数的时候,才会查找i的值。
解决方案:因为发生这种原因主要是因为i的值是ll函数外部的变量,从而导致在调用的时候才在内存中找这个值。因此,可以将i作为一个参数传递给ll函数,此时函数内部的i就不会受到外部i值的影响。
def multipliers(): funcs = [] for i in range(4): def ll(x,i=i): return i * x funcs.append(ll) return funcs a = multipliers() for m in a: print(m(2))
此时,通过在for循环的时候,就传递i的值,此时i不依赖与外部的i,此时函数ll不是一个闭包(因为ll内部没有调用外部的参数)。
4. 设计模式
4.1. 对设计模式的了解
设计模式是经过总结和优化的,经常会碰到一些编程问题的可重用解决方案。一个设计模式并不像一个类或者一个库那样可以直接作用于我们的代码,设计模式更高级,他是一种必须在特定情形下实现的一种方法模板。比较常见的是工厂模式和单例模式。
4.2. 单实例模式实现
函数装饰类实现
from functools import wraps def singleton(cls): instances = {} @wraps(cls) def wrapper(*args, **kargs): if cls not in instances: instances[cls] = cls(*args, **kargs) return instances[cls] return wrapper @singleton class Foo(): pass foo1 = Foo() foo2 = Foo() print(id(foo1)) print(id(foo2))
使用class,new实现的单例模式
class A(object): __instance = None def __new__(cls,*args,**kwargs): if cls.__instance is None: cls.__instance = objecet.__new__(cls) return cls.__instance else: return cls.__instance
上面这个版本是python2版本,还不清楚具体的实现原理。
4.3. 单例模式的应用场景
资源共享情况下,避免由于资源操作时导致的性能或损耗等,如日志文件,应用配置。控制资源的情况下,方便资源之间的互相通信。如线程池等。
- 网站的计数器。
- 应用配置
- 多线程池
- 数据库配置数据库连接池
- 应用程序的日志应用
4.4. 用一行代码生成[1,4,9,16,25,36,49,64,81,100]
[x**2 for x in range(1, 11)]
4.5. 对装饰器的理解,并写出一个计时器记录方法执行性能的装饰器?
装饰器本质上是一个callable object,他可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值是一个函数。
import time from functools import wraps def log(func): @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter () ret = func(*args, **kwargs) end = time.perf_counter() print("start:%s, end:%s, 耗时%s" % (start, end, end-start)) return ret return wrapper @log def foo(): print('xxxxxx') foo()
上面代码在foo函数上增加了一个计时的装饰器,该装饰器可以统计函数运行的时间。好处是foo函数的逻辑没有增加,同时可以很方便的计算其他函数的运行耗时,很大程度上简化了。
装饰器应用场景:插入日志,性能测试,事务处理,缓存,权限的校验等场景,有了装饰器就可以抽离出大量的与函数功能本身无关的雷同代码并发并继续使用。
4.6. 什么是闭包?
在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。
4.7. 生成器和迭代器
参考已经整理的内容。
4.8. 请用一行代码 实现将1-N 的整数列表以3为单位分组
首先,创建一个列表,列表为[1,2,3,4,...,N],然后,使用列表切片来每3位获取一次。
# 1-100(不包含100),每3个元素分组一次。 [[x for x in range(1,100)][i:i+3] for i in range(0, 97, 3)]
5. 面向对象
面向对象是相对于面向过程而言的,面向过程语言是一种基于功能分析的语言,以算法为中心的程序设计方法,而面向对象是一种基于结构分析的,以数据为中心的程序设计思想。在面向对象中有一个很重要的概念,叫做类,面向对象的三大特性:封装,继承,多态。
5.1. Python中的可变对象和不可变对象
不可变对象:该对象所指向的内存中的值是不可改变的,当改变该对象时,相当于把原来的值复制一份后然后进行改变,此时会开辟一个新的地址,变量再指向这个新的地址。
可变对象:该对象所指向的内存中的值是可以改变的,引用改变后,实际上是所指向的值发生了改变,并没有发生复制行为,也没有开辟新的内存。
Python中,不可变类型包括int,float,str,tuple。list,dict,set是可变类型
5.2. Python的魔法方法
参考blog
5.3. 面向对象中怎么实现只读属性?
方法一:通过公有方法提供一个读取数据的接口
class Person: def __init__(self, age): self.__age = age def getAge(self): return self.__age a = Person(12) a.getAge()
此时,只能在实例化对象的时候赋值,然后通过公有方法访问数据。
方法二:@property
class MyCls(object): __weight = 50 @property def weight(self): return self.__weight m = MyCls() print(m.weight)
6. 正则
6.1. 正则匹配ip地址
import re p = re.compile(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', re.S) st = '114.252.354.128你好呀' result = re.search(p, st)
上面这个没有判断数字大小,即数字应该在0-255之间。下面这个比较复杂的是在网络上找到的方法。
import re p = re.compile(r'(?<![\.\d])(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?![\.\d])', re.S) st = '114.252.123.128你好呀' result = re.search(p, st) result
6.2. 出现多次的内容只保留一次
import re a = 'abbbccc' res = re.sub(r'b+', 'b', a) res
6.3. find 返回字符串中第一次出现要查找的子串的位置,如果没有找到,就返回-1.
函数原型:
str.find(substr [,pos_start [,pos_end ] ] )
返回str中第一次出现的substr的第一个字母的标号,如果str中没有substr则返回-1,也就是说从左边算起的第一次出现的substr的首字母标号。
参数说明:
str:代表原字符串 substr:代表要查找的字符串 pos_start:代表查找的开始位置,默认是从下标0开始查找 pos_end:代表查找的结束位置
'aabbcc.find('bb')' # 2
6.4. index 和find一样,不同的是,没有找到抛出异常。
index()函数类似于find()函数,在Python中也是在字符串中查找子串第一次出现的位置,跟find()不同的是,未找到则抛出异常。
函数原型:
str.index(substr [, pos_start, [ pos_end ] ] )
参数说明:
str:代表原字符串 substr:代表要查找的字符串 pos_start:代表查找的开始位置,默认是从下标0开始查找 pos_end:代表查找的结束位置
例子:
'acdd l1 23'.index(' ') # 4
6.5. rfind()返回str中最后出现的substr的第一个字母的标号
函数原型:
str.rfind( substr [, pos_start [,pos_ end ] ])
返回str中最后出现的substr的第一个字母的标号,如果str中没有substr则返回-1,也就是说从右边算起的第一次出现的substr的首字母标号。
参数说明:
str:代表原字符串
substr:代表要查找的字符串
pos_start:代表查找的开始位置,默认是从下标0开始查找
pos_end:代表查找的结束位置
同理,rindex()
6.6. 用Python匹配HTML tag的时候,<.> 和 <.?> 有什么区别
第一个代表贪心匹配,第二个代表非贪心;
?在一般正则表达式里的语法是指的"零次或一次匹配左边的字符或表达式"相当于{0,1}
而当?后缀于*,+,?,{n},{n,},{n,m}之后,则代表非贪心匹配模式,也就是说,尽可能少的匹配左边的字符或表达式,这里是尽可能少的匹配.(任意字符)
所以:第一种写法是,尽可能多的匹配,就是匹配到的字符串尽量长,第二中写法是尽可能少的匹配,就是匹配到的字符串尽量短。
比如<tag>tag>tag>end,第一个会匹配<tag>tag>tag>,第二个会匹配<tag>。</tag></tag></tag>
6.7. 正则表达式贪婪与非贪婪模式的区别?
贪婪模式:
定义:正则表达式去匹配时,会尽量多的匹配符合条件的内容
标识符:+,?,*,{n},{n,},{n,m}
匹配时,如果遇到上述标识符,代表是贪婪匹配,会尽可能多的去匹配内容
非贪婪模式:
定义:正则表达式去匹配时,会尽量少的匹配符合条件的内容 也就是说,一旦发现匹配符合要求,立马就匹配成功,而不会继续匹配下去(除非有g,开启下一组匹配)
标识符:+?,??,*?,{n}?,{n,}?,{n,m}?
可以看到,非贪婪模式的标识符很有规律,就是贪婪模式的标识符后面加上一个?
6.8. 匹配指定开头和结尾的内容
import re s1='_aai0efe00' res=re.findall('^[a-zA-Z_].*?\d$',s1) print(res) # _aai0efe00
6.9. 过滤评论中的表情
import re pattern = re.compile(u'[\uD800-\uDBFF][\uDC00-\uDFFF]') pattern.sub('',text)
这里不清楚是过滤哪里的表情,如果是微博,微博表情符号中一般数字母和汉字,长度一般为1-5个,所以需要看具体的评论中的表情符号范围。
6.10. match()和search()函数的区别
match函数会从字符串开头开始匹配,匹配成功返回结果。否则,返回None
search函数会查找整个字符串进行匹配,只需找到第一个匹配成功的对象就返回,然后可以使用group(num)得到匹配的字符串;否则,返回None。
7. 系统编程
7.1. python进程相关内容
参考我的另一篇bloghere
7.2. 谈谈多进程,多线程,协程的理解。
进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,拥有自己独立的内存空间,所有进程之间数据不共享,开销大。
线程:cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在。一个进程至少包含一个线程,叫主线程。而多个线程共享内存(数据共享,共享全局变量),从而极大提高程序的运行效率
协程:是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度的时候,将寄存器上下文和栈保存到其他地方,再切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈,基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常的快。
7.3. Python异步的使用场景
- 不涉及共享资源,或者对共享资源只读,也就是非互斥操作
- 没有时序上的严格关系
- 常用于IO操作等耗时操作,因为比较影响客服体验和使用性能
- 不影响主线程逻辑
- 不需要原子操作,或者可以通过其他方式控制原子性。
原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程切换。
7.4. 什么是多线程竞争?
线程是非独立的,同一个进程里,线程是数据共享的,当各个线程访问数据资源时会出现竞争状态,即数据几乎同时被多个线程占用,从而造成数据混乱,也就是所谓的线程不安全。
要解决多线程竞争问题,关键是“锁”。
锁的好处:确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整的执行,能解决多线程资源竞争下的原子操作问题。
锁的坏处:阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大下降了。
锁的致命问题:死锁。
7.5. 多线程交互访问数据的时候,如何做到访问到了就不再访问?
简单来说,也就是避免重读的问题。创建一个已访问数据的列表,用于存储已经访问的数据,然后加上互斥锁,在所线程访问数据的时候,首先查看已访问的数据列表中是否存在,如果存在就直接跳过即可。
7.6. 线程安全,互斥锁。
线程安全:是指在多线程的环境下,能够保证多个线程同时执行程序依旧运行正确,而且要保证对于共享的数据可以由多个线程存取,但是同一时刻只能有一个线程进行存取。
互斥锁:保证每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
7.7. 同步,异步,阻塞,非阻塞
参考here
- 同步:一个进程或者线程在提交任务或发出调用后,要等待返回的最终结果后才算执行完毕,才会继续执行下一步操作
- 异步:当一个进程或者线程发起一个异步功能调用,或者说提交任务的方式是异步的,不会立即得到结果,而是继续执行下一步操作。当异步功能结束的时候,通过状态、通知或者回调函数来通知调用者。
阻塞是一种状态,一种线程或者进程的状态,不能和同步混淆
- 阻塞:当进程或线程发起调用或者提交任务后,结果返回之前,调用者(线程或进程)就会被挂起,只有等到有结果后才会再将阻塞的线程激活。
- 非阻塞:就算不能立即得到结果也不会将调用者挂起,而是会继续执行下一步操作。
7.8. 僵尸进程和孤儿进程
- 孤儿进程:父进程退出后,子进程还在运行的这些进程就是孤儿进程。比如上面的2.1中的例子,没有调用join方法的时候,主进程先执行完毕,然后退出,此时的子进程还在运行。
- 僵尸进程:被使用terminate方法强制终止的进程为僵尸进程。只能等待主程序结束,这个僵尸进程才算消失。
如何避免僵尸进程??
- 使用wait调用来读取子进程状态,在multiprocessong.Process产出的进程可以通过子进程调用join()方法来wait。也就是在子进程调用terminate的后面调用join方法。
- 结束父进程。当父进程结束后,僵尸进程也随之被消除。
7.9. python中进程与线程的使用场景?
- 多进程适合CPU密集操作(比如浮点数运算,CPU操作命令比较多)
- 多线程适合IP密集型操作(比如爬虫,读写数据操作比较多)
IO密集型:系统运行,大部分的状况是等待磁盘的读写
CPU密集型:大部分时间用来计算、逻辑判断等CPU动作的程序。
7.10. 线程是并发还是并行,进程是并发还是并行?
线程是并发的,进程是并行的。
并行:不会再同一时刻同时运行,存在交替执行的情况
并发:同一时刻多个任务同时运行。
进程之间相互独立,是系统分配资源的最小单位。同一个进程中的所有线程共享资源。
8. 网络编程
8.1. 怎么实现强行关闭客户端和服务器之间的连接?
在socket通信过程中不断循环检测一个全局变量(开关标记变量),一旦标记变量变为关闭,就调用socket的close方法,从而达到关闭连接的目的(S 目前不晓得这种回答是否正确)
8.2. 描述浏览器访问www.baidu.com的过程
浏览器通过DNS解析得到Baidu的IP地址。(DNS首先查询DNS Cache,接着查看本地硬盘里面的host文件,最后联系DNS的服务器;得到IP地址之后返回给浏览器。)
联系DNS服务器:DNS将查询打包,通过UDP传输,通过IP传输数据包和路由选择,最终将要查询的内容送到DNS的服务器,通过DNS服务器得到Baidu的IP地址,然后返回给浏览器。
浏览器拿到IP地址之后,浏览器与该IP建立TCP连接。(需要三次握手,每次“握手”的数据包是通过IP来传输的。)
浏览器发送http请求。
服务器将请求信息返回给浏览器
TCP连接释放
浏览器解析服务器返回内容,并将页面渲染后呈现给用户
涉及的协议:
- 应用层:HTTP,DNS
- 传输层:TCP,UDP
- 网络层:IP,ICMP,ARP
8.3. POST请求,GET请求区别
主要参考here
GET和POST使用上的区别:
- GET使用URL传递参数,而POST将数据放入在BODY中。这个是因为HTTP协议用法的规定,不是他们本身区别。
- GET方法提交的数据有长度限制,而POST的数据则可以非常大。这个是因为操作系统和浏览器设置的不同引起的,也不是GET和POST请求的本质区别。
- POST比GET安全,因为数据在地址栏上不可见。这个依然不是本质区别。
本质区别是:GET是幂等的,POST是不幂等的。幂等性是指一次和多次请求某一资源应该具有同样的副作用。简单来说意味着同一个URL的多个请求应该返回同样的结果。
引入幂等主要是为了处理同一个请求重复发送的情况,比如在请求响应之前失去连接,如果请求方法是幂等的,就可以放心重发一次请求。这也是浏览器再刷新时遇到POST时会给用户一个提示的原因:POST是不幂等的,重复请求可能会造成意料不到的后果。
8.4. Cookie和Session的区别
背景: HTTP是一个无状态协议,上一次请求和这一次请求没有任何关系,所以服务器端要记录用户状态时,就需要某种机制来识别具体的用户,这个机制就是Session。
session是一个抽象概念,开发者为了实现中断和继续的操作,将用户和服务器之间一对一的交互抽象为对话,这就是session的概念。
而Cookie是一个实际存在的东西,是http协议中定义在header中的字段。
参考here
客户端访问服务器的流程如下:
- 客户端发送一个http请求到服务器
- 服务器接受客户端请求后,建立一个session,并发送一个http响应到客户端,这个响应头中就包含了set-cookie头部,该头部包含session id。
- 在客户端发起的第二次请求,此时浏览器就会自动在请求头中增加cookie信息。
- 服务器接收到请求后,分解cookie,验证信息,核对成功后就返回response给客户端。
需要注意,session因为session id的存在需要借助cookie实现,但是这并非必要的,如果客户端禁用cookie,此时可以使用URL重写的技术来进行回话跟踪,也就是每次HTTP交互,URL后面都附加一个诸如sid=xxxx的参数,服务器据此来识别用户。
8.5. HTTP状态码
常见的:
- 200 请求成功
- 404 请求资源不存在
归类
- 1: 服务器接到请求需要请求者继续执行操作。
- 100 continue
- 2:操作被成功接收并处理。
- 200:OK
- 3:要完成请求需要进一步操作
- 301:请求的网页已永久移动到新位置
- 4:客户端错误,请求包含错误或无法完成请求
- 403:服务器禁止请求
- 404:请求资源不存在
- 5:服务器错误:
- 500:服务器内部错误
- 503:服务器目前无法使用
8.6. 为什么TCP4次挥手时等待为2MSL?
参考here
首先,在第四次握手的时候,客户端向服务器端发送确认的ACK信号,当服务器收到的时候,即认为双方达成一致,都可以释放连接。此时服务器端可以安全的释放TCP连接的资源,端口号,所以服务器端不需要等待,直接释放资源。而客户端不知道服务器端是否接收到自己的ACK确认信号,此时有两种可能,一种是如果服务器端没有接收到ACK,那么服务器端会超时重发ACK,FIN信号;另一种是服务器端接收到了ACK,此时服务器不会发送任何信息。
无论这两种的哪种情况,都需要等待,需要取两种情况等待时间的最大值,从而应对最坏的情况。而最大的等待时间就是:从客户端发送ACK到服务器端最多需要1MSL,超过这个时间B会重发FIN。服务器重发的FIN最多经过1MSL到达客户端。
因此,在没有网络故障情况下,最多只需等待2MSL就可以知道服务器端是否接收到自己的ACK。
如果不等待2MSL,客户端直接释放TCP连接资源和端口号,此时第二个连接可能会重连刚断开的服务器,这样依然存活在网络里面的老TCP报文可能会干扰第二个连接。TCP必须防止某个连接的重复报文在连接终止后出现,所以要等待2MSL让老的TCP连接的报文都死掉。
8.7. HTTP和HTTPS的区别
- HTTP是明文传输的,数据都是未加密的,安全性比较差。HTTPS数据传输过程是加密的,安全性较好。
- HTTP页面响应速度比HTTPS快,主要是HTTP包使用TCP 3次握手建立连接,客户端和服务端交换3个包,而HTTPS除了TCP的3个包,还要加上ssl握手需要的9个包,所以一共是12个包。
- 使用HTTPS协议需要到CA(数字证书认证机构)申请证书,一般免费的证书比较少,因此需要一定的费用。
- HTTP用的端口是80, HTTPS用的端口是443
- HTTPS其实就是构建在SSL/TLS之上的HTTP协议,所以,要比较HTTPS比HTTP更耗费服务器资源。
8.8. HTTP协议以及HTTP协议头部中表示数据类型的字段?
9. MySQL
10. web
10.1. RESTful
参考RESTful
REST: Representational State Transfer,表现层状态转化。具体解释:
- 资源:表现层状态转化中省略了主语,“表现层”其实是资源的“表现层”。所谓资源,是指互联网上存在的具体实体,比如一段文本,一张照片,一首歌曲等。可以使用URI(统一资源定位符)指向它,因此,要获取这个资源,只需访问对应的URL即可。
- 表现层:资源是一种信息实体,它可以有多种表现形式,我们把资源具体呈现出来的形式,称为“表现层”。比如,一段txt文本,它可以使用HTML,XML,JSON来表现。一张照片,可以使用JPG或者PNG表现。
- 状态转化:访问一个资源,就代表客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。
如果一个架构符合REST原则,就称它为RESTful架构。
11. 基础的排序和查找
11.1. 快速排序
思想就是,首先选择一个分割点,然后将比它大的放在分割点左边,比它小的放在分割点右边,不断递归,当数组长度小于2的时候,就不用排序,直接返回。
def quick_sort(array): if len(array) < 2: return array else: pivot = array[0] left_array = [x for x in array if x<pivot] right_array = [x for x in array if x>pivot] return quick_sort(left_array) + [pivot] + right_array arr = [1,5,7,-2,-3,10,3,200,-300,1000,-30] print(quick_sort(arr))
11.2. 冒泡排序
一次比较两个元素,如果顺序错误,就将他们两个的顺序进行调换,直到没有发生元素调换的时候就停止遍历。
思路:首先,需要一个flag判断这次遍历中是否发生元素的顺序调换。其次,每次都是从第一个元素开始遍历到最后一个元素,其实每遍历一次都会将最大的元素放到了最后,所以存在一些重复操作。
def bubble_sort(arr): while True: flag = False for i in range(0, len(arr)-1): if arr[i] > arr[i+1]: arr[i], arr[i+1] = arr[i+1], arr[i] flag = True if not flag: break arr = [3,2,1,5,3,4,30,-2,-4] bubble_sort(arr) arr
改进避免多余的比较,也就是避免比较已经排序好的内容。此时可以去掉多余的flag指示,因为每次遍历的时候都会确定一个元素的位置,所以n次遍历就确定了n个元素的位置,不需要flag了。
def bubble_sort(arr): for j in range(0, len(arr)): for i in range(0, len(arr)-j-1): if arr[i] > arr[i+1]: arr[i], arr[i+1] = arr[i+1], arr[i] arr = [3,2,1,5,3,4,30,-2,-4] bubble_sort(arr) arr