秋招历险记-Python

Python基础知识

Python_牛客博客 (nowcoder.net)

hantmac/Python-Interview-Customs-Collection: Python面试通关宝典,秋招、春招的小伙伴✿✿ヽ(°▽°)ノ✿),有面Python开发方向的,看这一个repo就够啦😘 (github.com)

1.赋值、深拷贝和浅拷贝

b=a,相当于给a起了个别名,a和b都指向同一个对象

b=a.copy() ,浅拷贝,建立了新的对象b,但a和b的子对象仍然指向同一个对象。

#以字典为例
>>>a = {1: [1,2,3]}
>>> b = a.copy()
>>> a, b
({1: [1, 2, 3]}, {1: [1, 2, 3]})
>>> a[1].append(4)
>>> a, b
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
# 以数组为例
>>> c = [1,[2,3,4]]
>>> d = a.copy()
>>>> id(c)!=id(d)>>> id(c[1])==id(d[1])# 即外层对象地址不同,内部对象采用引用的方式,对内部对象的修改也会传递回去!

b=copy.deepcopy(a),深拷贝,建立了新的对象b,且a和b的子对象是独立的,因此a和b是完全独立的。

>>>import copy
>>> c = copy.deepcopy(a)
>>> a, c
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
>>> a[1].append(5)
>>> a, c
({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})

对于可变对象深浅拷贝

赋值: 值相等,地址相等

copy浅拷贝:值相等,地址不相等

deepcopy深拷贝:值相等,地址不相等

1.外层添加元素时,浅拷贝不会随原列表变化而变化;内层添加元素时,浅拷贝才会变化。
2.无论原列表如何变化,深拷贝都保持不变。
3.赋值对象随着原列表一起变化。

对于不可变对象的深浅拷贝

不可变对象类型,没有被拷贝的说法,即便是用深拷贝,查看id的话也是一样的,如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。

一句话就是,不可变类型,不管是深拷贝还是浅拷贝,地址值和拷贝后的值都是一样的。

2. python多线程能用多个cpu么?

python的多线程不能利用多个cpu,因为python的解释器用了GIL(全局解释器锁),当python一个线程运行的时候,解释器就会把自己锁起来,防止别的线程使用它。因此任意时刻都只有一个python线程在运行,无论有多少个cpu,python程序都只能在一个cpu上跑。

CPU采用的是类似于时间片轮转的机制,在某一个时间点上只有一个线程在运行。

3. python垃圾回收机制(GC)

以引用计数器为主、以标记清除和分代回收为辅。

引用计数:在对象被创建的时候,计数为1,当被引用时计数+1,被删除时计数-1,次数为0时python的垃圾回收机制会自动清除这个对象。

+1的情况:对象被创建;对象被copy;对象被作为一个参数传入到函数中;对象作为一个子元素存储到容器中。

-1的情况:对象的别名被赋予新的值;对象的别名被显示销毁;对象离开了它的作用域,如函数执行完成;对象所在的容器被销毁,或者从容器中删除对象。

标记清除:解决循环引用时无法回收的问题。情境:c被b循环引用了,b的引用为1,虽然del了c,但是c的引用还是1。

  • 对已经执行完删除操作后的引用-1,例如此时c的引用变成了0,b还是1,此时引用次数为0的放到死亡容器内,不为0的放到存活容器内
  • 循环存活容器,发现b引用a,复活a,将a放到存活容器内。
  • 删除死亡容器内的所有对象。

因此,只有循环引用的对象都被删除,才可以被回收。

缺点:必须顺序扫描整个内存堆才能找出活动和非活动对象。

PyGC:使用不同的链表来持续追踪活跃对象,每创建一个对象都会被加入到零代链表中;随后会进行“检测循环引用”根据规则减掉相互引用对象的引用计数,将引用计数为0的进行回收,剩下的活跃对象移动到一代链表中。

Python何时进行标记回收过程?
PyGC阙值:被分配计数值与被释放计数值的差值达到设定阙值的时候进行标记回收,一代链表中遵循同样的方法,将活跃对象移动到二代链表中。

分代回收

将那些在多次垃圾收集过程中都没有被标记为垃圾对象的内存对象集中到另外一个区域。这个区域内标记清除的频率较低。

PyGC:引用分代回收,一个代就是一个链表,所有属于同一个代的内存块都链接在同一个链表中(总共三代);每个代中有对象数量上限,每创建的对象都会被加入到到零代中并检查零代是否满,如果满就开始进行垃圾回收

PyGC设计三"代"且通过阙值(时间)和对象数量上限(空间)完成了“标记-清除”和“分代回收”这两种GC思想,异曲同工之妙。

4.迭代器和生成器和装饰器

迭代器存在一个延迟计算的功能,例如在深度学习中数据集的input就是需要一个迭代器去操作,否则这么大列表的维护是比较耗费资源的。迭代器与生成器主要的差距是在于yield方法。

迭代器有两个基本方法:iter()和next().字符串、元组、列表、字典、集合都可以创建迭代器

创建迭代器的方法有三种:

  • 定义一个容器,并添加iter()和next()两种方法

  • 通过iter()将可迭代对象转化为迭代器,如列表等

    list = [1,2,3,4]
    it = iter(list) #iter将列表转化成了迭代器
    #输出迭代器中的值
    #方法一
    for i in it:
        print(it)
    #方法二
    print(next(it))
    
  • 生成器,通过yield语句生成迭代器,省略了iter和next方法

生成器:使用了yield的函数被称为生成器,它可以认为是一种迭代器,生成器运行的过程中,如果遇到了yield,函数暂停并保存所有信息,返回yield的值,在下一次执行next方法时从当前位置继续执行。

装饰器:对于python来说装饰器就是能够在不修改原始的代码情况下给其添加新的功能,在python中就可以用装饰器来实现,同样在写代码的时候也要考虑到后面的可扩展性。

趣解:装饰器就是在所有内容外面再套上一层东西,比如穿了裤子外面再套一层铆钉裤子,这样别人也不敢和你硬碰硬了(具备了铆钉裤的功能)

import time
def timer(func):
    def wrapper():
        time_start = time.time()
        func()
        time_end = time.time()
        print("Run time is %.6f" %(time_end - time_start))
    return wrapper
@timer
def foo():
    time.sleep(1)
    print("halo!")
foo()


>>>halo!
>>>Run time is 1.008703

上述代码的执行顺序如下:

image-20210827150903374

5.yield和return的异同

相同点:当循环只有一次时,yeild和return是相同的

不同点:循环>1次时,return只能在循环外面返回值,yield可以在每次循环的过程中返回值

6.列表中的del pop remove的区别

  • value = list.pop(index),按照索引位置删除元素,并返回元素值,默认是删除最后一个元素
  • list.remove(value),按照值删除元素,删除第一个值=value的元素,返回值为空None
  • del list[index],根据索引的位置删除元素,或删除指定范围内的元素。注意:删除的是引用而不是对象,对象是由GC删除的

8.字典、集合、列表的区别

从python3.6以后,字典是有序的;集合无序

字典里面不能出现重复的键,集合里面不会出现重复的元素。

集合去重机制的底层原理:哈希

列表的优点:

  • 异构性:可以装不同类型的对象
  • 有序性:元素有序,可以按照位置序号获取元素
  • 可变数据类型:可以在原地址上进行修改

字典:

  • 无序性:通过键值对反映映射关系,不能通过位置序号获取,只能通过键来存取对应的值。键只能是不可变数据类型,是唯一的。
  • 可以原地修改已有的键和值
  • 生成方法:初始为空{},动态填充;通过列表生成,dict(zip(列表1,列表2));对元组组成的列表进行构造dict([(x,x),(x,x),(x,x)])

9.怎么对字典的值进行排序?

  • 按照(值,键)zip,然后sorted()(把值放在前面是因为排序默认从头比较)
  • 通过sorted方法对key的设置,使排序的key是字典的值
a = d.items()
s = sorted(a,key= lambda x:x[1]) # 取每个item的第一维
print(s)

10.遍历字典可以用什么方法?

for i in dict
for key in dict.keys()
for value in dict.values()
for item in dict.items()

11.python 元组中元组转为字典

tuple = (('a',1),('b',2))
di = dict(tuple)  # {'a': 1, 'b': 2}
di2 = dict((y,x)for x,y in tuple) # {1: 'a', 2: 'b'}

dict(tuple)

12.python的lamda函数

又称丢弃函数,是一个单独的表达式匿名函数,只能使用一次。

13.Python 中的 is 和 == 有什么区别

  • is判断两个对象的id是否相等/判断两个对象是否为同一个对象。

  • ==判断两个对象的内容是否相等,会调用eq()方法。

14.gbk和utf8的区别

  • GBK编码专门用来解决中文编码的,是双字节的。不论中英文都是双字节的。

  • UTF-8 编码是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24位(三个字节)来编码。对于英文字符较多的论坛则用UTF-8 节省空间。另外,如果是外国人访问你的GBK网页,需要下载中文语言包支持。访问UTF-8编码的网页则不出现这问题。可以直接访问。

15.__init____new____call__的区别

  • __init__是初始化方法(初始化实例时),没有返回,是一个实例方法(当实例对象创建完成之后被调用的,然后设置对象属性的一些初始值)

  • __new__实例化对象,返回一个创建的实例,是一个静态方法(创建实例之前被调用的,它的任务是创建实例然后返回该实例,是个静态方法)

  • __call__:允许一个类的实例像函数一样被调用。实质上说,这意味着 x()x._call_() 是相同的

16.__init__.py 文件的作用以及意义

定义了包的属性和方法,也可以什么都不定义,但是如果这个不存在,这个文件就不是一个包,不能被导入其他模块。

17.Python中类方法和静态方法的区别

Python 类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为 cls,Python 会自动将类本身绑定给 cls 参数(注意,绑定的不是类对象)。也就是说,我们在调用类方法时,无需显式为 cls 参数传参

静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法

18.Python内存管理,做了哪些优化?

Python的内存优化可以从三个方面来讲

  1. 垃圾回收
  2. 引用计数
  3. 内存池机制

19.当退出 Python 时是否释放所有内存分配?

不会,有一些循环引用的变量,只是删除了引用,它本身还存在,python退出并不会释放。

20.Python 的传参是传值还是传址?

Python参数传递采用的肯定是“传对象引用”的方式,相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典或者列表),相当于通过“传引用/地址”来传递对象;如果函数收到的是一个不可变对象(比如数字或者字符串),相当于通过“传值”来传递对象

#不可变类型,传值
a = 23
def test(a):
    a+=2
    return a
print(a,id(a))  # 23 140722984821840
print(test(a)) # 25
print(a,id(a)) # 23 140722984821840 a的值不会发生改变
#可变类型,传址
b = [1,2,3]
def test2(b):
    b.append(4)
    return b
print(b,id(b)) # [1, 2, 3] 2094494040648
test2(b)
print(b,id(b)) # [1, 2, 3, 4] 2094494040648

23. python3/2的区别

  • 1、print函数

    1)在Python 2.6与Python2.7里面,以下三种形式是等价的:

    print 'Hello world!'
    print ('Hello world!')    # 注意print后面有个空格
    print('Hello world!')    # print()不能带有任何其它参数
    

    但在Python3中只能使用后两者,print语句被Python3废弃,只能使用print函数

    2)Python 2.6的__future__中实际已经支持了新的print()语法

    from __future__ import print_function 
    print('Hello', 'world!', sep=' ') 
    

    Python3中可以直接使用如下语句:

    print('Hello', 'world!', sep=' ') 
    

    2、Unicode

    Python2中是ASCII编码,需要更改字符集才能正常支持中文,所以在.py文件中会看到

    # -*- coding: utf-8 -*- 
    

    Python2代码:

    >>> str='梦想还是要有的'
    >>> str
    '\xe6\xa2\xa6\xe6\x83\xb3\xe8\xbf\x98\xe6\x98\xaf\xe8\xa6\x81\xe6\x9c\x89\xe7\x9a\x84'
    >>> str=u'梦想还是要有的'
    >>> str
    u'\u68a6\u60f3\u8fd8\u662f\u8981\u6709\u7684'
    

    Python3中字符串是Unicode (utf-8)编码,支持中文做标识符

    >>> str='梦想还是要有的' 
    >>> str 
    '梦想还是要有的' 
    

    由于Python3源码文件默认使用utf-8编码,这就使得以下代码是合法的:

    >>> 梦想='dream' 
    >>> 梦想 
    'dream' 
    

    3、除法运算

    Python中的除法有两个运算符,/和//

    1)/除法

    在Python2中/除法规则是整数相除的结果是一个整数,把小数部分完全忽略掉,浮点数除法会保留小数点的部分得到一个浮点数的结果。

    >>> 1/2 
    0 
    >>> 1.0/2.0 
    0.5 
    

    在Python3中/除法不再这么做了,对于整数之间的相除,结果也会是浮点数。

    >>> 1/2 
    0.5 
    >>> 1.0/2.0 
    0.5 
    

    2)//除法

    对于//除法,这种除法叫做floor除法,会对除法的结果自动进行一个floor操作,在Python2和Python3中是一致的。

    5、xrange和range

    1)Python3中不再使用xrange方法,只有range方法

    2)range在Python2中返回列表,而在Python3中返回range可迭代对象

    6、八进制字面量表示

    Python3中八进制数必须写成0o777,原来的形式0777不能用了;二进制必须写成0b111。

    新增了一个bin()函数用于将一个整数转换成二进制字串。 Python 2.6已经支持这两种语法。

    在Python3中,表示八进制字面量的方式只有一种,就是0o1000。

    10、数据类型

    1)在Python2中long是比int取值范围更大的整数,Python3中取消了long类型,int的取值范围扩大到之前的long类型范围

    2)新增了bytes类型,对应于Python2版本的八位串,定义一个bytes字面量的方法如下:

    >>> b=b'china' 
    >>> type(b) 
    <class 'bytes'> 
    

    3)str 对象和 bytes 对象可以使用 .encode() (str -> bytes) 或 .decode() (bytes -> str)方法相互转化

    
    

s=b.decode()
s
‘china’
b1=s.encode()
b1
b’china’


4)dict的.keys()、.items 和.values()方法返回迭代器,只能通过循环取值,不能通过索引取值,而之前的iterkeys()等函数都被废弃。同时去掉的还有 dict.has_key(),可以用 in来代替

  dict={'a':1, 'b':2, 'c':3} 
for key in dict: 
      print(key) 
print('c' in dict) 

11、input

在Python2中raw_input()和input( ),两个函数都存在,其中区别为:

1)raw_input():将所有输入作为字符串看待,返回字符串类型

2)input():只能接收"数字"的输入,在对待纯数字输入时具有自己的特性,它返回所输入的数字的类型(int, float )

在Python3中raw_input()和input( )进行了整合,去除了raw_input(),仅保留了input()函数,其接收任意任性输入,将所有输入默认为字符串处理,并返回字符串类型。

13、列表推导

不再支持[n for n in a,b]语法,改为[n for n in (a,b)]或[n for n in [a,b]]

a=1 
b=2 
c=[n for n in [a,b]] 
print(c) 

14、比较符

Python2 中任意两个对象都可以比较,11 < 'test’返回True

Python3中只有同一数据类型的对象可以比较,11 < 'test’报错,需要调用正则判断

import re 
11 < int('test') if re.compile('^[0-9]+$').match('test') else 0 

16、包的定义

Python2:文件夹中必须有_ _ init _ _.py文件

Python3:不需要有_ _ init _ _.py文件

24. python中*和**分别是什么作用?

针对于Python输入函数的内容可能是存在未知的个数,可以用*和**来标准对应的关键字。

其中*来修饰表示接收的数据通过元组的当时进行传递;如果是**表示接收的数据通过字典的方式进行传递。

25、Python中Dict的底层是什么?

26、Python中进程、线程、协程。

一个进程中可以启动N个线程,一个线程中又可以启动N个协程

多进程Process在multiprocessing库中

  • 优点:可以利用多核CPU进行并行运算,不会受到GIL的限制,因为GIL只是对于单个CPU的锁
  • 缺点:占用资源最多、可启动数目是受限于CPU核心数比线程的数量少
  • 适用于:CPU密集型运算

多线程Thread(threading)

  • 优点:相对于进程,更加轻量级,占用资源比少
  • 缺点:
    • 相对进程:多线程只能并发执行,不能利用多CPU(GIL锁)
    • 相对协程:启动数目有限制,占用内存资源,有线程切换的开销
  • 适用于:IO密集型运算,并且同时运行的任务数不多

多协程Corotine(asyncio)

  • 优点:内存开销最少,启动的数量最多(甚至上万)
  • 缺点:支持的库有限制,代码上实现复杂
  • 适用于:IO密集型运算,需要超多的任务运行,也需要有对应的线程库支持

image-20210902135534701

27、闭包是什么意思?

image-20210903142753390

由于上述中li列表属于全局变量,这样在别的代码中或者其他函数也是可以对其进行操作的,因此对于代码来说是极其安全的。

image-20210903143136381

  • 闭包作用,保证数据安全
  • 内层函数对外层函数非全局变量的引用就会形成闭包
  • 被引用的非全局变量也称自由变量,这个自由变量于内层函数产生一个绑定关系
  • 自由变量不会在内存中消失

28、设计模型

image-20210927104740340

初步了解:引入抽象类,将pay方法设定成对应的抽象接口,其他需要继承该方法的都需要实现这个接口,否则将无法实例化这个类。

设计模式

简单工厂模式

  • 内容:不直接想客户端暴露对象创建的实现细节,而是通过一个工厂类来负责创建产品类的实例
  • 角色:
    • 工厂角色
    • 抽象产品角色
    • 具体产品角色
  • 优点:
    • 隐藏了对象创建的实现细节
    • 客户端不需要修改代码
  • 缺点:
    • 违反了单一职责原则,将创建逻辑集中到一个工厂类里
    • 当添加新产品时,需要修改工厂类代码,违反了开闭原则
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值