python面试充电(2)基础

  • python函数常考

Python传递参数实质

上面那个问题第一种情况类似引用传递,第二种情况类似于值传递。但是python参数的传递不是值传递也不是引用传递,而是对象引用传递。一个很重要的概念,不可变对象:bool ,int ,float ,tuple , str ,frozenset ,可变对象 list ,set ,dict 。首先,python里面一切皆对象,函数参数传递其实是传递对象引用,实参和形参都指向同一个对象。但是,因为python存在可变和不可变对象两种情况,于是表现出类似于值传递和引用传递两种情况。可变对象可以在原来的基础上改变,所以实参和形参指向同一个对象,形参做出的修改可以反映到实参上。而不可变对象因为不可改变,所以形参会指向一个新创建的对象,但是实参仍然指向原来对象。

# 一个坑,默认参数只计算一次..而且这个参数还是可变的
def f(l=[1]):
    l.append(2)
    print(l)

f()    # print [1, 2]
f()    # print [1, 2,2]

浅拷贝与深拷贝

参考:https://blog.csdn.net/xinyuski/article/details/84238688

赋值:只是新建多一个对象引用而已

浅拷贝:浅拷贝只拷贝不可变对象(整数,实数,字符串等),对于可变对象,浅复制其实是创建了一个对于该对象的引用。

深拷贝:相对于浅拷贝把可变对象也完全拷贝。

浅拷贝包括:

  • 对列表切片拷贝L[:]
  • 调用对象的拷贝方法:list.copy()
  • 调用copy.copy()

深拷贝包括:

  • 调用copy.deepcopy()

函数传递中*args,**kwargs

*args,**kwargs都是用来处理可变参数的,前者*args会被打包成tuple,**kwargs(关键字参数传入)会被打包成一个字典。

值得一提的是,args和kwargs都是可以用其他字符代替的,*才是关键的语法。

def fun(a, b, *args):
    print(a,b)
    print(type(args), args)


fun('a', 'b', 'c', 'd', 'e')    
fun(*['a', 'b', 'c', 'd', 'e']) # 单*传入列表和元祖也是可以的
# 结果
# a ,b
# <class 'tuple'> ('c', 'd', 'e')
# 改变顺序,结果不一样了!所以这种传参,顺序很重要
fun('a', 'c', 'd', 'e', 'b')


def fun2(**kwargs):
    print(type(kwargs), kwargs)

fun2(a=1, b=2) # 以关键字参数传入
fun2(**dict(a=1,b=2))  # 双*直接传一个字典类型
#打印结果<class 'dict'> {'a': 1, 'b': 2}
  • python异常处理机制

python使用异常来处理错误,异常是可以打破程序正常执行流程的一些错误事件。我们常见的各类异常一般都是继承Exception类,而Exception继承自BaseException类。一般对象源码里面会抛出某些异常,所以要多看一些文档和源码,知道会抛出哪些异常并进行正确处理,防止程序终止。

try:
    pass                                    # 可能抛出异常的代码
except (Exception1, Exception2) as e:       # 可以捕获多个异常
    pass                                    # 异常处理代码
else:
    pass                                    # 没有异常抛出情况下正常执行的代码
finally:
    pass                             # 无论是否抛出异常都会执行的代码,一般处理资源的关闭和释放

自定义异常:继承Exception,附加信息,然后raise

为什么不是继承Exception的父类BaseException,因为except会捕获该异常及其子类异常,而BaseException包括一些别的子类exception,像ctrl+c等KeyboardInterrupt也会捕获,因此反而会影响程序正常运行。

class MyException(Exception):
    pass


try:
    raise MyException('my_exception')
except MyException as e:
# except Exception as e: 会捕获所有Exception的子类异常
    print(e)    # 打印结果 my_exception
  • python GIL

GIL,全局解释器锁。Cpyhon解释器的内存管理并不是线性安全的,需要保护多线程情况下对对象的访问,Cpython使用简单的所机制能够避免同时多个线程同时执行字节码。但是GIL限制了程序多核的执行,即“同一时间只能有一个线程执行字节码”,cpu密集型程序难以利用多核优势(所谓cpu密集型就是利用cpu计算能力的程序)。但是对不密集型如IO密集型程序,IO期间会释放GIL,所以对IO密集程序影响不大。CPU密集可以使用多进+进程尺,IO密集使用多线程/协程。

详细关于GIL,参考深入理解 GIL

简单来说就是GIL决定了“同一时间只有一个线程运行 Python ,而其他 N 个睡眠或者等待”,其机制是每隔一点时间(或者执行一定字节码)当前线程会检查是否有锁,如果有锁就释放给其他线程执行,之后再重新获取锁再继续执行。 没有获取锁的线程只能一直等待锁的释放。

n = 0
 
def foo():
    global n
    # 原子操作:一个字节码就可以完成的,原子操作可以保证线程安全
    # 但是n+=1 不是原子操作!
    n += 1

threads = []
for i in range(100):
    t = threading.Thread(target=foo)
    threads.append(t)
 
for t in threads:
    t.start()
 
for t in threads:
    t.join()
 
print(n)    
# 一般情况下都是100,但是有可能出现99或者98,说明有些线程还没来得及保存就被其他线程覆盖
# 说明尽管有GIL,但是线程仍然是不安全的

可以通过添加细粒度的锁去保证线程安全:

import threading
lock = threading.Lock()
 
def foo():
    global n
    with lock: 
    # 但是n+=1不是原子操作!加锁保证线程安全,虽然对性能有一点影响
    n += 1

猜你喜欢

转载自blog.csdn.net/weixin_37143690/article/details/90083336