python基础面试题汇总(持续更新),冲击offer

目录

  • 1.概念理解题
    • python内置数据结构,哪些是不可变的
    • python新式类和经典类的区别
    • is和==有什么区别
    • Python中变量查找顺序
    • python函数的参数是值传递还是引用传递
    • python垃圾回收机制
    • 什么是闭包
    • 什么是装饰器,开发中用到举例
    • 如何实现只读属性
    • Python中类方法、实例方法、静态方法有何区别
    • new和init的区别,什么时候使用到
    • 如何查一个对象的所有属性
    • Python中如何动态获取和设置对象的属性
    • GIL锁的理解
    • 有GIL锁一定可以保证线程安全吗
    • 为什么还要设计GIL锁
    • 常用的魔法方法
    • isinstance 的作用以及与 type()的区别
    • 如何实现一个上下文管理类
    • 字典内部如何实现
    • python实现单例模式
    • 单例模式的应用场景有哪些
    • Python 中的反射
    • 元类是什么?有什么作用?
    • python中为什么类都继承了object类
    • python实现一个抽象基类,什么时候使用抽象基类
  • 2.常考语法
    • 求集合交集和并集
    • 求字典value最大值对应的key
    • 给字典按值排序
    • 字符串反转
    • alist中元素的age由大到小排序
    • 产生一个公差为11的等差数列
    • 列表里放可变参数时,下面代码输出结果
    • 遍历列表时删除元素
    • 统计一个文本中单词频次最高的10个单词
    • 输入日期, 判断这一天是这一年的第几天
    • 排列:奇数在偶数前,且奇数升序排列,偶数降序排序
    • 以下代码他们的输出结果
    • 多装饰器代码打印顺序

1.概念理解题

python内置数据结构,哪些是不可变的

a. 整型 int、 长整型 long、浮点型 float、 复数 complex

b. 字符串 str、 列表 list、 元祖 tuple

c. 字典 dict 、 集合 set

d. Python3 中没有 long,只有无限精度的 int

不可变:数值类型(int、float、bool)、string(字符串)、tuple(元组)

可变:list(列表)、dict(字典)、集合 set

python新式类和经典类的区别

a. 在python里凡是继承了object的类,都是新式类

b. Python3里只有新式类

c. Python2里面继承object的是新式类,没有写父类的是经典类

d. 经典类目前在Python里基本没有应用

e. 保持class与type的统一对新式类的实例执行a.class与type(a)的结果是一致的,对于旧式类来说就不

一样了。

f.对于多重继承的属性搜索顺序不一样新式类是采用广度优先搜索,旧式类采用深度优先搜索。

is和==有什么区别

is:比较的是两个对象的id值是否相等,也就是比较俩对象是否为同一个实例对象。是否指向同一个内

存地址

== : 比较的两个对象的内容/值是否相等,默认会调用对象的eq()方法。

Python中变量查找顺序

函数作用域的LEGB顺序

1.什么是LEGB?

L: local 函数内部作用域

E: enclosing 函数内部与内嵌函数之间

G: global 全局作用域

B: build-in 内置作用

python在函数里面的查找分为4种,称之为LEGB,也正是按照这是顺序来查找的

python函数的参数是值传递还是引用传递

在Python中其实是引用传递。也就是函数外和函数内的变量都指向同一个对象。

但是当在函数里修改变量或重新赋值时,就需要区分:

  • 当参数是可变对象时,函数里变量都引用了同一个对象,那么在函数里改变变量,会影响其他引用的变量;

  • 当参数是不可变对象时,函数里的变量被拷贝了一份赋值给了函数内自己的变量,在函数内改变不会影响原来的值。

python垃圾回收机制

循环引用会怎么样?

参考:记一次面试问题——Python 垃圾回收机制 · TesterHome

python 采用的是引用计数机制为主,标记 - 清除分代收集两种机制为辅的策略。

**引用计数:**当一个对象被创建或者被引用时,该对象的引用计数就会加1,当对象被销毁时相应的引用计数就会减1,一旦引用计数减为0时,表示该对象已经没有被使用.可以将其所占用的内存资源释放掉。

引用计数无法解决循环引用问题。

**分代回收:**以空间换时间的操作方式,Python 将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python 将内存分为了 3“代”,分别为年轻代(第 0 代)、中年代(第 1 代)、老年代(第 2 代),他们对应的是 3 个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python 垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。分代回收是建立在标记清除技术基础之上。分代回收同样作为 Python 的辅助垃圾收集技术处理那些容器对象。

标记清除(Mark—Sweep):是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC 会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。那么 GC 又是如何判断哪些是活动对象哪些是非活动对象的呢?

对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。 mark-sweepg 在上图中,我们把小黑圈视为全局变量,也就是把它作为 root object,从小黑圈出发,对象 1 可直达,那么它将被标记,对象 2、3 可间接到达也会被标记,而 4 和 5 不可达,那么 1、2、3 就是活动对象,4 和 5 是非活动对象会被 GC 回收。

标记清除算法作为 Python 的辅助垃圾收集技术主要处理的是一些容器对象,比如 list、dict、tuple,instance 等,因为对于字符串、数值对象是不可能造成循环引用问题。Python 使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。

什么是闭包

在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。

构成条件:

  • 在函数嵌套(函数里面在定义函数)的前提下

  • 内部函数使用了外部函数的变量(还包括外部函数的参数)

  • 外部函数返回了内部函数

实现:

除了使用嵌套函数外,还可以使用类实现闭包:

class Out:
  	def __init__(self, x):
      	self.x = x
    
    def __call__(self, y):
      	return x + y

out = Out(3)
out(4)

定义类使用__call__实现对象可被调用。

什么是装饰器,开发中用到举例

装饰器就是一个特殊的闭包,把一个函数当作外部函数的参数,在内部函数中使用这个函数。使用装饰器则是使用语法糖@来装饰函数。

开发中使用的比较多,比如:

1.flask钩子函数、Django中间件

2.flask路由函数@route

3.自定义接口返回格式的时候的装饰器

4.日志记录装饰器

如何实现只读属性

class Computer:
    def __init__(self, name, mem, cpu):
        self.__name = name

    @property
    def name(self):  # 只读, getter方法
        return self.__name


pc2 = Computer('admin', '8G', 8)
print(pc2.name )
# pc2.name = "aa"  # 修改会报错AttributeError: can't set attribute

Python中类方法、实例方法、静态方法有何区别

类方法: 是类对象的方法,在定义时需要在上方使用 @classmethod 进行装饰,形参为cls,表示类对象,类对象和实例对象都可调用

类实例方法: 是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身;

静态方法: 是一个任意函数,在其上方使用 @staticmethod 进行装饰,可以用对象直接调用,静态方法实际上跟该类没有太大关系

new和init的区别,什么时候使用到

new是在实例创建之前被调用的,任务是创建实例然后返回该实例对象,是个静态方法,至少要有一个参数cls(代表当前类),必须有返回值(即该实例对象)。

init是在实例创建完成后被调用的,设置对象属性的一些初始值,通常用在初始化一个类实例的时候,是一个实例方法,至少有一个参数self(代表当前的实例),无需返回值。

init在new之后被调用,new的返回值(实例)传递给init的第一个参数,然后由init设置一些实例的参数。

如何查一个对象的所有属性

可以使用dir()__dict__查询对象属性。

dir()是python提供的API函数,利用对象的继承关系来查询该对象的所有有效属性,查询顺序为从对象本身向上级查询,下级的属性查询不到。

__dict__本身作为对象的一种属性,查询范围区别于dir()利用继承关系查询,__dict__仅限于对象本身属性。但是并不是所有对象都有__dict__属性。如果类的属性中有__slots__属性,则该类的实例没有__dict__属性。

Python中如何动态获取和设置对象的属性

hasattr和setattr

if hasattr(Parent, 'x'): 
    print(getattr(Parent, 'x')) 
    setattr(Parent, 'x',3) 
    print(getattr(Parent,'x'))

GIL锁的理解

GIL是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的的运行仍是有先后顺序的,并不是同时进行。
多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大。

有GIL锁一定可以保证线程安全吗

不能。

python中一个线程有两种情况释放GIL:一种情况是在该线程进入IO操作之前,会主动释放GIL,另一种情况是解释器不间断运行了1000字节码(Py2)或运行15毫秒(Py3)后,该线程也会放弃GIL。既然一个线程可能随时会失去GIL,那么就涉及到线程安全。GIL设计的出发点是考虑到线程安全,但是这种线程安全是粗粒度(不需要程序员自己对线程加锁,语言层面本身维护着一个全局的锁机制,用来保证线程安全)。

为什么还要设计GIL锁

不能完全保证线程安全,为什么还要设计GIL锁?

这和 CPython 的底层内存管理有关。

CPython 使用引用计数来管理内容,所有 Python 脚本中创建的实例,都会配备一个引用计数,来记录有多少个指针来指向它。当实例的引用计数的值为 0 时,会自动释放其所占的内存。

假设有两个 Python 线程同时引用 a,那么双方就都会尝试操作该数据,很有可能造成引用计数的条件竞争,导致引用计数只增加 1(实际应增加 2),这造成的后果是,当第一个线程结束时,会把引用计数减少 1,此时可能已经达到释放内存的条件(引用计数为 0),当第 2 个线程再次视图访问 a 时,就无法找到有效的内存了。

所以,CPython 引进 GIL,可以最大程度上规避类似内存管理这样复杂的竞争风险问题。

常用的魔法方法

__repr__
__str__
__iter__
__call__
__new__
__init__
__getattr__
__setattr__
__dir__
__lt__
__le__

isinstance 的作用以及与 type()的区别

在python中,isinstance的意思是“判断类型”;isinstance()是一个内置函数,用于判断一个对象是否是一个已知的类型,类似type()。

isinstance() 与 type() 区别:

  • type() 不会认为子类是一种父类类型,不考虑继承关系。
  • isinstance() 会认为子类是一种父类类型,考虑继承关系。

如果要判断两个类型是否相同推荐使用 isinstance()。

如何实现一个上下文管理类

1.实现enter和exist方法

class File:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        print("进入")
        self.f = open(self.filename, self.mode)
        return self.f

    def __exit__(self, exc_type=None, exc_val=None, exc_tbs=None):
        print("退出")
        self.f.close()

2.使用contextmanager装饰器

该装饰器将生成器中的代码通过yield语句分成两部分,yield之前的代码为__enter__方法,yield之后的代码为__exit__方法,yield的返回值即__enter__方法的返回值,用于赋给as后的变量。

from contextlib import contextmanager

@contextmanager
def open_file(filename, mode):
    print('进入')
    f = open(filename, mode)
    try:
        yield f
    finally:
        print('退出')
        f.close()

字典内部如何实现

https://blog.csdn.net/YZL40514131/article/details/125349175

python字典的底层实现的是哈希表。调用python内置的哈希函数,将键(key)作为参数进行转换(哈希运算+取余运算),得到一个唯一的地址(地址的索引),然后将值(value)存放到对应的地址中(给相同的键赋值会直接覆盖原值,因为相同的键转换后的地址时一样的)。

哈希表使用顺序表存储数据:存储键值对时,通过哈希函数计算出键对应的索引,将值存到索引对应的数据区中
获取数据时,通过哈希函数计算出键对应的索引,将该索引对应的数据取出来。

所以键(Key)必须是可哈希的,即通过哈希函数可为此键计算出唯一地址。

对于 Python 来说,变量,列表、字典、集合这些都是可变的,所以都不能做为键(Key)来使用。因为元组里边可以存放列表这类可变元素,所以如果实在想拿元组当字典的键(Key),那必须对元组做限制:元组中只包括像数字和字符串这样的不可变元素时,才可以作为字典中有效的键(Key)。另外还需要注意的一点是,Python 的哈希算法对相同的值计算得到的结果是一样的,也就是说 12315 和 12315.0 的值相同,他们被认为是相同的键(Key)。

python实现单例模式

第一种方法:使用装饰器

def singleton(cls):
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper


@singleton
class Foo(object):
    pass


if __name__ == "__main__":
    foo1 = Foo()
    foo2 = Foo()
    print(foo1 is foo2)

第二种方法:使用基类

class Singleton(object):

    _instance = None

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance


class Foo(Singleton):
    pass


if __name__ == "__main__":
    foo1 = Foo()
    foo2 = Foo()
    print(foo1 is foo2)

第三种方法:使用元类

元类,元类是用于创建类对象的类,类对象创建实例对象时一定要调用call方法,因此在调用call时候保证始终只创建一个实例即可,type是python的元类。

class Singleton(type):
    # 元类必须继承type
    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instance


class Foo(metaclass=Singleton):
    pass


if __name__ == "__main__":
    foo1 = Foo()
    foo2 = Foo()
    print(foo1 is foo2)

单例模式的应用场景有哪些

单例模式应用的场景一般发现在以下条件下:

资源共享的情况下,避免由于资源操作时导致的性能或损耗等,如日志文件,应用配置。

控制资源的情况下,方便资源之间的互相通信。如线程池等,1,网站的计数器 2,应用配置 3.多线程池 4.数据库配置 数据库连接池 5.应用程序的日志应用…

Python 中的反射

反射的定义:主要是应用于类的对象上,在运行时,将对象中的属性和方法反射出来。

使用场景:可以动态的向对象中添加属性和方法。也可以动态的调用对象中的方法或者属性。

反射的常用方法:
1.hasaattr(obj,str)
判断输入的str字符串在对象obj中是否存在(属性或方法),存在返回True,否则返回False

2.getattr(obj,str)
将按照输入的str字符串在对象obj中查找。如找到同名属性,则返回该属性;如找到同名方法,则返回方法的引用,想要调用此方法得使用 getattr(obj,str)()进行调用。 如果未能找到同名的属性或者方法,则抛出异常:AttributeError。

3.setattr(obj,name,value)
name为属性名或者方法名,value为属性值或者方法的引用动态添加属性。如上,首先定义一个方法。再使用setattr(对象名,想要定义的方法名,所定义方法的方法名)

4.delattr(obj,str)
将你输入的字符串str在对象obj中查找,如找到同名属性或者方法就进行删除

元类是什么?有什么作用?

python 里一些皆对象,包括类也是一个对象。元类及创建类的类。

一般自己写元类主要作用是对创建类或类的对象做一些限制和验证等。

python中为什么类都继承了object类

新式类:以 object 为基类(父类)的类;经典类:不以 object 为基类的类。

新式类和经典类在多继承中,会影响 MRO(方法搜索顺序),经典类会按照深度优先的方法去搜索 ;新式类会按照广度优先的方法去搜索。

在 Python 2.x 中,如果没有指定父类,也不会把 object 作为基类。

而在 Python 3.x 中,如果未指定父类,则默认将 object 作为该类的基类,所以 Python 3.x 中定义的都是新式类。

主要是为了兼容经典类,所以都统一默认为新式类了。(不理解。。。)

python实现一个抽象基类,什么时候使用抽象基类

2.常考语法

求集合交集和并集

交集:

s1 = {2, 3.6, True, 2 + 3j}
s2 = {1, 3.6, False, 2 + 3j}

print(s1 & s2)
# {1, 3.6, (2+3j)}
print(s2 & s1)
# {True, 3.6, (2+3j)}

交集时,布尔类型和0、1比较时,返回结果参考的是后一个数据;

但是把s2的False去掉,又不一样了,还不知道原因:

s1 = {2, 3.6, True, 2 + 3j}
s2 = {1, 3.6, 2 + 3j}

print(s1 & s2)
# {1, 3.6, (2+3j)}
print(s2 & s1)
# {1, 3.6, (2+3j)}

并集:

s1 = {2, 3.6, True, 2 + 3j}
s2 = {1, 3.6, False, 2 + 3j}

print(s1 | s2)
# {False, True, 2, 3.6, (2+3j)}
print(s2 | s1)
# {False, 1, 2, 3.6, (2+3j)}

并集时,布尔类型和0、1比较时,返回结果参考的是第一个数据;

把s2的False去掉,求并集时没有出现上面那种现象。

求字典value最大值对应的key

d1 = {
    "a": 2,
    "b": 3,
    "c": 1
}

print(max(d1, key=lambda x: d1[x]))

max默认比较的是字典的key,可以指定key来制定比较规则。

给字典按值排序

sorted(d.items(),key=lambda x:x[1])

字符串反转

s = "aStr"[::-1]

alist中元素的age由大到小排序

sorted函数的使用

alist = [{'name':'a','age':20},{'name':'b','age':30},{'name':'c','age':25}]
def sort_by_age(list1):
    return sorted(alist,key=lambda x:x['age'], reverse=True)

print(sort_by_age(alist))

产生一个公差为11的等差数列

print([x*11 for x in range(10)])

列表里放可变参数时,下面代码输出结果

list1 = []
dict1 = {}

for i in range(3):
    dict1["num"] = i
    list1.append(dict1)

print(list1)
# [{'num': 2}, {'num': 2}, {'num': 2}]

因为dict1是可变变量,所以每次把dict1加到列表里,其实列表里存的都是dict1的引用,当dict1倍改变,列表里引用的dict1都一起跟着改变。

遍历列表时删除元素

1.使用一个新列表,把不需要删除的元素拷贝到新列表;

2.如果不使用新列表,可以使用倒序遍历,这样遍历时,删除元素不会导致前面的索引变化

a=[1,2,3,4,5,6,7,8]
print(id(a)) 
for i in range(len(a)-1,-1,-1): 
    if a[i]>5: 
      	pass 
    else:
    		a.remove(a[i]) 
  
print(id(a)) print('-----------') print(a)

统计一个文本中单词频次最高的10个单词

import re
from collections import Counter

def test2(filepath):
    with open(filepath) as f:
        return list(map(lambda c: c[0], Counter(re.sub("\W+", " ", f.read()).split()).most_common(10)))

输入日期, 判断这一天是这一年的第几天

考察datetime模块使用

import datetime 
def dayofyear(): 
  	year = input("请输入年份: ") 
    month = input("请输入月份: ") 
    day = input("请输入天: ") 
    date1 = datetime.date(year=int(year),month=int(month),day=int(day)) 
    date2 = datetime.date(year=int(year),month=1,day=1) 
    return (date1-date2).days+1

排列:奇数在偶数前,且奇数升序排列,偶数降序排序

方法1

def func1(l):
    if isinstance(l, str):
        l = [int(i) for i in l]
    l.sort(reverse=True)
    for i in range(len(l)):
        if l[i] % 2 > 0:
            l.insert(0, l.pop(i))
    print(''.join(str(e) for e in l))

方法2

def func2(l):
    print("".join(sorted(l, key=lambda x: int(x) % 2 == 0 and 20 - int(x) or int(x))))

以下代码他们的输出结果

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

正确答案是[9,9,9,9],而不是[0,3,6,9]。产生的原因是Python的闭包的后期绑定导致的,这意味着在闭包中的变量是在内部函数被调用的时候被查找的,因为,最后函数被调用的时候,for循环已经完成, i 的值最后是3,因此每一个返回值的i都是3,所以最后的结果是[9,9,9,9]。

因为延迟绑定的特性,即直到函数调用时,函数内部的执行内容才被确定,而此时i值在产生序列时,最终被赋值为4了,因此每一个函数调用中的上下文的i都是4,而不是预期的递增变化的。

多装饰器代码打印顺序

from collections import abc

def decorator(func):
    print("a")
    def wrapper(*args, **kwargs):
        print("b")
        return func(*args, **kwargs)
        print("c")
    return wrapper

@decorator
def worker(*args, **kwargs):
    print("d")

if __name__ == "__main__":
    print("开始")
    worker()
# a
# 开始
# b
# d
# c没打印因为提前return了

你可能感兴趣的:(面试冲击,python,开发语言,后端)