面试准备——python知识

1、range和xrange的用法和区别

在Python2中,range()与xrange()功能是一样的,多用于for循环。但是不同的是range产生的是一个list对象,而xrange是一个生成器对象 。从性能上,xrange优于range。
因此要生成很大的数字序列的时候,用xrange会比range性能优很多,因为不需要一上来就开辟一块很大的内存空间。
在python3中xrange()已经不存在了,range()就是xrange()。

2、可变对象和不可变对象

  • 不可变对象,该对象所指向内存中的值不能被改变
    当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。
    不可变变量性质的优点在于减少了重复的值对内存空间的占用。
  • 可变对象,该对象所指向的内存中的值可以被改变
    变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。

Python中,数值类型(int和float),字符串str,元组tuple都是不可变类型。而列表list,字典dict,集合set都是可变类型。
在除了tuple的不可变变量中,只要两个变量的数据类型相同并且值也相等,那么这两个变量的地址也相同。

作为函数参数
对于python中的可变与不可变对象,同样还涉及到python函数的值传递和引用传递。简单地说,不可变类型传递的是内容,可变类型传递的是引用。
当传递的是内容时,函数内改变参数,不影响函数外的值;当传递引用时,会改变函数外的值。

2.1 list如何去除重复元素

使用set(最快的方法)或dict。

3、深拷贝和浅拷贝

首先深拷贝和浅拷贝都是对象的拷贝,都会生成一个看起来相同的对象,他们本质的区别是拷贝出来的对象的地址是否和原对象一样,也就是地址的复制还是值的复制的区别。
1) copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。
2)copy.deepcopy 深拷贝 拷贝对象及其子对象
在浅拷贝时,拷贝出来的新对象的地址和原对象是不一样的,但是新对象里面的可变元素(如列表)的地址和原对象里的可变元素的地址是相同的,也就是说浅拷贝拷贝的是浅层次的数据结构(不可变元素),对象里的可变元素作为深层次的数据结构并没有被拷贝到新地址里面去,而是和原对象里的可变元素指向同一个地址,所以在新对象或原对象里对这个可变元素做修改时,两个对象是同时改变的,但是深拷贝不会这样,这个是浅拷贝相对于深拷贝最根本的区别。

4、多线程、多进程

  • 进程是操作系统分配资源的最小单元, 线程是操作系统调度的最小单元。
  • 一个应用程序至少包括1个进程,而1个进程包括1个或多个线程,线程的尺度更小。
  • 每个进程在执行过程中拥有独立的内存单元,而一个线程的多个线程在执行过程中共享内存。

5、描述数组、链表、队列、堆栈的区别?

数组与链表是数据存储方式的概念,数组在连续的空间中存储数据,而链表可以在非连续的空间中存储数据;

队列和堆栈是描述数据存取方式的概念,队列是先进先出,而堆栈是后进先出;队列和堆栈可以用数组来实现,也可以用链表实现。

6、迭代器和生成器的区别

1)迭代器是一个更抽象的概念,任何对象,如果它的类有next方法和iter方法返回自己本身。对于string、list、dict、tuple等这类容器对象,使用for循环遍历是很方便的。在后台for语句对容器对象调用iter()函数,iter()是python的内置函数。iter()会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内元素,next()也是python的内置函数。在没有后续元素时,next()会抛出一个StopIteration异常

2)生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数据的时候使用yield语句。每次next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)

区别:生成器能做到迭代器能做的所有事,而且因为自动创建了__iter__()和next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出StopIteration异常。

6.1 大数据的文件读取

① 利用生成器generator
②迭代器进行迭代遍历:for line in file

6.2 Python中的yield用法

yield简单说来就是一个生成器,这样函数它记住上次返 回时在函数体中的位置。对生成器第 二次(或n 次)调用跳转至该函 次)调用跳转至该函 数。

7、Python是如何进行内存管理的

一、垃圾回收:python不像C++,Java等语言一样,他们可以不用事先声明变量类型而直接对变量进行赋值。对Python语言来讲,对象的类型和内存都是在运行时确定的。这也是为什么我们称Python语言为动态类型的原因(这里我们把动态类型可以简单的归结为对变量内存地址的分配是在运行时自动判断变量类型并对变量进行赋值)。

二、引用计数:Python采用了类似Windows内核对象一样的方式来对内存进行管理。每一个对象,都维护这一个对指向该对对象的引用的计数。当变量被绑定在一个对象上的时候,该变量的引用计数就是1,(还有另外一些情况也会导致变量引用计数的增加),系统会自动维护这些标签,并定时扫描,当某标签的引用计数变为0的时候,该对就会被回收。
三、内存池机制Python的内存机制以金字塔行,-1,-2层主要有操作系统进行操作,第0层是C中的malloc,free等内存分配和释放函数进行操作;第1层和第2层是内存池,有Python的接口函数PyMem_Malloc函数实现,当对象小于256K时有该层直接分配内存;第3层是最上层,也就是我们对Python对象的直接操作;

在 C 中如果频繁的调用 malloc 与 free 时,是会产生性能问题的.再加上频繁的分配与释放小块的内存会产生内存碎片. Python 在这里主要干的工作有:
如果请求分配的内存在1~256字节之间就使用自己的内存管理系统,否则直接使用 malloc.
这里还是会调用 malloc 分配内存,但每次会分配一块大小为256k的大块内存.

经由内存池登记的内存到最后还是会回收到内存池,并不会调用 C 的 free 释放掉.以便下次使用.对于简单的Python对象,例如数值、字符串,元组(tuple不允许被更改)采用的是复制的方式(深拷贝?),也就是说当将另一个变量B赋值给变量A时,虽然A和B的内存空间仍然相同,但当A的值发生变化时,会重新给A分配空间,A和B的地址变得不再相同

8、GIL的用法

Global Interpreter Lock(全局解释器锁)

Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python在设计的时候,还没有多核处理器的概念,因此,Python 在设计之初为了设计方便与线程安全,就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

在多线程环境中,Python 虚拟机按以下方式执行:

  1. 设置GIL

  2. 切换到一个线程去运行

  3. 运行:
    a. 指定数量的字节码指令,或者

    b. 线程主动让出控制(可以调用time.sleep(0))

  4. 把线程设置为睡眠状态

  5. 解锁GIL

  6. 再次重复以上所有步骤

在调用外部代码(如C/C++扩展函数)的时候,GIL 将会被锁定,直到这个函数结束为止(由于在这期间没有Python 的字节码被运行,所以不会做线程切换)。

因此,并不能为多个线程分配多个CPU。所以Python中的线程只能实现并发,而不能实现真正的并行。
但是Python3中的GIL锁有一个很棒的设计,在遇到阻塞(不是耗时)的时候,会自动切换线程。

9、装饰器的作用和功能

引入日志

函数执行时间统计

执行函数前预备处理

执行函数后的清理功能

权限校验等场景

缓存

python的引用

参考网址:
Python中的可变对象和不可变对象
关于Python中的可变对象与不可变对象的区别
python深拷贝和浅拷贝的区别

发布了143 篇原创文章 · 获赞 161 · 访问量 29万+

猜你喜欢

转载自blog.csdn.net/vivian_ll/article/details/89468772