python函数相关面试题

2. 函数相关

2.1 fun(*args,**kwargs)中的args,*kwargs什么意思?

难度指数: ★★
重要指数: ★★★★

这个题目要考察的是你对python函数可变参数的理解。

python可变参数分为两种:

  1. *args 接受任意多个实际参数
  2. **kwargs接收任意多个以关键字参数赋值的实际参数

*args

在定义函数时,有时候你并不希望参数的个数是固定的,这种设计在实际工作中很常见。

def func(*args):
    print(args, type(args))
    sum_res = 0
    for item in args:
        sum_res += item

    return sum_res

print(func(2))
print(func(2, 3, 4))

你可以看到,我在定义func时,使用*args, args只是个名字,你可以随意修改,关键是前面有一个星。有了这个星,函数调用时传递多少个参数就变成了一个很随意的事情,所有传入的参数将被放入到一个元组中,因此args的类型是tuple元组.

**kwargs

def print_score(**kwargs):
    print(kwargs, type(kwargs))
    for course, score in kwargs.items():
        print(course, score)


print_score(yuwen=89, shuxue=94)

在调用函数时,以关键字参数的形式进行参数传递,最终这些key-value对将组装成字典,kwargs的类型是dict。个人理解,**kwargs就是一种为了偷懒而做的设计,当函数需要传入很多参数(多达10几个)时,使用**kwargs定义函数是非常方便的。

回答这个问题有两个要点:

  1. 他们是可变参数
  2. 在函数内,args的数据类型是tuple,kwargs的数据类型是字典

2.2 说说你对lambda函数的理解

难度指数: ★★
重要指数: ★★

首先要明确,对于lambda准确称呼应当是lambda表达式,这个表达式创建了一个匿名函数。

2.2.1 lambda表达式创建的函数与def定义的函数有什么区别

何为匿名函数呢? 我们用def 关键字创建的函数,有自己的名字,有自己的函数文档

def score(x):
    """
    返回分数
    :param x:
    :return:
    """
    return x[1]

print(score.__name__)
print(score.__doc__)

上面这段代码执行结果为

score

    返回分数
    :param x:
    :return:

使用def定义的函数,有名字,有文档,而使用lambda表达式创建的函数,是没又名字和函数文档的,不然也就不会叫它匿名函数了

下表详细展示了两者的区别

函数 是否有名字 是否有函数文档 代码行数要求 是否自动返回结果
def 创建的函数 无限制
lambda创建的函数 一行

2.2.2 lambda表达式应用场景

要在合适的场景应用lambda表达式

  1. 逻辑简单
  2. 只用一次或极少次
  3. 作为参数

下面这段代码计算列表里所有数据的乘积

lst = [2, 1, 3]
product = 1
for item in lst:
    product *= item

print(product)

如果改为reduce算法,则可以这样写

from functools import reduce

lst = [2, 1, 3]
product = reduce(lambda x, y: x * y, lst, 1)
print(product)

lambda表达式作为reduce函数的参数来使用,如果你在工作中需要使用pyspark,那么,lambda表达式应用的会更加频繁

参考文章 python lambda表达式精讲

2.3 用lambda函数实现两个数相乘

func = lambda a, b: a*b
print(func(3, 4))

lambda表达式的结果是一个匿名函数,将这个匿名函数赋值给变量func,接下来,你可以像使用函数一样来使用func,不过这是PEP8中明确建议禁止的行为,lambda表达式最好只作为其他函数的参数来使用。

2.4 python传参数是传值还是传址?

难度指数: ★★
重要指数: ★★★

在C++中,函数传参,可以选择传值,或者传址,理解这两者的区别,需要你对指针很熟悉。

如果你没有C++语言的基础,没关系,我们直接来看python的传参。python的函数传参,没有传值和传址的分别,一律是传对象的引用。

如果传入参数是可变对象,例如字典,列表,那么就相当于传址,你可以对传入的对象进行修改,如果传入参数是不可变对象,那么就相当于传值,不能对传入的对象进行修改。

回答这个问题,一定要指出python传参,传的是对象的引用。

2.5 说说你对装饰器的理解

难度指数: ★★★★★
重要指数: ★★★★★

面试必考题目,对装饰器的理解程度,几乎可以代表你对python这门语言的掌握程度,以下3个关键点,务必掌握

2.5.1 函数可以作为另一个函数的参数

python中一切皆对象,函数也是对象,正因为函数也是对象,所以,函数可以作为一个函数的参数

import time

def cost(func):
    t1 = time.time()
    result = func()
    t2 = time.time()
    print(f"{func.__name__}执行时长: {t2-t1}")

def test():
    time.sleep(1.5)

cost(test)

在调用执行cost函数时,将test函数做为参数传入,在cost函数内,执行test函数。

2.5.2 函数可以作为另一个函数的返回值

python中,一切皆对象,所以函数可以作为另一个函数的返回值

import time

def cost(func):
    def wrapper():
        t1 = time.time()
        result = func()
        t2 = time.time()
        print(f"{func.__name__}执行时长: {t2-t1}")
        return result
    return wrapper


def test():
    time.sleep(1.5)

new_test = cost(test)
new_test()

函数cost的返回值是wrapper,它是一个函数,cost函数的返回值赋值给new_test, test_test() 等价于wrapper(),调用test_test就是在调用等价于wrapper。

wrapper这个函数内部,调用执行了func, func是什么,是函数cost的参数,别忘了你是如何调用执行cost函数的

new_test = cost(test)

func这个参数,实际传入的是函数test,wrapper内部调用的是test函数,统计的是它的执行时长。

2.5.3 闭包

在一个内部函数中,对外部作用域的变量进行引用,(并且一般外部函数的返回值为内部函数),那么内部函数就被认为是闭包。

如何理解这段话,以2.5.2 中的代码为例,在函数wrapper内部,引用了外部作用域的func, 并且外部函数cost的返回值就是内部函数wrapper,内部函数wrapper就被认为是闭包。

闭包的概念,又牵扯出嵌套作用域,函数作用域,关于作用域和装饰器,更详细的讲解请参考酷python---装饰器

2.6 实现一个装饰器,输出被装饰函数的执行时长

经过2.5 的讲解,你应当已经对装饰器有了基本认识,参考文章里,对装饰器有着更深入的讲解,下面是参考代码

import time
from functools import wraps


def cost(func):
    @wraps(func)
    def warpper(*args, **kwargs):
        t1 = time.time()
        res = func(*args, **kwargs)
        t2 = time.time()
        print(func.__name__ + "执行耗时" +  str(t2-t1))
        return res
    return warpper

@cost
def test(sleep_time):
    """
    测试装饰器
    :param sleep_time:
    :return:
    """
    time.sleep(sleep_time)

test(0.3)

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案