面试面试常问之python修饰器

Posted 黑黑白白君

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试面试常问之python修饰器相关的知识,希望对你有一定的参考价值。


1)什么是python修饰器?

Python的修饰器(decorator)是一个非常强大的功能,一种优秀的设计模式,将重复的内容抽象出来,赋予一个函数其他的功能,但又不去改变函数自身

  • 简单来说,修饰器是修改其他函数的功能的函数
  • 使得代码极其简洁,易于维护。

为什么需要修饰器?

装饰器的作用就是为已经存在的函数或对象添加额外的功能

  • 装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
  • 经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计。

有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用


2)如何写修饰器?

背景

  • 一个程序实现了say_hello()和say_goodbye()两个函数:

    def say_hello():
        print "hello!" 	
        
    def say_goodbye():
        print "goodbye!" 	 	 	
        
     if __name__ == '__main__':
        say_hello()
        say_goodbye() 	
    
  • 老板要求调用每个方法前都要记录进入函数的名称,比如这样:

    [DEBUG]: Enter say_hello()
    Hello!
    [DEBUG]: Enter say_goodbye()
    Goodbye!
    

2.1 初级修饰器

Version 1.0

在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的:

def debug(func):
    def wrapper():
        print "[DEBUG]: enter {}()".format(func.__name__)
        return func()
    return wrapper

def say_hello():
    print "hello!"

say_hello = debug(say_hello)  # 添加功能并保持原函数名不变
  • debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。

*Python format 格式化函数

Python2.6 开始,新增了一种格式化字符串的函数 str.format(),它增强了字符串格式化的功能

  • 基本语法是通过 {} 和 : 来代替以前的 % 。
  • format 函数可以接受不限个参数,位置可以不按顺序。
>>> "{}-{}".format("Hello","goodbye")  # 不设置指定位置,按默认顺序 
'Hello-goodbye'
>>> "{1}-{0}".format("Hello","goodbye")  # 设置指定位置 
'goodbye-Hello'
>>> "{1}-{0}-{1}".format("Hello","goodbye")  # 设置指定位置 
'goodbye-Hello-goodbye'
>>> "网站名:{name}, 地址 {url}".format(name="菜鸟教程", url="www.runoob.com")  # 可以设置参数,也可以通过字典或者列表索引设置参数 
'网站名:菜鸟教程, 地址 www.runoob.com'

*Python函数的属性

python中的函数是一种对象,它有属于对象的属性。除此之外,函数还可以自定义自己的属性。

  • 查看函数对象的属性:dir(func_name)
  • 有一个属性__name__,它表示函数的名称。

Version 2.0

在后面版本的Python中支持了@语法糖,使代码变得更优雅,下面代码等同于早期的写法:

def debug(func):
    def wrapper():
        print "[DEBUG]: enter {}()".format(func.__name__)
        return func()
    return wrapper

@debug
def say_hello():
    print "hello!"

*语法糖(Syntactic sugar)

也译为糖衣语法,指计算机语言中特殊的某种语法。

  • 这种语法对语言的功能并没有影响,但是更方便程序员使用。
  • 通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

常见的语法糖:

  • 列表生成式:[ 对x的操作 for x in 集合]的形式
  • 装饰器实质上是一个函数,它把函数作为参数输入到另一个函数。

这是最简单的装饰器。

  • 但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了,因为返回的函数并不能接受参数。

Version 3.0

def debug(func):
    def wrapper(*args, **kwargs):  # 指定宇宙无敌参数
        print "[DEBUG]: enter {}()".format(func.__name__)
        print 'Prepare and say...',
        return func(*args, **kwargs)
    return wrapper  # 返回

@debug
def say(something):
    print "hello {}!".format(something)
  • 可以指定装饰器函数wrapper接受和原函数一样的参数,通过Python提供的可变参数*args关键字参数**kwargs,装饰器就可以用于任意目标函数了。

可变参数 *args

原理:python解释器会把传入的一组参数组装成一个tuple传递给可变参数。

关键字参数 **kwargs

原理:python解释器会把传入的一组参数组装成一个dict传递给关键字参数。


2.2 进阶版修饰器

带参数的装饰器和类装饰器属于进阶的内容。在理解这些装饰器之前,最好对函数的闭包和装饰器的接口约定有一定了解

  • 闭包:就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。
  • 可以把闭包理解成轻量级的接口封装。
  • 详细介绍可参考:说说Python中的闭包
  • 带参数的装饰器

    假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的:

    def logging(level):
        def wrapper(func):
            def inner_wrapper(*args, **kwargs):
                print("[{level}]: enter function {func}()".format(
                    level=level,
                    func=func.__name__))
                return func(*args, **kwargs)
            return inner_wrapper
        return wrapper
    
    @logging(level='INFO')
    def say(something):
        print("say {}!".format(something))
    
    
    # 如果没有使用@语法,等同于
    # say = logging(level='INFO')(say)
    @logging(level='DEBUG')
    def do(something):
        print("do {}...".format(something))
    
    if __name__ == '__main__':
        say('hello')
        do("my work")
    

  • 基于类实现的装饰器

    class logging(object):
        def __init__(self, func):
            self.func = func
    
        def __call__(self, *args, **kwargs):
            print("[DEBUG]: enter function {func}()".format(
                func=self.func.__name__))
            return self.func(*args, **kwargs)
    
    
    @logging
    def say(something):
        print("say {}!".format(something))
    
    
    if __name__ == '__main__':
    	say('hello')
    

  • 带参数的类装饰器

    在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接受一个函数并返回一个函数。

    class logging(object):
        def __init__(self, level='info'):
            self.level = level
    
        def __call__(self, func):
            def wrapper():
                print("[{level}]: enter function {func}()".format(
                    level=self.level,
                    func=func.__name__))
                return func
            return wrapper()
    
    
    @logging(level='alarm')
    def say(something):
        print("say {}!".format(something))
    
    
    if __name__ == '__main__':
    	say('hello')
    

3)装饰器常见的坑

  • 位置错误的代码

    如果心里没底,那么最好不要在装饰器函数之外添加逻辑功能,否则这个装饰器就不受控制了。

  • 错误的函数签名和文档

  • 不能装饰@staticmethod 或者 @classmethod



【部分内容参考自】

  • 详解Python的装饰器:https://www.cnblogs.com/cicaday/p/python-decorator.html#_caption_0
  • Python 函数 类 语法糖:https://segmentfault.com/a/1190000006261012

以上是关于面试面试常问之python修饰器的主要内容,如果未能解决你的问题,请参考以下文章

面试面试常问之数据库索引

面试面试常问之堆栈的区别

面试面试常问之堆栈的区别

面试面试常问之数据库事务

面试常问之concurrentHashMap

iOS 面试常问之多线程