1、Python 语言特性
1.1 Python 是静态还是动态?强还是弱类型
- 动态强类型语言
- 动态还是静态:指的是编译时还是运行时确定变量类型
- 静态语言:在编译时变量的数据类型即可确定的语言,多数静态类型语言要求在使用变量之前必须声明数据类型。 例如:
C++、Java、Delphi、C#
等。 - 动态语言:在运行时确定数据类型的语言。变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型。 例如:
PHP/ASP/Ruby/Python/Perl/ABAP/SQL/JavaScript/Unix Shell
等等。
- 静态语言:在编译时变量的数据类型即可确定的语言,多数静态类型语言要求在使用变量之前必须声明数据类型。 例如:
- 强类型指的是不会发生隐式类型转换
# 如:1 + '1'
# js,不会报错,因为内部进行了隐式类型转换
1 + '1' = '11'
# Python,报错
1 + '1'
1.2 python 作为后端语言的优缺点
- 胶水语言。轮子多,应用广泛
- 语言灵活,生产力高
- 性能问题,代码维护问题,
Python2/3
不兼容
1.3 鸭子类型
当看到一只鸟走起来像鸭子,游泳起来像鸭子, 叫起来也像鸭子,那么这只鸟就可以被称为鸭子
- 关注点在对象的行为,而不是类型(
duck typing
) - 比如
file、stringI、socket
对象都支持read、write
方法(file object
,都可以称为文件对象) - 比如定义了
__iter__
魔法方法的对象都可以用for
循环迭代 - 鸭子类型关注的是接口而不是类型
class Duck:
def quack(self):
print('gua gua')
class Persopn:
def quack(self):
print('我是人类,但我也会 gua gua')
def in_the_forest(duck):
duck.quack()
def game():
donald = Duck()
john = Person()
in_the_forest(donald)
in_the_fores(john)
我们关注的不应该是什么类型,而是实现了什么方法。上面鸭子和人类不是同一种类型,但是都实现了 quack()
方法,且都会 gua gua
叫,即也可以把人看做为 “鸭子”...
1.4 monkey patch(猴子补丁)
什么是猴子补丁,哪些地方用到?自己如何实现?
- 什么是:属性在运行时的动态替换
- 比如
gevent
库需要修改内置的socket
from gevent import monkey;monkey.patch_socket()
import socket
print(socket.socket)
print('after monkey patch')
from gevent import monkey
monkey.patch_socket()
print(socket.socket)
import select
print(select.select)
monkey.patch_select()
print('after monkey patch')
print(select.select)
<class 'socket.socket'>
after monkey patch # 使用猴子补丁之后
<class 'gevent._socket3.socket'>
<built-in function select>
after monkey patch
<function select at 0x000001BF93821488>
应用场景:比较出名的是 gevent
(Python 并发模块)使用猴子补丁修改了 socket
模块内部属性,使其变得非阻塞。
自己实现,替换内置的 time.time()
方法:
# 运行替换
import time
print(time.time())
def _time():
return 1234
time.time = _time
print(time.time())
1.5 自省机制
所谓自省机制是运行时判断一个对象的类型和能力。
在日常生活中,自省(introspection
)是一种自我检查行为。
在计算机编程中,自省是指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么。自省向程序员提供了极大的灵活性和控制力。
也可以说:自省就是面向对象的语言所写的程序在运行时,能够知道对象的类型。
Python中比较常见的自省(introspection)机制(函数用法)有: dir(),type(), hasattr(), isinstance()
,通过这些函数,我们能够在程序运行时得知对象的类型,判断对象是否存在某个属性,访问对象的属性。
1、dir()
返回传递给它的任何对象的属性名称经过排序的列表。如果不指定对象,则 dir()
返回当前作用域中的名称。
>>> import keyword
>>> dir(keyword)
['__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'iskeyword', 'kwlist', 'main']
# 也可以用来检查一个对象的所有方法或属性
class Foo:
a = 1
def func(self):
return 'Hello World!'
f = Foo()
print(dir(f))
# 运行结果
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a', 'func']
2、type()
返回对象的类型:
>>> type(99)
<class 'int'>
3、hasattr()
判断对象是否拥有某个属性或方法
class Foo:
a = 1
def func(self):
return 'Hello World!'
f = Foo()
print(hasattr(f, 'func'))
4、isinstance()
判断对象是否是某个类型:
>>> isinstance("python", str)
True
def add(a, b):
is isinstance(a, int):
return a + b
elif isinstance(a, str):
return a.upper() + b
is 与 == 的区别
# 双等号比较的是两者值是否相等,is 比较的是内部内存地址是否相同
l1 = [1, 2, 3]
l2 = [1, 2, 3]
l1 == l2
l1 is l2 # False,内部比较的是 id(li)
1.6 列表和字典推导式
快速生成一个列表或字典或集合的方式
a = [''a', 'b', 'c']
b = [1, 2, 3]
# d = {'a': 1, 'b': 2, 'c': 3}
d = {}
for i in range(len(a)):
d[a[i]] = b[i]
print(d)
d = {k: v for km v in zip(a, b)}
1.7 知道python 之禅?
- the zen of python
- 题目 peters 编写的关于 Python 编程的准则
输入 import this
即可查看
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
1.8 Python2/3 的差异
Python3 的变化
-
print
称为函数,更灵活的配置 - 编码问题,
utf-8
,没有unicode
对象 - 除法变化,返回浮点数
- 传输的时候使用字节,操作的时候使用
Unicode
- 类型注解(
type hint
),帮助 ide 实现类型检查 - 优化
super()
方便直接调用父类的方法 - 高级解包操作,啊, 吧,
*rest = range(10)
# type hint
def hello(name: syr) -> str:
return 'hello' + nam
a, b,_c = range(10) # 丢弃后面的参数不要
Python3 改进
- 限定关键字参数:
keyword only arguments
- Python3 重新抛出异常,不会丢失栈信息:
chained exceptions
- 一切返回迭代器:
range、zip、map、dict
等
# 限定关键字参数
def (a, b, c=3):
pass
# 抛出异常
import shutil
# python2 里会丢失原来的 traceback信息
def mycopy(source, dest):
try:
shutil.copy2(source, dest)
except OSError:
raise NotImportedError('automatic sudo injection from OSError)
mycopy('old' ,'new')
新增的语法
-
yield from
链接子生成器 -
asyncio
内置库,async/await
原生协程支持异步编程 - 新的内置库
enum,mock、asyncio、ipaddress、concurrnet、futures
等 - 将 pyc 文件同意放到
__pycache__
中 - 一些内置库的修改:
urlib、selector
等 - 性能优化等
Python2/3 兼容工具
-
six
模块 -
2to3
等工具转换代码 __future__
-
pyenv
工具(安装不同版本的Python环境)
2、Python 函数
2.1 函数传参
函数传参常考的,传递的参数分为可变和不可变类型,下面是一个小示例:
# 可变不可变。传参类型
def flist(l):
l.append(0)
print(l)
ll = []
flsit(ll) # [0]
flist(ll) # [0, 0]
def fstr(s):
s += 'a'
print(s)
ss = 'hehe'
fstr(ss) # hehea
fstr(ss) # hehea
-
flistr()
中:当传入的是一个list
(可变类型)时,调用两次,最后值变为:[0, 0]
-
fstr()
中:当传入的是一个str
(不可变类型)时,调用两次,最后值仍然为:hehea
这是因为形参和实参指向的是同一个对象,可变对象可以修改对象的值,不可变对象不可修改,因此它在进行赋值的时候只能重新新建一个,然后标识(变量)重新指向新的值。
Python 如何传参(容易混淆的问题)
- 传递值时引用呢?还是什么?其实都不是,唯一支持的是参数传递是共享传参
- Call by object reference of call by sharling
- call by sharing(共享传参),函数形参获得实参中各个引用的副本
2.2 可变不可变类型
- 可变:list、set、dict
- 不可变:bool、int、float、tuple、str、fronzent
2.3 可变参数作为默认参数
默认参数只计算一次
def flist(l=[1]):
l.append(l)
print(l)
flist()
flist()
2.4 *args, 和 **kwargs
- 用来处理成可变参数(当不知道有多少参数时用到)
- args:打包成元组
- kwargs:打包成字典
def fuunc(*args):
print(type(args), args)
for index, value, enumerate(args):
printidnex, value)
2.5 异常机制
-
Exception
继承BaseException
-
systemexit
、keyboardInterrrupt
、generatorexit
:系统相关 exception
异常类继承关系:
[图片上传失败...(image-1e0dd7-1593094235780)]
使用场景
- 网络请求(超时。连接错误)
- 资源访问(权限问题。资源不存在)
- 代码逻辑(越界访问,keyerror等)
如何自定义异常
- 继承 Exception 实现自定义异常
- 给异常加上一些附加信息
- 处理一些业务相关的特定异常
raise myexception
class MyException(Exception):
pass
try:
raise MyException('my exception')
exception exception as e:
print(e)
3. 分析和优化 GIL 问题
GIL(Global Interpretyer Lock) 全局解释锁,这是从 Python 解释器层面给进程加的一把锁,其目的是为了保证线程的安全。
其核心思想是,无论开了个少个线程,有多少个 CPU(多核),Python 解释器在执行时,在同一时刻只允许一个线程运行。只有 cpython 采用 GIL。
优缺点
优点:保证了线程安全,省去了自己加锁的麻烦。其他语言如:Java,需要自己受手动给每个线程加锁,以此来保证线程之间的安全。
缺点:在同一时刻只能有一个线程运行,只有等这个线程遇到 切换时才会将 CPU 给另一个线程,在如今这个多核的时代,使多核成为了鸡肋。
-
限制了程序的多核执行
- 同一个时间之内有一个线程执字节码
- cpu 密集程序难以利用多核优势
- IO 期间会释放 GIL,对 IO 密集程序影响比较小
[图片上传失败...(image-763ee3-1593094235780)]
区分 CPU 和IO 密集程序 (如何规避 GIL 影响)
- CPU 密集:多进程 + 进程池
- IO 密集型:多线程/协程
- cpython 拓展
[图片上传失败...(image-521445-1593094235780)]
多线程有面临着安全问题,上一个线程没有执行完毕,就被另一个线程覆盖:
[图片上传失败...(image-3baefc-1593094235780)]
3.1 为什么有了 GIL 还要关注线程安全
Python 中声名操作才是原子的?一步到位完成的
- 一个操作如果是一个字节码指令可以完成的就是原子的
- 原子的是可以保证线程安全的
- 使用 dis 模块操作来分析字节码
[图片上传失败...(image-a1d205-1593094235780)]
[图片上传失败...(image-817b9a-1593094235780)]
通过加锁来保证线程安全
[图片上传失败...(image-16b6ee-1593094235781)]
3.2 如何剖析程序性能
使用各种 profile 工具(内置或第三方)
- 二八定律,大部分时间耗时在少量代码
- 内置的 profile / cprofile 等工具
- 使用pyflame(uber开源)的火焰图工具
3.3 服务端性能优化
Web 应用一般语言不会成为瓶颈
- 数据结构与算话优化
- 数据层:索引优化,慢查询消除,批量操作减少IO,使用 NoSQL 数据库
- 网络 IO:批量操作,pipeline 操作 ,减少 IO
- 缓存,使用内存数据 redis、memcached等
- 异步:asyncio、celery 提交到异步执行
- 并发:gevent/多线程等
4. 生成器和协程
4.1 生成器 generator
- 可以生成值的函数
- 当一个函数里有了 yield 关键字就是生成器函数
- 生成器可以挂起执行并且保持当前执行的状态
def func():
yield 'hello'
yield 'world'
f = func()
print(next(f))
4.2 基于生成器的协程 Python2
Python3 之前的没有原生协程, 只有基于生成器的协程
- pep 342:增强生成器功能
- 生成器可以通过 yield 暂停执行和产出数据
- 同时支持 send() 向生成器发送数据和 throw()向生成器抛出异常
[图片上传失败...(image-1f98d6-1593094235781)]
协程装饰器(参考书籍:流畅的 Python)
[图片上传失败...(image-8692d3-1593094235781)]
原生协程
async /await 支持原生的协程(native coroutine)
impory asyncio
import datetime
import random
async def display_date(num, loop):
end_time = loop.time() + 50.0
while True:
pirnt('Loop: {} Time: {}' .formate(num, datatime.datetime.now()))
if (loop.time() + 1.0) >= end_time:
break
await asyncio.sleep(random.randint(0, 5))
loop = asyncio.get_event_loop()
asyncio.ensure_future(dsiplay_date(1, loop))
asyncio.ensure_future(display_date(2, loop))
loop.run_forever()
5. 单元测试
- 针对程序模块进行正确性检验
- 一个函数,一个类进行验证
- 自底向上保证程序正确性
为什么写单元测试
- 三无代码:无文档、注释、单元测试
- 保证代码逻辑的正确性(有些采用测试驱动开发TDD)
- 单元测试影响设计,易测的代码往往是高内聚低耦合的
- 回归测试,防止改一处整个服务不可用
相关库
- nose、pytest:常用
- mock 模块:模拟替换网络请求等
- coverage:同级测试覆盖率
如何设计测试用例(等价类划分)
正常值功能测试
边界值测试(如最大最小,最左最右)
异常值(如 None,空值,非法值)
下面是一个 pytest
的小示例,需要先安装:pip3 install pytest
:
def binary_search(array, target):
if not array:
return -1
beg, end = 0, len(array)
while beg < end:
mid = beg + (end - beg) // 2
if array[mid] == target:
return mid
elif array[mid] > target:
end = mid
else:
beg = mid + 1
return -1
def test():
assert binary_search([0, 1, 2, 3, 4, 5], 1) == 1
使用方法:pytest xxx.py
,会自动执行 test
开头的函数:
=============================== test session starts ===========================================
platform win32 -- Python 3.6.5, pytest-5.0.0, py-1.8.0, pluggy-0.12.0
rootdir: E:\Python_virtualenvs\for_django\Projects\restfulwork
plugins: celery-4.3.0
collected 1 item
t.py . [100%]
=============================== 1 passed in 1.01 seconds =====================================
6. Python 常见练习题
1、深拷贝和浅拷贝区别
- 什么是深拷贝、浅拷贝
- 如何实现深拷贝?
2、Python 中如何正确初始化一个二维数组?
7. 面试题
1、请至少列举 5 个 PEP8 规范
每一级四个缩进、类名首字母大写、导入包和类函数之间使用两个空格、规定每行最多 79 个字符、不能一句 import
导入多个库
2、什么是动态、静态语言,编译型、解释性语言?
- 编译型:先将源代码编译为机器语言,并保存为二进制文件,再由机器运行
- 解释型:边解释边运行,先解释为中间代码再运行
- 静态语言:在编译时变量的数据类型即可确定的语言,多数静态类型语言要求在使用变量之前必须声明数据类型。 例如:
C++、Java、Delphi、C#
等。 - 动态语言:在运行时确定数据类型的语言。变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型。 例如:
PHP/ASP/Ruby/Python/Perl/ABAP/SQL/JavaScript/Unix Shell
等等。
3、ascii、Unicode、utf-8、gbk
的区别
-
ASCII
:使用一个字节编码,范围只有英文字母、数字和一些特殊字符,只有 256 个字符 -
Unicode
:可以表示全世界所以的字符 -
GBK
:用于编码汉字字符,采用双字节编码 -
utf-8
:Unicode
编码的一种,又称万国码,是一种针对Unicode
的可变长度字符编码
4、字节码和机器码区别?
- 字节码:是一种中间状态(中间码)的二进制代码(文件),需要直译器转译后才能成为机器码
- 机器码:电脑
CPU
直接读取运行的机器指令,运行速度最快,但非常晦涩难懂
5、Python2/3
的区别
- 编码:2(
ASCII
)、3(utf-8
) -
print
:2 中是一个语句,3 中是一个函数,支持传参 -
input
:2 中有两个:input()、raw_input()
,3 中只有一个 -
xrange
:2 中range()
返回一个列表,xrange()
返回一个迭代器,3 中range()
返回迭代器 - 新式类、旧式类
6、lambda
表达式格式及应用场景
比传统函数更加灵活,可以在程序中被传递和调用,用来替换局部函数
lambda 参数: 表达式 # lambda x: 2x
# 示例
func2 = lambda a1, a2: a1 * a2
print(func2(12, 1000))
# sort 中对列表排序,key 会作用到列表的每个元素,x 为参数,即列表的每个元素,x[1] 即每个元组的第二个元素,这里指的是用元组的第二个元素来排序
a = [('b', 4), ('a', 12), ('d', 7), ('h', 6), ('j', 3)]
a.sort(key=lambda x: x[1])
print(a) # [('j', 3), ('b', 4), ('h', 6), ('d', 7), ('a', 12)]
常与一些内置函数相结合使用,如过滤、排序等
7、正则表达式匹配文本
import re
url = 'http://127.0.0.1:8000?name=rose&age=18'
pattern1 = re.compile('name=(.+?)&')
pattern2 = re.compile('http.*?name=(.+?)&')
pattern3 = re.compile('.*?name=(.+?)&')
result1 = re.findall(pattern, url) # 查询所有,列表
print(result1)
result2 = re.search(pattern2, url) # 查询第一个
print(result2.group(1)) if result2 else print('没有匹配成功')
result3 = re.match(pattern3, url) # 从头开始匹配
print(result3.group(1)) if result3 else print('没有匹配成功')
8、可变与不可变类型
- 可变:
list、dict、set
- 不可变:
str、int、tuple、frozenset
(不可变集合)
9、浅拷贝与深拷贝的实现方式、区别,deepcopy 如果你来设计,如何实现?
# 由于共享内存导致的结果
import copy
a = [1, 2, 3, [4, 5]]
b = copy.copy(a)
print(b)
b[3][0] = 'h'
print(a)
print(b)
a[0] = 'e'
print(a)
print(b)
--------
[1, 2, 3, [4, 5]]
[1, 2, 3, ['h', 5]]
[1, 2, 3, ['h', 5]]
['e', 2, 3, ['h', 5]]
[1, 2, 3, ['h', 5]]
浅拷贝只拷贝第一层,a 修改第一层, 对 b 没影响,b 修改第二层,对 a 的第二层有影响。两种共享第二层内存
深拷贝:
import copy
a = [1, 2, 3, [4, 5]]
b = copy.deepcopy(a)
print(b)
b[3][0] = 'h'
print(a)
print(b)
a[0] = 'e'
print(a)
print(b)
------
[1, 2, 3, [4, 5]]
[1, 2, 3, [4, 5]]
[1, 2, 3, ['h', 5]]
['e', 2, 3, [4, 5]]
[1, 2, 3, ['h', 5]]
内存地址完全不共享,修改 a 的第一层,对比 b 无影响,修改 b 的第二层对 a 不影响。
9、__new__()
与__init__()
的区别
-
__new__()
:产生实例对象,静态方法 -
__init__()
:初始类实例对象,实例方法
class Foo:
def __new__(cls):
return object.__new__(cls)
def __init__(self):
pass
10、你知道几种设计模式
单例、享元、工厂、装饰器
11、编码和解码你了解?
- 编码:将字符串转换为二进制
- 解码:接二进制转换为字符串(编码和解码方式必须相同才能解的出来)
>>> b = b"example"
>>> s = "example"
>>> sb = bytes(s, encoding='utf-8')
>>> sb
b'example'
>>> str(b, encoding='utf-8')
'example'
>>> str.encode(s)
b'example'
>>> bytes.decode(b)
'example'
12、列表推导 list comprehension
和生成器的优劣
- 列表推导式:将所有的值一次性加载到内存中
- 生成器:不会将所有的值一次性加载到内存中,延迟计算,一次返回一个结果,它不会一次生成所有的结果,这对大数据量处理,非常有用
列表推导式:
l = [x**2 for x in range(10) if x % 2 == 0]
print(l)
生成器:
# 生成器函数
def func(li):
for x in li:
yield x**2
li = [1, 2, 3, 4]
f = func(li)
print(next(f))
print(next(f))
print(next(f))
# 生成器表达式
li = (x**2 for x in range(10) if x % 2 == 0)
print(li)
print(next(li))
将列表生成式中[]改成() 之后数据结构是否改变? 答案:是,从列表推导式变为变为生成器
12、什么是装饰器;如果想在函数之后进行装饰,应该怎么做?
装饰器可以用来装饰一个函数或者类,让函数或类拥有额外的功能,在执行函数之前, 进行一些提前的处理。如:检查 Django 登录、login_required
@check_login
def func():
pass
13、手写个使用装饰器实现的单例模式
def singleton(cls):
isintances = {}
def wrapper(*args, **kwargs):
if cls not in isintances:
isintances[cls] = cls(*args, **kwargs)
return isintances[cls]
retun wrapper
@singleton
class Bar:
pass
s = SingleSton()
14、使用装饰器的单例和使用其他方法的单例,在后续使用中,有何区别
Import 方法改变了类本身,new方法,只是把所有实例对象共享属性,每次产生一个新对象。算作伪单例,共享属性方法实例化了许多个相同属性。所以,装饰器方法最为实用。
10、手写:正则邮箱地址
- 汉字在正则中表示为[\u4e00-\u9fa5]
- 字母和数字表示为A-Za-z0-9
[a-zA-Z0-9]+@[a-zA-Z0-9]+.[a-zA-Z]+ # + 表示匹配多个
15、
介绍下垃圾回收:引用计数/分代回收/孤立引用
采用的是引用计数为主,标记清除和分代回收为辅的策略。
Python
的 GC
模块主要运用了引用计数来跟踪和回收垃圾,在引用计数的基础上,还可以通过 标记-清除 解决容器对象可能产生的循环引用的问题,通过分代回收以空间换取时间进一步提高垃圾回收的效率。
- 标记清除:打破了循环引用,即它只关注那些可能会产生循环引用的对象;缺点就是该机制带来的额外操作和需要回收的内存块成正比
- 隔代回收:将系统中所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个 “代”,垃圾收集的频率随着 “代” 的存活时间的增大而减小,即活的越长的对象,越不可能是垃圾,就应该减少对它的垃圾收集频率,那么如何来衡量这个存活时间:通常是利用几次垃圾收集动作来衡量,若一个对象经过的垃圾收集次数越多,可得出该对象存活时间就越长。
在Python中,为了解决内存泄露问题,采用了对象引用计数,并基于引用计数实现自动垃圾回收。
16、多进程与多线程的区别;CPU密集型适合用什么
- 多进程:适合 CPU 密集型(计算)
- 多线程:IO 密集型
原因:进程全局锁(Global Interpreter Lock), 即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程
17、进程通信的方式有几种
队列 Queue、管道、Managers
- Queue:用于多个进程间实现通信
- Pipe:是两个进程的通信
- Managers:可以进行数据共享,上面两个不可以
https://www.cnblogs.com/guguobao/p/9398653.html
18、介绍下协程,为何比线程还快
进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态。
因为线程会等待,遇到 IO 会阻塞,而协程遇到 IO 阻塞可以去处理别的请求,等 IO 阻塞过去了,可以调用回调函数执行别的操作
19、xrange 与 range 的区别
- xrange:Python2,生成器
- range:Python3,生成器,list 对象
20、将IP地址字符串(比如“172.0.0.1”)转为32位二进制数的函数
十进制转二进制:
>>> a = 172
>>> b = '{:08b}'.format(a) # 高位补零
>>> b
'10101100'
>>> '{:8b}'.format(8) # 高位不补零
' 1000'
def transfer():
s = []
for i in ips:
ret = '{:08b}'.format(int(i))
s.append(ret)
return '.'.join(s)
if __name__ == '__main__':
ip = '172.0.0.1'
ips = ip.split('.')
print(transfer())
结果:
10101100.00000000.00000000.00000001
参考文档:https://blog.csdn.net/daydayjump/article/details/80705131
Python 内置进制转换
# 十进制转十六进制
>>> hex(8)
'0x8'
# 十进制转八进制
>>> oct(8)
'0o10'
# 十进制转二进制
>>> bin(8)
'0b1000'
# 转换一个[0, 255]之间的整数为对应的ASCII字符
>>> chr(65)
'A'
>>> chr(97)
'a'
# 一个ASCII字符转换为对应整数
>>> ord('A')
65
int(x, base=10) 函数
int()
函数可以将一个字符串(整数字符串)转换为整数,默认转换为十进制,也可以自定义转换进制:
# 十六进制转十进制
>>> int('0x10', base=16)
16
>>> int('10', 16) # 简写
16
# 八进制转十进制
>>> int('0o10', base=8)
8
>>> int('10', base=8)
8
# 二进制转十进制
>>> int('0b1010', base=2)
10
>>> int('1010', base=2)
10
参考文章:Python内置进制转换函数(实现16进制和ASCII转换)
21、不用中间件,交换 a 和 b 的值
a, b = b, a
22、import 原理
23、编码问题
24、跨域问题
浏览器的同源策略限制,不是同源的脚本不能操作其他源下面的资源,想操作另一个源下面的资源就属于跨域。
只要协议、域名、端口有任何一个不同,都被当作是不同的域,不同域之间的请求就是跨域操作。AJAX
跨域就是AJAX
在A
服务器下对B
服务器发送了 ajax 请求,一般情况下会被浏览器禁止。
解决办法:
- 关闭浏览器同源策略
- JSONP:利用的是 HTML 中某些含 src 属性的标签不遵循同源策略的特性,巧妙地绕过同源策略。如:script 、image、iframe标签,jQuery 也可以实现 JSONP,它会帮我们自动创建新的 script 标签,获得远程服务端返回的数据后,并将其删除,不需要我们手动创建删除。
<script>
// 向另一台服务器 JSONP2 发送请求
// 使用 jQuery 发送,不需要创建新的 script 标签,其内部会自动帮忙创建,并删除
function btn3() {
$.ajax({
url: 'http://127.0.0.1:8000/app/index/',
type: 'get',
dataType: 'jsonp',
jsonp: 'callback',
jsonpCallback: 'list'
})
}
function list(arg) {
console.log(arg);
}
</script>
- 在响应头加上响应的允许跨域的参数,告诉浏览器当前请求被服务器接受,这种跨域方式也是通用的。
- Django中最常用的另一种处理跨域的方式:Django-cors-headers 来处理跨域
如果缺少了同源策略,浏览器很容易受到XSS(跨站脚本攻击 cross site scripting)和CSRF(跨站请求伪造cross-site request forgery)等攻击。
参考文章:python 跨域处理方式
25、手写 IP 正则
这里讲的是IPv4的地址格式,总长度 32位=4段*8位,每段之间用.分割, 每段都是0-255之间的十进制数值。
将0-255用正则表达式表示,可以分成一下几块来分别考虑:
| 或
? 匹配前面的字符或数字 0 次,或 1次
\d 匹配一个数字
\. 将 . 转义为一个普通的点,点在正则中有特殊含义,所有需要转义
取值区间 | 特点 | 正则写法 | 可合并的写法 | 可合并的写法 | 可合并的写法 |
---|---|---|---|---|---|
0-9 | 一位数,只有个位,取值是0~9 | \d | [1-9]?\d | (1\d{2})|([1-9]?\d) | (25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))) |
10-99 | 两位数,十位取值1-9,个位取值是0~9 | [1-9]\d | |||
100-199 | 三位数,最高位取值为1,十位取值0-9,个位取值0-9 | 1\d{2} | - | ||
200-249 | 三位数,最高位取值为2,十位取值0-4,个位取值0-9 | 2[0-4]\d | - | - | |
250-255 | 三位数,最高位取值为2,十位取值5,个位取值0-5 | 25[0-5] | - | - |
IP地址格式可表示为:XXX.XXX.XXX.XXX,XXX取值范围是0-255,前三段加一个.重复了三次,在与最后一段合并及组成IP地址的完整格式。
所以IP地址的正则表示法如下:
((25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))
26、请简述值传递和引用传递的区别?
- 值传递仅仅传递的是具体的值
- 引用传递,传递的是内存地址,修改后会改变内存地址对应储存的值。
27、Kafka 怎么判断重复消费,怎么清除历史记录
kafka是一个分布式消息队列。具有高性能、持久化、多副本备份、横向扩展能力。生产者往队列里写消息,消费者从队列里取消息进行业务逻辑。一般在架构设计中起到解耦、削峰、异步处理的作用。
28、ajax、csrf、jsonp
1、Ajax
Ajax:Asynchronous JavaScript and XML (异步的 JavaScript 和 XML),是一种创建交互式网页应用的网页开发技术方案。
- 异步 JavaScript:使用 JavaScript 以及浏览器提供的类库功能项服务器 偷偷发送请求(页面无法感知),当服务器处理完请求后,自动执行某个 JavaScript 的回调函数。
- XML:标记语言,是 Ajax 在后台交互时传输数据格式之一。
利用 Ajax 可以偷偷向后台发送请求,而页面不会刷新,在有些方面有很大作用:
- 注册时,输入用户名自动检测用户名是否已存在。
- 登录时,提示用户名和密码错误。
- 删除数据行时,将 ID 发送到后台,后台在数据库删除,数据库删除成功后,在前端 DOM 中也将数据行删除。
实现 ajax 的三种方法:
- 原生 Ajax:不需要客户端加载 jQuery,依赖浏览器类库,但是 IE 早期版本没有提供相应类库(XMLHttpResponse 对象)。
- jQuery 实现:发起请求时,会将 jQuery 加载到客户端,对于一些比较小的 Web 程序无疑增加了负担。
- iframe 标签 + form 表单:兼容性最好
29、jsonp 跨域请求
这是在前端解决跨域请求问题,也可以从 Django 后端解决:有相应插件(中间件形式)
要想向远程服务器发送请求,那么必须 绕过浏览器的同源策略、或者让浏览器觉得这是可信性的。
浏览器同源策略: XMLHttpResponse 对象只能在本地发送请求,不能向远程服务器发送请求,响应会被浏览器阻止。
解决方法: JSONP 是一种绕过浏览器同源策略的方式,利用的是 HTML 中某些含 src 属性的标签不遵循同源策略的特性,巧妙地绕过同源策略。如:script
标签
每发送一次请求,便新建一个 scrip 标签,src 路径为远程服务器地址,请求完毕获得数据后便将其删除
- 远程服务端必须返回的数据,必须是
函数(数据)
的形式,且访问端回调函数的函数名必须与其相同。 - JSONP 内部只能实现 GET 请求,即使发起的是 POST 请求,也会以 GET 请求实现。
30、 RabbitMQ、celery
1、Celery
Celery 是一个 基于 Python 开发的分布式异步消息任务队列,可以实现任务异步处理,制定定时任务等
Celery 在执行任务时需要通过一个消息中间件来接收和发送任务消息,以及存储任务结果, 一般使用 rabbitMQ
或 Redis
(默认采用 RabbitMQ)
优点:
- 简单易用
- 高可用:即使执行失败或执行过程发生中断,也会尝试再次执行
- 快速:一个单进程的 Celery 每分钟可以执行上百万个任务
- 拓展性强:Celery 的各个组件都可以拓展和自定制
Celery 主要模块:
- 任务模块 Task:异步和定时任务
- 消息中间件 Broker:即任务调度队列,接收生产者发来的任务,将任务存入队列。Celery 本身不提供队列服务,官方推荐 RabbitMQ 或 Redis 等
- 任务执行单元 Worker:处理任务,实时监控消息队列,获取队列中调度的任务,并执行它。
- 结果存储 Backend:存储任务执行结果,以便查询,与中间件一样,也可以使用 RabbitMQ、Redis 或 MongoDB 存储
应用场景:
- Web 应用,当触发时间需要较长时间,将任务交给 celery 异步处理,处理完毕后返回任务结果即可,在这期间可以去做别的事
- 完成任务执行别的程序(如:发送邮件?)回调函数
- 定时任务
2、RabbitMQ
RabbitMQ 是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。
特点:
- 消息持久化(除非服务器崩溃)
- 公平分发(一个生产者,多个消费者,你接收一个我接收一个,轮流来)
- 根据实际情况分发:服务器配置、网络等
- 订阅:exchange 在定义时是有类型的,只有符合条件的才能接收消息,大致可分为以下几类:
- fanout(全民广播):凡是绑定 exchange 的队列都可以接收到消息,视频直播
- direct(组播):以组为单位接收消息,如:发送到某个组,那么这个组里的所有队列都能接收,
routingKey
为关键字/组名 - topic(根据特征收发消息):所有符合
routingKey
绑定的队列都可以接收消息
- RPC(Remote Procedure Call)双向传输
应用场景:比如业务服务模块解耦,异步通信,高并发限
- 异步处理:用户注册(需要注册邮件和注册短信),传统串行,并行速度稍有提高,RabbitMQ 消息队列,(将发送邮件和短信异步处理)缩短时间
- 应用解耦:订单与库存关系,传统:用户下单后订单系统调用库存系统(缺点:库存系统出问题,订单失效);消息队列(用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回下单成功信息给用户;库存系统从消息队列中获取下单信息,更新库存,再返回库存量)
- 流量削峰:秒杀活动,流量很大,导致应用挂掉,在前端假如消息队列(控制活动人数,超过一定阈值,将订单丢弃)等
参考:https://www.cnblogs.com/wanglijun/p/10896896.html
31、手写一个装饰器
1、手写个使用装饰器实现的单例模式
def singleton(cls):
isintances = {}
def wrapper(*args, **kwargs):
if cls not in isintances:
isintances[cls] = cls(*args, **kwargs)
return isintances[cls]
retun wrapper
@singleton
class Bar:
pass
s = SingleSton()
2、手写一个装饰器
登录认证
def check_login(func):
def wrapper(request, *args, **kwargs):
is_login = request.session.get('is_login', False)
if is_login:
func(request, *args, **kwargs)
else:
return redirect('/')
return wrapper
带参数装饰器:
def log(text):
def inner(func):
def wrapper(*args, **kwargs):
pass
return wrapper
return inner
@log('hello')
def index(name, age, gender):
return 'index 返回'
i = index('rose', 18, 'female')
print(s)
32、短路求值
a = 10
b = 20
print(a and b) # a、b 皆为真,所以取最后一个真,即 20
print(a or b) # a、b 皆为真,所以取第一个真,即 10
运行结果:
20
10
a = 10
b = 0
print(a and b) # a 为真,b 为假,取第一个假值,即 0
-
and
:返回第一个假值或最后一个真值 -
or
:返回第一个真值或最后一个假值 -
and
对False
短路,or
对True
短路
33、is 和 ==
的区别
-
is
:判断两者id
是否一致 -
==
:判断两者value
是否一致
34、求值(函数闭包)
def num():
return [lambda x: x * i for i in range(4)]
print(m(4) for m in num())
# 结果为 [12, 12, 12, 12]
原因:i
在闭包作用域,闭包中用到的变量的值,是在内部函数被调用时查询得到的,所以 num()
返回的是 [lambda x: 3x, lambda x: 3x, lambda x: 3x, lambda x: 3x]
,再乘以 4,就是四个 12 了。
35、谈谈对函数闭包的理解?
一个外部函数定义了一个内部函数,内部函数调用了外部函数的临时变量,并且外部函数的返回值是内部函数的引用,这样就构成了一个闭包。
36、一行代码实现删除列表中重复的值?
list(set(li))
先转换为集合,再转换为列表
37、json 序列化支持的数据类型?如何支持 datatime
时间类型?
支持的类型:string、int、list、tuple、dict、bool、None
import json
import datetime
class CJsonEncoder(json.JSONEncoder):
def default(self, obj):
# 若是 datetime 类型就转换为字符串,否则使用默认的方式序列化
if isinstance(obj, datetime.date):
return obj.strftime("%Y-%m-%d %H:%M:%S")
else:
return json.JSONEncoder.default(self, obj)
now = datetime.datetime.now()
print(json.dumps(now, cls=CJsonEncoder))
38、json 序列化时,遇到中文默认会转换为 Unicode
,若想保留中文,该怎样做?
json.dumps(obj, ensure_ascii=False)
39、用代码实现查看列举目录下所有文件
import os
path = "C:\\Users\\hj\\Desktop\\笔记"
file_list=[]
def check_file(path):
if os.path.isdir(path):
for file in os.listdir(path):
base_url = os.path.join(path, file)
if os.path.isfile(base_url):
file_list.append(file)
return file_list
else:
return os.path.basename(path)
print(check_file(path))