Python基础知识总结

Python基础知识总结

简述解释型和编译型编程语言?

  • 解释型:代码编译后可以直接运行需要解释器将代码翻译成机器码
  • 编译型:代码编写完成后会有一个编译后的过程,生成可执行文件。然后运行编译好的机器码。

Python解释器种类以及特点?

Python 解释器的版本

我们编写Python代码时,我们得到的是一个包含Python代码的以.py为扩展名的文本文件。要运行代码,就需要Python解释器去执行.py文件。

CPython

官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。

CPython是使用最广的Python解释器。

IPython

IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。

CPython用>>>作为提示符,而IPython用In [序号]:作为提示符。

PyPy

PyPy是另一个Python解释器,它的目标是执行速度。PyPy采用JIT技术,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。

绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到PyPy下执行,就需要了解PyPy和CPython的不同点。

Jython

Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。

IronPython

IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。

位和字节的关系?

一字节8位

b、B、KB、MB、GB 的关系?

  • b:表示一位
  • B:表示一字节8位
  • KB:表示1024个字节
  • MB:表示1024个KB
  • GB:表示1024个MB

请至少列举5个 PEP8 规范(越多越好)。

  • 缩进与换行 每级缩进使用四个空格
  • 限制每行的最大长度为79个字符

空行
– 顶层函数和类之间使用两个空行
– 类的方法之间用一个空行
– 在函数中使用空行表示不同逻辑段落
– 导入位于文件的顶部
– 避免多余空格

注释
– 注释要保持与时俱进 一句后面两个空格 跟注释
– 命名规范
– 除了正常的命名规范外
– 不要使用 大小写的L 大写的O 作为变量名
– 类名首字母大写 内部类 加上前导下划线
– 函数名应该小写 增强可读性可以使用下划线分割

其他
– 别用 ‘==‘ 进行布尔值 和 True 或者 False 的比较 应该用 is通过代码实现如下转换:

二进制转换成十进制:v = “0b1111011”

用int()就可以将其他进制转成10进制

v = "0b1111011"

print(int(v,2))

#123

十进制转换成二进制:v = 18

bin()是转换2进制的方法

v = 18

print(bin(v))

#0b10010

八进制转换成十进制:v = “011”

v = "011"

print(int(v,8))

#9

十进制转换成八进制:v = 30

oct(十进制) 可以转换成8进制

v = 30

print(oct(v))

#0o36

十六进制转换成十进制:v = “0x12”

v = "0x12"

print(int(v,16))

#18

十进制转换成十六进制:v = 87

hex(十进指) 可以转换成16进制

v = 87

print(hex(87))

#0x57

请编写一个函数实现将IP地址转换成一个整数。

如 10.3.9.12 转换规则为:
        10            00001010
         3            00000011
         9            00001001
        12            00001100
再将以上二进制拼接起来计算十进制结果:00001010 00000011 00001001 00001100 = ?

二进制转十进制

def func(IPAddress):
    iplist = IPAddress.split(" ")
    newlist = [str(int(i,2)) for i in iplist]
    return  ".".join(newlist)

print(func("00001010 00000011 00001001 00001100"))
#10.3.9.12

十进制转二进制

def func(IPAddress):
    iplist = IPAddress.split(".")
    newlist = [str(bin(int(i))) for i in iplist]
    return " ".join(newlist)

print(func("10.3.9.12"))
#0b1010 0b11 0b1001 0b1100

python递归的最大层数?

使用sys.getrecursionlimit()获取最大递归层数
当然也可以设置,不过不建议设置太大的递归数

import sys
sys.getrecursionlimit()
#在python3.8 linux8 中为3000
可以通过sys.setrecursionlimit()来设置最大递归数

求结果:

and 的返回结果问题:
从左到右计算表达式,若所有的都为真,则返回最后一个值,若存在假,返回第一个假值.

or 的返回结果问题:
从左到右计算表达式,只要遇到真值就返回那个真是,如果表达式结束依旧没有遇到真值,就返回最后一个假值.

and 和or 对比
可以理解为or遇到真就返回不行才返回最后一个假值
and 对假值敏感,遇到假的就返回。不行才返回最后一个真值

  • v1 = 1 or 3 # 1
  • v2 = 1 and 3 # 3
  • v3 = 0 and 2 and 1 # 0
  • v4 = 0 and 2 or 1 # 1
  • v5 = 0 and 2 or 1 or 4 # 1
  • v6 = 0 or Flase and 1#False

ascii、unicode、utf-8、gbk 区别?

  • ascii 用来表示英文字符和符号,只需要占用1字节 但是缺点就是只能表示英文
  • unicode:为了统一世界各国语言的不同,统一用2个bytes代表一个字符,特点:速度快,但浪费空间
  • utf8:为了改变Unicode都占2个字节,规定一个英文字符用一个字节表示,一个中文字符用三个字节表示,特点:节省空间,速度慢
  • gbk:是中文的字符编码,用2个字节代表一个字符

字节码和机器码的区别?

  • 机器码:是机器可以识别的代码。对机器友好运行速度快。缺点是不容易读取
  • 字节码:代码编译后的产物,介于机器码和代码之间需要直译器转译后才能成为机器码

三元运算规则以及应用场景?

当你写的if 判断逻辑比较简单而且条件内的代码也比较简单的时候就可以使用三元运算操作

a = -1
a = a>0?1:2

在Python 中并没有有三元运算但是也有代替的

为真时的结果 if 判定条件 else 为假时的结果  
a = 1
b = 2
h = ""

h = "变量1" if a>b else "变量2"

print(h)

列举 Python2和Python3的区别?

  • Python3 使用 print 必须要以小括号包裹打印内容,比如 print(‘hi’)
  • Python2 既可以使用带小括号的方式,也可以使用一个空格来分隔打印内容,比如 print ‘hi’
  • python2 range(1,10)返回列表,python3中返回迭代器,节约内存
  • python2中使用ascii编码,python中使用utf-8编码
  • python2中unicode表示字符串序列,str表示字节序列python3中str表示字符串序列,byte表示字节序列
  • python2中为正常显示中文,引入coding声明,python3中不需要
  • python2中是raw_input()函数,python3中是input()函数

用一行代码实现数值交换

a = 1
b = 2
a,b = b,a
print(a,b)

Python3和Python2中 int 和 long的区别?

在python3中long被取消统一使用int

Python2 有 int 和 long 类型。int 类型最大值不能超过 sys.maxint,而且这个最大值是平台相关的。可以通过在数字的末尾附上一个L来定义长整型,显然,它比 int 类型表示的数字范围更大。

xrange和range的区别?

首先在Python3中 取消了在Python2中的range 并用range代替xrange
在Python2 中range和xrange的用法完全一样
  • range:生成一个list,如果要生成的list的很大,会占用很大的内存空间
  • xrange :返回一个生成器对象,只有真正需要的时候才会返回生成的内容

文件操作时:xreadlines和readlines的区别?

  • xreadlines返回的是一个生成器类型,python3已经没有该方法.
  • readlines返回的是一个列表: [‘第一行\n’, ‘第二行\n’, ‘第三行’]

从Python 2.3开始,Python中的文件类型开始支持迭代功能,比如下面两段代码做的其实差不多:

with open('foo.txt', 'r') as f:
    for line in f.readlines():
        # do_something(line)
和
with open('foo.txt', 'r') as f:
    for line in f:
        # do_something(line)

但是,后面一种迭代占用更小的内存,而且更加智能(依赖于Python文件对象的实现),所需文件内容是自动从buffer中按照需要读取的,是值得鼓励的做法。

至于file.xreadlines()则直接返回一个iter(file)迭代器,在Python 2.3之后已经不推荐这种表示方法了,推荐用上面的这种方式。

for line in f:
    # do_something(line)

列举布尔值为False的常见值?

(0,空字符串,空列表、空字典、空元组、None, False)

字符串、列表、元组、字典每个常用的5个方法?

字符串

str.split() 按照给定字符分割字符串
str.strip() 去掉首尾空格
str.find() 查找字符位置
str.low() 转小写
字符串切片

列表

list.append()向列表末尾添加
list.reverse() 列表反转
list.remove 删除指定角标的元素
list.sort() 列表排序
list.index() 给定元素在列表里的索引
list.insert() 指定位置插入元素
list.extend() 一个列表扩展到另一个列表上 相当于 list + list

元祖
set.index()
– 索引
– 切片
– 循环
– 长度

字典

dict.update() 更新字典或者合并字典
dict.items() 返回所有项的列表形式
dict.keys()返回所有键
dict.values()返回所有值
dict.has_key()判断字典有没有这个key

lambda表达式格式以及应用场景?

lambda是指一类无需定义标识符(函数名)的函数或子程序。也叫匿名函数,比如这样简单的方法就可以写成 lambda表达式的样子

multiply = lambda a,b:a*b
print(multiply(2,4))

pass的作用?

类似if 语句当有些语句内的方法 可以先用pass占用 不会报错

if a >0:
    pass

is和==的区别

is 对比的是内存地址 对比的是id值
== 对比的是值 value

简述Python的深浅拷贝以及应用场景?

浅拷贝

使用深浅拷贝的场景。比如读取了一个可变类型的配置信息。但是需要对一个节点进行修改。

这时第一步需要将配置文件修改一下。通常的方法就是先复制一份原配置文件然后在修改

但是对于可变类型的数据会指向一个内存地址。造成原配置也会被修改。结果如下

import copy
li1 = [1, 2, 3]
li2 = li1
#修改复制的配置文件
li2.append(4)
print(li1, li2)  #[1, 2, 3, 4] [1, 2, 3, 4]
#所以说需要使用到copy()的方法复制一份
li3 = li1.copy()
li3.append(5)
print(li1,li3) #[1, 2, 3, 4] [1, 2, 3, 4, 5]

深拷贝

浅拷贝只拷贝一层(如果有嵌套),深拷贝拷贝所有层。

import copy
li1 = [1, 2, 3, [4, 5], 6]
    li2 = copy.deepcopy(li1)
    li1[3].append(7)
    print(li1, li2)  # [1, 2, 3, [4, 5, 7], 6] [1, 2, 3, [4, 5], 6]

Python垃圾回收机制?

Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。

1 引用计数

PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。

优点:
简单 实时性

缺点:
维护引用计数消耗资源 循环引用

2 标记-清除机制

基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。

3 分代技术

分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。

Python默认定义了三代对象集合,索引数越大,对象存活时间越长。

http://python.jobbole.com/82061/

Python的可变类型和不可变类型?

可变类型:
– 不可变数据类型:数值型、字符串型string和元组tuple
– 可变数据类型:列表list和字典dict;

求结果:

v = dict.fromkeys(['k1','k2'],[]) 
v[‘k1’].append(666)
print(v)
v[‘k1’] = 777
print(v)
v = {'k1': [666], 'k2': [666]}
v = {'k1': 777, 'k2': [666]}

用fromkeys()创建出来的字典,value值只要是list、dict等可变类型,改变其中某个key的值,其他的key的value值也会跟着改变;而如果value值是number、str不可变类型时,改变其中某个key的值,其他的key的value值并不会跟着改变。

求结果

def num():
    return [lambda x:i*x for i in range(4)]
print([m(2) for m in num()])

[6, 6, 6, 6]

列举常见的内置函数?

image

官方文档有详细介绍

以下只对自己不熟悉的函数记录

ascii(object)

就像函数 repr(),返回一个对象可打印的字符串,但是 repr() 返回的字符串中非 ASCII 编码的字符,会使用 \x、\u 和 \U 来转义。生成的字符串和 Python 2 的 repr() 返回的结果相似。

简单来说就是把非ascii编码的字符进行转化

>>> ascii(8763)
'8763'
>>> ascii('+&^%$#')
"'+&^%$#'"
>>> ascii('中华民族')
"'\\u4e2d\\u534e\\u6c11\\u65cf'"
>>> ascii('b\31')
"'b\\x19'"
>>> ascii('0x\1000')
"'0x@0'"

breakpoint()

Python 3.7添加了breakpoint(),这个内置函数使得函数被调用时,让执行切换到调试器。相应的调试器不一定是Python自己的pdb,可以是之前被设为首选调试器的任何调试器。以前,调试器不得不手动设置,然后调用,因而使代码更冗长。而有了breakpoint(),只需一个命令即可调用调试器,并且让设置调试器和调用调试器泾渭分明。

brtearray()

返回一个新的 bytes 数组。 bytearray 类是一个可变序列,包含范围为 0 <= x < 256 的整数。它有可变序列大部分常见的方法,见 可变序列类型 的描述;同时有 bytes 类型的大部分方法,参见 bytes 和 bytearray 操作。

  • 如果 source 为整数,则返回一个长度为 source 的初始化数组;
  • 如果 source 为字符串,则按照指定的 encoding 将字符串转换为字节序列;
  • 如果 source 为可迭代类型,则元素必须为[0 ,255] 中的整数;
  • 如果 source 为与 buffer 接口一致的对象,则此对象也可以被用于初始化 bytearray。
  • 如果没有输入任何参数,默认就是初始化数组为0个元素。

一种新的类型 bytearray 字节数组的构造方法

>>>bytearray()
bytearray(b'')
>>> bytearray([1,2,3])
bytearray(b'\x01\x02\x03')
>>> bytearray('runoob', 'utf-8')
bytearray(b'runoob')

callable()

如果参数 object 是可调用的就返回 True,否则返回 False。 如果返回 True,调用仍可能失败,但如果返回 False,则调用 object 将肯定不会成功。 请注意类是可调用的(调用类将返回一个新的实例);如果实例所属的类有 call() 则它就是可调用的。

3.2 新版功能: 这个函数一开始在 Python 3.0 被移除了,但在 Python 3.2 被重新加入。

>>>callable(0)
False
>>> callable("runoob")
False

>>> def add(a, b):
...     return a + b
... 
>>> callable(add)             # 函数返回 True
True
>>> class A:                  # 类
...     def method(self):
...             return 0
... 
>>> callable(A)               # 类返回 True
True
>>> a = A()
>>> callable(a)               # 没有实现 __call__, 返回 False
False
>>> class B:
...     def __call__(self):
...             return 0
... 
>>> callable(B)
True
>>> b = B()
>>> callable(b)               # 实现 __call__, 返回 True
True

help()

用于打印一个类或者对象的帮助方法
这个帮助文档在类的开头注释定义或者通过doc获取

class myclass():
    """这里是帮助内容"""
    def __init__(self):
        self.name = "myclass"
help(myclass)

Help on class myclass in module __main__:

class myclass(builtins.object)
 |  这里是帮助方法
 |  
 |  Methods defined here:
 |  
 |  __init__(self)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

classmethod()

把一个方法封装成类方法。
一个类方法把类自己作为第一个实参,就像一个实例方法把实例自己作为第一个实参。请用以下习惯来声明类方法:

class C:
    @classmethod
    def f(cls, arg1, arg2, ...): ...

类方法的调用可以在类上进行 (例如 C.f()) 也可以在实例上进行 (例如 C().f())。 其所属类以外的类实例会被忽略。 如果类方法在其所属类的派生类上调用,则该派生类对象会被作为隐含的第一个参数被传入

compile()

将一段字符串编译执行成编译成代码或 AST 对象。本身不能被执行只是编译。再由代码对象可以被 exec() 或 eval() 执行

eval_code = compile('100 + 200', '', 'eval')
eval(eval_code)

complex

返回值为 real + imag*1j 的复数,或将字符串或数字转换为复数。如果第一个形参是字符串,则它被解释为一个复数,并且函数调用时必须没有第二个形参。第二个形参不能是字符串。每个实参都可以是任意的数值类型(包括复数)。如果省略了 imag,则默认值为零,构造函数会像 int 和 float 一样进行数值转换。如果两个实参都省略,则返回 0j。

divmod()

数学不好看不懂

函数接收两个数字类型(非复数)参数,返回一个包含商和余数的元组(a // b, a % b)。

dir()

如果没有实参,则返回当前本地作用域中的名称列表。如果有实参,它会尝试返回该对象的有效属性列表。

简单来说就返回一个模块的属性列表

dir(str)

['__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',
 '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']

frozenset

frozenset类的构造方法 返回一个冻结的集合,冻结后集合不能再添加或删除任何元素。
用于创建不可变集合

globals()

返回表示当前全局符号表的字典。这总是当前模块的字典(在函数或方法中,不是调用它的模块,而是定义它的模块)。

iter

用来生成生成器

lst = "1232"
for i in lst:
    print(i)
1
2
3
2 

locals()

更新并返回表示当前本地符号表的字典。 在函数代码块但不是类代码块中调用 locals() 时将返回自由变量。 请注意在模块层级上,locals() 和 globals() 是同一个字典。

>>>def runoob(arg):    # 两个局部变量:arg、z
...     z = 1
...     print (locals())
... 
>>> runoob(4)
{'z': 1, 'arg': 4}      # 返回一个名字/值对的字典
>>>

property

函数的作用是在新式类中返回属性值。

chr()

chr() 用一个整数作参数,返回一个对应的字符。

打印整数对应的ascii码字符

slice()

class slice(stop)
class slice(start, stop[, step])
就是将一个切片转化成对象方面使用参数和切片参数一样

myslice = slice(5) 
w = [1,3,4,5,5]
print(w[myslice])
#[1, 3, 4, 5, 5]

var()

返回对象object的属性和属性值的字典对象,如果没有参数,就打印当前调用位置的属性和属性值 类似 locals()。

filter、map、reduce的作用?

filter

filter 是过滤器输入是一个返回布尔值的函数,和一个列表,会将函数为True的列表元素返回。

map

map 接受一个函数和一个列表并将列表元素输入函数,返回一个函数返回的新列表

reduce reduce:对于序列内所有元素进行累计操作

reduce() 函数在 python 2 是内置函数, 从python 3 开始移到了 functools 模块。

from functools import reduce
reduce(add, [1,2,3,4])
10

一行代码实现9*9乘法表

print("\n".join("\t".join(["%s*%s=%s" %(x,y,x*y) for y in range(1, x+1)]) for x in range(1, 10)))

如何安装第三方模块?以及用过哪些第三方模块?

使用pip命令 install +模块名称

至少列举8个常用模块都有那些?

requeest
json
datetime
tornado
redis
cv2
pymysql
sqlaclmly

re的match和search区别?

  • match方法从头开始找,找到就返回,否则为None,只匹配一次
  • search从头依次搜索,只匹配一次
  • findall方法:返回列表,匹配所有

什么是正则的贪婪匹配?

贪婪和非贪婪
正则表达式通常用于在文本中查找匹配的字符串。Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪则相反,总是尝试匹配尽可能少的字符。在”*”,”?”,”+”,”{m,n}”后面加上?,使贪婪变成非贪婪。

求结果: a. [ i % 2 for i in range(10) ] b. ( i % 2 for i in range(10) )

#0~10 余2的值
[ i % 2 for i in range(10) ]  # [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
( i % 2 for i in range(10) )  # <generator object <genexpr> at 0x0000000003180FC0>

求结果:a. 1 or 2 b. 1 and 2 c. 1 < (2==2) d. 1 < 2 == 2

and 的返回结果问题:
从左到右计算表达式,若所有的都为真,则返回最后一个值,若存在假,返回第一个假值.

or 的返回结果问题:
从左到右计算表达式,只要遇到真值就返回那个真是,如果表达式结束依旧没有遇到真值,就返回最后一个假值.

< = 是比较运算符

print(1 or 2)  # 1 
print(1 and 2) # 2
print(1 < (2==2)) # False
print(1 < 2 == 2) # True

def func(a,b=[]) 这种写法有什么坑?

由于list 列表是个可变数据类型所以说,如果使用默认参数的方式对与b的操作都会反应到一个list中

def func(a,b=[]):
    b.append(1)
    print(b)
func(1)
func(1)
func(1)
#[1]
#[1, 1]
#[1, 1, 1]
func(2,[])
func(2,[])
func(2,[])
#[1]
#[1]
#[1]

如何实现 “1,2,3” 变成 [‘1’,’2’,’3’] ?

使用split()分割字符串

a = "1,2,3".split(",")
print(a)
#['1', '2', '3']

比较: a = [1,2,3] 和 b = [(1),(2),(3) ] 以及 b = [(1,),(2,),(3,) ] 的区别?

a 和 b 得每个元素都是int类型
[(1,),(2,),(3,) ] 的每一个元素是一个元祖tuple

在声明一个只有一个元素的list或者tuple 要使用‘,’ [1,] (1,)

如何用一行代码生成[1,4,9,16,25,36,49,64,81,100] ?

每个数都是 1-10的平方

[i*i for i in range(1,11)]

一行代码实现删除列表中重复的值 ?

将列表转成set() 就可以去掉重复值

如何在函数中设置一个全局变量 ?

在函数中定义的局部变量如果和全局变量同名,则它会隐藏该全局变量。如果想在函数中使用全局变量,则需要使用global进行声明。

logging模块的作用?以及应用场景?

日志模块: 用来记录用户的行为 或者 代码执行的过程

任务场景 最佳工具
普通情况下,在控制台显示输出 print()
报告正常程序操作过程中发生的事件 logging.info()(或者更详细的logging.debug())
发出有关特定事件的警告 warnings.warn()或者logging.warning()
报告错误 弹出异常
在不引发异常的情况下报告错误 logging.error(),logging.exception()或者logging.critical()

作用:可以了解程序的运行情况是否正常,在程序出现故障快速定位出错地方以及故障分析.

logging模块的日志级别

logging模块默认定义了以下几个日志等级,它允许开发人员自定义其他日志级别,但是这是不被推荐的,尤其是在开发供别人使用的库时,因为这会导致日志级别的混乱。

日志等级(level) 描述
DEBUG 最详细的日志信息,典型应用场景是 问题诊断
INFO 信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作
INFO 信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作
WARNING 当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的
ERROR 由于一个更严重的问题导致某些功能不能正常运行时记录的信息
CRITICAL 当发生严重错误,导致应用程序不能继续运行时记录的信息

将日志记录在文件内

logging.basicConfig()方法。

import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)#设定日志的级别
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')

多模块中使用logging

只需要要在需要日志的模块中也引入logging模块即可

# myapp.py
import logging
import mylib

def main():
    logging.basicConfig(filename='myapp.log', level=logging.INFO)
    logging.info('Started')
    mylib.do_something()
    logging.info('Finished')

if __name__ == '__main__':
    main()
# mylib.py
import logging

def do_something():
    logging.info('Doing something')

请用代码简单实现stack

栈和队列是两种基本的数据结构,同为容器类型。两者根本的区别在于:
stack:后进先出
image
queue:先进先出
image

根据stack的特性可以使用python自带的list实现

入栈使用append添加到最后
出栈使用pop()弹出最后一个元素

class Stack(object):
    # 初始化栈为空列表
    def __init__(self):
        self.items = []

    # 判断栈是否为空,返回布尔值
    def is_empty(self):
        return self.items == []

    # 返回栈顶元素
    def peek(self):
        return self.items[len(self.items) - 1]

    # 返回栈的大小
    def size(self):
        return len(self.items)

    # 把新的元素堆进栈里面(程序员喜欢把这个过程叫做压栈,入栈,进栈……)
    def push(self, item):
        self.items.append(item)

    # 把栈顶元素丢出去(程序员喜欢把这个过程叫做出栈……)
    def pop(self, item):
        return self.items.pop()

if __name__ == __main__:
    # 初始化一个栈对象
    my_stack = Stack()
    # 把'h'丢进栈里
    my_stack.push('h')
    # 把'a'丢进栈里
    my_stack.push('a')
    # 看一下栈的大小(有几个元素)
    print my_stack.size()
    # 打印栈顶元素
    print my_stack.peek()
    # 把栈顶元素丢出去,并打印出来
    print my_stack.pop()
    # 再看一下栈顶元素是谁
    print my_stack.peek()
    # 这个时候栈的大小是多少?
    print my_stack.size()
    # 再丢一个栈顶元素
    print my_stack.pop()
    # 看一下栈的大小
    print my_stack.size
    # 栈是不是空了?
    print my_stack.is_empty()
    # 哇~真好吃~
    print 'Yummy~'
————————————————
版权声明:本文为CSDN博主「BenCotreJohnson」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xuqiang20121991/article/details/54139431

常用字符串格式化哪几种?

  • 百分号方式
  • format方式 推荐

百分号方式

  • s,获取传入对象的str方法的返回值,并将其格式化到指定位置
  • r,获取传入对象的repr方法的返回值,并将其格式化到指定位置
  • c,整数:将数字转换成其unicode对应的值,10进制范围为 0 <= i <= 1114111(py27则只支持0-255);字符:将字符添加到指定位置
  • o,将整数转换成 八 进制表示,并将其格式化到指定位置
  • x,将整数转换成十六进制表示,并将其格式化到指定位置
  • d,将整数、浮点数转换成 十 进制表示,并将其格式化到指定位置
  • e,将整数、浮点数转换成科学计数法,并将其格式化到指定位置(小写e)
  • E,将整数、浮点数转换成科学计数法,并将其格式化到指定位置(大写E)
  • f, 将整数、浮点数转换成浮点数表示,并将其格式化到指定位置(默认保留小数点后6位)
  • F,同上
  • g,自动调整将整数、浮点数转换成 浮点型或科学计数法表示(超过6位数用科学计数法),并将其格式化到指定位置(如果是科学计数则是e;)
  • G,自动调整将整数、浮点数转换成 浮点型或科学计数法表示(超过6位数用科学计数法),并将其格式化到指定位置(如果是科学计数则是E;)
  • %,当字符串中存在格式化标志时,需要用%%表示一个百分号
tpl = "i am %s" % "alex"
#i am alex
tpl = "i am %s age %d" % ("alex", 18)
#i am alex age 18
tpl = "i am %(name)s age %(age)d" % {"name": "alex", "age": 18}
#i am alex age 18
tpl = "percent %.2f" % 99.97623
#percent 99.98
tpl = "i am %(pp).2f" % {"pp": 123.425556, }
#i am 123.43
tpl = "i am %(pp).2f %%" % {"pp": 123.425556, }
# %% 表示一个百分号
#i am 123.43 %

format方式

传入” 字符串类型 “的参数
– s,格式化字符串类型数据
– 空白,未指定类型,则默认是None,同s

传入“ 整数类型 ”的参数
– b,将10进制整数自动转换成2进制表示然后格式化
– c,将10进制整数自动转换为其对应的unicode字符
– d,十进制整数
– o,将10进制整数自动转换成8进制表示然后格式化;
– x,将10进制整数自动转换成16进制表示然后格式化(小写x)
– X,将10进制整数自动转换成16进制表示然后格式化(大写X)

传入“ 浮点型或小数类型 ”的参数
– e, 转换为科学计数法(小写e)表示,然后格式化;
– E, 转换为科学计数法(大写E)表示,然后格式化;
– f , 转换为浮点型(默认小数点后保留6位)表示,然后格式化;
– F, 转换为浮点型(默认小数点后保留6位)表示,然后格式化;
– g, 自动在e和f中切换
– G, 自动在E和F中切换
– %,显示百分比(默认显示小数点后6位)

tpl = "i am {}, age {}, {}".format("seven", 18, 'alex')
#i am seven, age 18, alex
tpl = "i am {}, age {}, {}".format(*["seven", 18, 'alex'])
#i am seven, age 18, alex
tpl = "i am {0}, age {1}, really {0}".format("seven", 18)
#i am seven, age 18, really seven
tpl = "i am {0}, age {1}, really {0}".format(*["seven", 18])
#i am seven, age 18, really seven
tpl = "i am {name}, age {age}, really {name}".format(name="seven", age=18)
#i am seven, age 18, really seven
tpl = "i am {name}, age {age}, really {name}".format(**{"name": "seven", "age": 18})
#i am seven, age 18, really seven
tpl = "i am {0[0]}, age {0[1]}, really {0[2]}".format([1, 2, 3], [11, 22, 33])
#i am 1, age 2, really 3
tpl = "i am {:s}, age {:d}, money {:f}".format("seven", 18, 88888.1)
#i am seven, age 18, money 88888.100000
tpl = "i am {:s}, age {:d}".format(*["seven", 18])
#i am seven, age 18
tpl = "i am {name:s}, age {age:d}".format(name="seven", age=18)
#i am seven, age 18
tpl = "i am {name:s}, age {age:d}".format(**{"name": "seven", "age": 18})
#i am seven, age 18
tpl = "numbers: {:b},{:o},{:d},{:x},{:X}, {:%}".format(15, 15, 15, 15, 15, 15.87623, 2)
#numbers: 1111,17,15,f,F, 1587.623000%
tpl = "numbers: {:b},{:o},{:d},{:x},{:X}, {:%}".format(15, 15, 15, 15, 15, 15.87623, 2)
#numbers: 1111,17,15,f,F, 1587.623000%
tpl = "numbers: {0:b},{0:o},{0:d},{0:x},{0:X}, {0:%}".format(15)
#numbers: 1111,17,15,f,F, 1500.000000%
tpl = "numbers: {num:b},{num:o},{num:d},{num:x},{num:X}, {num:%}".format(num=15)
#numbers: 1111,17,15,f,F, 1500.000000%

简述 生成器、迭代器、可迭代对象 以及应用场景?

image

  • 迭代对象包含迭代器。
  • 如果一个对象拥有iter方法,其是可迭代对象;如果一个对象拥有next方法,其是迭代器。
  • 定义可迭代对象,必须实现iter方法;定义迭代器,必须实现iter和next方法。

如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)刚才说过,很多容器都是可迭代对象,此外还有更多的对象同样也是可迭代对象,比如处于打开状态的files,sockets等等。但凡是可以返回一个 迭代器 的对象都可称之为可迭代对象
那么什么迭代器呢?它是一个带状态的对象,他能在你调用 next() 方法的时候返回容器中的下一个值,任何实现了 next() (python2中实现 next() )方法的对象都是迭代器
生成器算得上是Python语言中最吸引人的特性之一,生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。生成器(yield)不需要再像上面的类一样写 iter() 和 next() 方法了,只需要一个 yiled 关键字。 生成器有如下特征是它一定也是迭代器(反之不成立),因此任何生成器也是以一种懒加载的模式生成值。
http://www.cnblogs.com/yuanchenqi/articles/5769491.html

用Python实现一个二分查找的函数。

def bin_search_rec(data_set, value, low, high):
      if low <= high:
          mid = (low + high) // 2
          if data_set[mid] == value:
              return mid
          elif data_set[mid] > value:
              return bin_search_rec(data_set, value, low, mid - 1)
          else:
              return bin_search_rec(data_set, value, mid + 1, high)
      else:
          return

谈谈你对闭包的理解?

https://www.cnblogs.com/Lin-Yi/p/7305364.html
image

os和sys模块的作用?

  • os 用于提供系统级别的操作
  • sys 用于提供对解释器相关的操作

os

os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径
os.chdir("dirname")  改变当前脚本工作目录;相当于shell下cd
os.curdir  返回当前目录: ('.')
os.pardir  获取当前目录的父目录字符串名:('..')
os.makedirs('dirname1/dirname2')    可生成多层递归目录
os.removedirs('dirname1')    若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
os.mkdir('dirname')    生成单级目录;相当于shell中mkdir dirname
os.rmdir('dirname')    删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
os.listdir('dirname')    列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
os.remove()  删除一个文件
os.rename("oldname","newname")  重命名文件/目录
os.stat('path/filename')  获取文件/目录信息
os.sep    输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
os.linesep    输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"
os.pathsep    输出用于分割文件路径的字符串
os.name    输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
os.system("bash command")  运行shell命令,直接显示
os.environ  获取系统环境变量
os.path.abspath(path)  返回path规范化的绝对路径
os.path.split(path)  将path分割成目录和文件名二元组返回
os.path.dirname(path)  返回path的目录。其实就是os.path.split(path)的第一个元素
os.path.basename(path)  返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素
os.path.exists(path)  如果path存在,返回True;如果path不存在,返回False
os.path.isabs(path)  如果path是绝对路径,返回True
os.path.isfile(path)  如果path是一个存在的文件,返回True。否则返回False
os.path.isdir(path)  如果path是一个存在的目录,则返回True。否则返回False
os.path.join(path1[, path2[, ...]])  将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
os.path.getatime(path)  返回path所指向的文件或者目录的最后存取时间
os.path.getmtime(path)  返回path所指向的文件或者目录的最后修改时间

sys

sys.argv           命令行参数List,第一个元素是程序本身路径
sys.exit(n)        退出程序,正常退出时exit(0)
sys.version        获取Python解释程序的版本信息
sys.maxint         最大的Int值
sys.path           返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform       返回操作系统平台名称
sys.stdout.write('please:')
val = sys.stdin.readline()[:-1]

如何生成一个随机数?

  • 随机整数:random.randint(a,b),生成区间内的整数
  • 随机小数:习惯用numpy库,利用np.random.randn(5)生成5个随机小数
  • 0-1随机小数:random.random(),括号中不传参

如何使用python删除一个文件?

os.remove()  删除一个文件

谈谈你对面向对象的理解?

在编程中使用类和对象思想编程的就是面向对象编程
– 面向过程:根据业务逻辑从上到下写垒代码
– 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可
– 面向对象:对函数进行分类和封装,让开发“更快更好更强…”
面向对象的三大特性是指:封装、继承和多态

Python面向对象中的继承有什么特点?

  1. Python的类可以继承多个类,Java和C#中则只能继承一个类
  2. Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先

面向对象深度优先和广度优先是什么?

当子类继承多个父类的时候就涉及到,寻找父类的方式分别是深度优先和广度优先

  • 当类是经典类时,多继承情况下,会按照深度优先方式查找
  • 当类是新式类时,多继承情况下,会按照广度优先方式查找
    image

经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法,从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。

image

class D:

    def bar(self):
        print 'D.bar'


class C(D):

    def bar(self):
        print 'C.bar'


class B(D):

    def bar(self):
        print 'B.bar'


class A(B, C):

    def bar(self):
        print 'A.bar'

a = A()
# 执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> D --> C
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
a.bar()

经典类多继承
class D(object):

    def bar(self):
        print 'D.bar'


class C(D):

    def bar(self):
        print 'C.bar'


class B(D):

    def bar(self):
        print 'B.bar'


class A(B, C):

    def bar(self):
        print 'A.bar'

a = A()
# 执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> C --> D
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
a.bar()

新式类多继承

面向对象中super的作用?

super() 函数是用于调用父类(超类)的一个方法。

super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。

我们都知道,在子类中如果有与父类同名的成员,那就会覆盖掉父类里的成员。那如果你想强制调用父类的成员呢?使用super()函数!这是一个非常重要的函数,最常见的就是通过super调用父类的实例化方法__init__!

  语法:super(子类名, self).方法名(),需要传入的是子类名和self,调用的是父类里的方法,按父类的方法需要传入参数。
  class A:
      def __init__(self, name):
          self.name = name
          print("父类的__init__方法被执行了!")
      def show(self):
          print("父类的show方法被执行了!")

  class B(A):
      def __init__(self, name, age):
          super(B, self).__init__(name=name)
          self.age = age

      def show(self):
          super(B, self).show()

  obj = B("jack", 18)
  obj.show()

是否使用过functools中的函数?其作用是什么?

functools用于高阶函数:指那些作用于函数或者返回其他函数的函数。通常情况下,只要是可以被当做函数调用的对象就是这个模块的目标

列举面向对象中带双下划线的特殊方法,如:newinit

__init__ :      构造函数,在生成对象时调用
__del__ :       析构函数,释放对象时使用
__repr__ :      打印,转换
__setitem__ :   按照索引赋值
__getitem__:    按照索引获取值
__len__:        获得长度
__cmp__:        比较运算
__call__:       调用
__add__:        加运算
__sub__:        减运算
__mul__:        乘运算
__div__:        除运算
__mod__:        求余运算
__pow__:        幂
https://ltoddy.github.io/essay/2018/05/27/python-magic-methods.html

如何判断是函数还是方法?

通常我们认为在类中的函数为方法,类外面声明def为函数,
可以看出通过类方法调用为函数,通过实例化对象调用为方法

  print(isinstance(obj.func, FunctionType))   # False
  print(isinstance(obj.func, MethodType))    # True

  示例:
  class Foo(object):
      def __init__(self):
          self.name = 'lcg'

      def func(self):
          print(self.name)


  obj = Foo()
  print(obj.func)  # <bound method Foo.func of <__main__.Foo object at 0x000001ABC0F15F98>>

  print(Foo.func)  # <function Foo.func at 0x000001ABC1F45BF8>

  # ------------------------FunctionType, MethodType------------#


  from types import FunctionType, MethodType

  obj = Foo()
  print(isinstance(obj.func, FunctionType))  # False
  print(isinstance(obj.func, MethodType))  # True

  print(isinstance(Foo.func, FunctionType))  # True
  print(isinstance(Foo.func, MethodType))  # False

  # ------------------------------------------------------------#
  obj = Foo()
  Foo.func(obj)  # lcg

  obj = Foo()
  obj.func()  # lcg

  """
  注意:
      方法,无需传入self参数
      函数,必须手动传入self参数
  """

静态方法和类方法区别?

实例方法(instance method)
类方法(class method)
静态方法(static method)
https://blog.csdn.net/lihao21/article/details/79762681

列举面向对象中的特殊成员以及应用场景

http://www.cnblogs.com/bainianminguo/p/8076329.html

1、2、3、4、5 能组成多少个互不相同且无重复的三位数

# 1,2,3,4,5可以组成多少个三位数并列举出来
# 总体思想,三位数, 遍历3次, 去除各个位数上相等的数。余下的就是
def threeNum(l):
    lists = []
    for i in l:
        for j in l:
            for k in l:
                if i != j and i != k and j != k:
                    num = str(i)+str(j)+str(k)
                    lists.append(num)

    return set(lists)  # l中有重复元素时候去重

l = [1, 2, 3, 4, 5, 5, 3, 2, 1]
print(threeNum(l))

i = 0
  for x in range(1, 6):
      for y in range(1, 6):
          for z in range(1, 6):
              if (x != y) and (y != z) and (z != x):
                  i += 1
                  if i % 4:
                      print("%d%d%d" % (x, y, z), end=" | ")
                  else:
                      print("%d%d%d" % (x, y, z))
  print(i)

什么是反射?以及应用场景?

通过字符串映射object对象的方法或者属性
https://www.jianshu.com/p/628f61f01a54
– hasattr(obj,name_str): 判断objec是否有name_str这个方法或者属性
– getattr(obj,name_str): 获取object对象中与name_str同名的方法或者函数
– setattr(obj,name_str,value): 为object对象设置一个以name_str为名的value方法或者属性
– delattr(obj,name_str): 删除object对象中的name_str方法或者属性

getattr 获取属性值,hasattr判断objec是否有方法或者属性

class User:
    def __init__(self,name):
        self.name = name

    def eat(self):
        print('%s 正在吃夜宵 ...'%self.name)

    def run(self):
        print('%s 正在跑步中 ...'%self.name)
#函数

# 实例化一个对象: 胖毛
c = User('蓝泽希')
choose = "name"

# 通过hasattr判断属性/方法是否存在
if hasattr(c,choose):
    # 存在,用一个func接收
    func = getattr(c,choose)
    # 通过类型判断是属性还是方法
    if type(func) == str:
        print(func)
    else:
        func()
else:
    print('操作有误,请重新输入')

setattr


def sing(self): print("%s 正在唱歌中..."%self.name) class User: def __init__(self,name): self.name = name def eat(self): print('%s 正在吃夜宵 ...'%self.name) def run(self): print('%s 正在跑步中 ...'%self.name) choice = "sex" c = User('蓝泽希') if hasattr(c, choice): func = getattr(c, choice) print(func) else: # 装饰一个方法或者属性,可以装饰一个方法或者一个属性 setattr(c, choice, "man") func = getattr(c,choice) #func(c) print(func)

delattr

class User:
    def __init__(self,name):
        self.name = name

choice = "name"
c = User('蓝泽希')

# 这里以删除name属性为例
print(c.name)
try:
    # 先判断属性是否存在,存在就删除
    if hasattr(c,choice):
        delattr(c,choice)
    else:
        pass
    print(c.name)
# 捕获没有属性抛出的异常
except AttributeError:
    print('删除%s成功'%choice)

metaclass作用?以及应用场景?

metaclass用来指定类是由谁创建的。

类的metaclass 默认是type。我们也可以指定类的metaclass值。
http://www.cnblogs.com/0bug/p/8578747.html

用尽量多的方法实现单例模式。

什么是面向对象的mro

mro(Method Resoluthion Order, 或MRO)表示在一个类继承多个父类时的查找顺序。

  • 经典类(calssic class),深度优先遍历
  • 在python2.2中提出了type和class的统一,出现了一个内建类型以及自定义类的公共祖先object,即新式类(new-style class)预计算
  • python2.3之后新式类的C3算法,这是python3唯一支持的方式

isinstance作用以及应用场景?

isinstance作用:来判断一个对象是否是一个已知的类型

注意:这里的str list 不是一个字符串是类型(type)

p = '123'
print(isinstance(p,str))#判断P是否是字符串类型
a = "中国"
list1 = [1,2,3,4,5]
print(isinstance(list1,list))#判断list1是否是列表的类型

json序列化时,可以处理的数据类型有哪些?如何定制支持datetime类型?

json序列化时默认支持的数据类型

Python JSON
dict object
list, tuple array
str string
int, float, int- & float-derived Enumse number
True true
False false
None null

通过json.dump cls参数 继承一个JSONEncoder对象后自定义datatime

import json
  from json import JSONEncoder
  from datetime import datetime
  class ComplexEncoder(JSONEncoder):
      def default(self, obj):
          if isinstance(obj, datetime):
              return obj.strftime('%Y-%m-%d %H:%M:%S')
          else:
              return super(ComplexEncoder,self).default(obj)
  d = { 'name':'alex','data':datetime.now()}
  print(json.dumps(d,cls=ComplexEncoder))
  # {"name": "alex", "data": "2018-05-18 19:52:05"}

  https://www.cnblogs.com/tkqasn/p/6005025.html

json序列化时,默认遇到中文会转换成unicode,如果想要保留中文怎么办?

只需要在序列化时候加上参数即可 ensure_ascii=False

import json
#原始json数据
jsonData=[{'name':"一大段文章。。。。。"}]
#序列化,然后输出
jsonStr=json.dumps(jsonData,ensure_ascii=False)
print(jsonStr)
#[{"name": "一大段文章。。。。。"}]

有用过with statement吗?它的好处是什么?

with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。

使用代码实现查看列举目录下的所有文件。

import os
#获取当前工作路径
path = os.getcwd()
print(os.listdir(path))
#['__pycache__', 'app.py', 'app_lzxs-iMac.local_Dec-25-094007-2019_Conflict.py', 'application.py', 'carinfo.py', 'carinfo2.py', 'fanshe.py', 'readme.md', 'sender.py', 'test.py', 'testserver.py']

简述 yield和yield from关键字。

yield

生成器关键字就是将一个需要占用内存的数据比如一个大列表转化成。在调用的时候再去计算生成的方式

yield from

yield from 为了让生成器(带yield函数),能简易的在其他函数中直接调用,就产生了yield from。

https://blog.csdn.net/chenbin520/article/details/78111399

发表评论

电子邮件地址不会被公开。 必填项已用*标注