python中的闭包、装饰器和@property @staticmethod

目录

问题引出

使用方法

闭包是什么

装饰器的实现


看一段 mido 这个库的源代码 mido/midifiles/tracks.py

class MidiTrack(list):
    @property
    def name(self):
        """
        doc...
        """
        for message in self:
            if message.type == 'track_name':
                return message.name
        else:
            return ''

    @name.setter
    def name(self, name):
        # Find the first track_name message and modify it.
        for message in self:
            if message.type == 'track_name':
                message.name = name
                return
        else:
            # No track name found, add one.
            self.insert(0, MetaMessage('track_name', name=name, time=0))

这些 @ 符号是什么意思?

问题引出

定义类函数时会有大量相同的需求,比如给某个函数记录运行日志,设置某个变量只读,限制函数输入参数范围并在范围外抛出异常;

为了使得代码更简洁,把这种与函数本身的功能无关的功能集合起来,叫做装饰器,用@表示,把@叫做装饰器的语法糖;

装饰器把大量相同的工作省去了,使得程序更整洁(关注核心功能)

使用方法

在想要装饰的函数前写上 @xxx 即可

如上文中用 property 装饰了 name 这个函数

常用的装饰器有

@property

@xxx.name

@staticmethod

@classmethod

@abstractmethod

@wraps

下面分别讲上述接种常见装饰器的常见用法,这篇博客讲的也不错,可以参考看看

  • @property 与 @xxx.name 一起

将一个方法伪装成属性,被修饰的特性方法,内部可以实现处理逻辑,但对外提供统一的调用方式,实现一个实例属性的get,set,delete三种方法的内部逻辑。看一个例子

  • class Screen(object):
    
        @property
        def width(self):
            return self._width
    
        @width.setter
        def width(self, value):
            if value < 0:
                raise ValueError('width must be a positive number.')
            self._width = value
    
        @property
        def height(self):
            return self._height
    
        @height.setter
        def height(self, value):
            if value < 0:
                raise ValueError('height must be a positive number.')
            self._height = value
    
        @property
        def resolution(self):
            return self.width * self.height
    
    
    s = Screen()
    s.height = 20
    s.width = 40
    print(s.resolution)

正确输出 height*width=800

  • 只有 @property

设置某个只读属性,通常用在类内成员变量赋值防止修改

在上面的例子运行后,加一句 s.resolution = 1

报错 AttributeError: can't set attribute

  • @staticmethod
    class c:
        @staticmethod
        def f(*args, **kwargs):
            pass

    在类函数的前面加上@staticmethod就是定义了一个静态方法,不用实例化类 c 也可以调用函数 f,即 c.f() 

  • classmethod

类似于@staticmethod,也可以在不实例化类的情况下调用类内函数,区别是

  • abstracmethod

翻译过来就是抽象方法,用在抽象类(虚类)的虚函数上;含abstractmethod方法的类不能实例化,继承了含abstractmethod方法的子类必须复写所有abstractmethod装饰的方法,未被装饰的可以不重写;下面这个示例代码来自上面提到的CSDN博客链接

# -*- coding:utf-8 -*-
from abc import ABC, abstractmethod

class **A**(**ABC**):
    @abstractmethod
    def **test**(self):
    pass

class **B**(**A**):
    def **test_1**(self):
        print("未覆盖父类abstractmethod")
 
class **C**(**A**):
    def **test**(self):
    print("覆盖父类abstractmethod")
 
if __name__ == '__main__':
    a = A()
    b = B()
    c = C()

"""
前两个分别报错如下:
a = A()
TypeError: Can't instantiate abstract class A with abstract methods test
b = B()
TypeError: Can't instantiate abstract class **B** **with** **abstract** **methods** **test**
第三个实例化是正确的
"""
  • @wraps

见下方,说明装饰器原理后叙述

以下的内容需要一些时间消化,需要耐心理解

笔者水平有限,推荐一个讲的比较通俗易懂的视频 Python的闭包与装饰器_哔哩哔哩_bilibili


闭包是什么

闭包是一个普遍的概念,许多语言里都有

闭包:在作用域外(以特定方式)使用局部变量

复习一些概念

  • 变量有生命周期(作用域),C++中的作用域是{ },pyhton中的作用域是tab之间(比如 def 和 return 之间)
  • 离开作用域后无法访问到变量
  • def fun(*args, **kwargs):
        a = 1
    
    print(a)
    
    # output: NameError: name 'a' is not defined

加入一些新概念

  • 函数也是一种变量
  • 所以函数里也能定义函数,叫嵌套函数
  • 函数的返回值可以是函数
  • def fun():
        print("fun")
        def fun_in():
            print("fun_in")
            return fun_in
        return fun_in
    
    var = fun()  # 调用fun,初始化fun_in
    var()  # 调用fun_in
    
    # output:
    # fun
    # fun_in

看到,在函数 fun 的外部实现了内部函数 fun_in 的调用,也就是在变量作用域外“访问”了 fun_in;

这是因为在调用 fun 的时候初始化了 fun_in ,并将该内部变量保存在 var 里;

此时 var = fun_in,所以调用 var 效果与 fun_in( ) 相同

再看一个例子

  • def fun():
        a = 1
        def fun_in(num):
            return num + a
        return fun_in
    
    var = fun()
    var(3)
    
    # output: 4

正确输出了 a+3 = 4

在作用域外依然能使用局部变量 a ,也是因为这个 a 保存在 var 里了

但是直接 print(a) 会报错,因为 a 这个变量只存在于 var 中,只有 var 能(通过调用 fun_in 的方式)使用

装饰器的实现

那闭包和装饰器有什么关系?

再看下文章最开始的代码

class MidiTrack(list):
    @property
    def name(self):
        ...

在 pycharm 中 Ctrl + 鼠标点击 来到 @property 的声明处

class property(object):
    """
    Property attribute. ...
    """
    def __init__(self, fget=None, fset=None, fdel=None, doc=None): 
    # known special case of property.__init__
        ...

也就是说 property 也是一个类

@property 实际上就是初始化 property 这个类,也就是调用 __init__ 函数

装饰器 @xxx 的实质就是调用函数

在函数 def fun 前使用 @decorator 等同于 decorator(fun)

看下面一段例子,想给函数 real_fun 记录一段日志

import logging

def decorator(fun):
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                        datefmt='%a, %d %b %Y %H:%M:%S',
                        filename='./test.log',
                        filemode='w')
    def fun_in(*args, **kwargs):
        logging.debug("{} will run.".format(fun.__name__))
        fun(*args, **kwargs)
        logging.debug("{} finished.".format(fun.__name__))

    return fun_in

def real_fun():
    print("real_fun")


real_fun = decorator(real_fun)
real_fun()
# output: real fun

当前文件夹下生成的日志文件 test.log

Thu, 19 Mar 2020 13:15:51 property_test.py[line:88] DEBUG real_fun will run.
Thu, 19 Mar 2020 13:15:51 property_test.py[line:90] DEBUG real_fun finished.

为了简洁美观,关注有更有价值的 real_fun 的功能

在函数 real_fun 前使用@decorator  与  real_fun = decorator(real_fun)   是一样的

把上例写成

import logging

def decorator(fun):
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                        datefmt='%a, %d %b %Y %H:%M:%S',
                        filename='./test.log',
                        filemode='w')

    def fun_in(*args, **kwargs):
        logging.debug("{} will run.".format(fun.__name__))
        fun(*args, **kwargs)
        logging.debug("{} finished.".format(fun.__name__))

    return fun_in

@decorator
def real_fun():
    print("real_fun")


real_fun()

输出与日志记录内容相同

现在我们可以把 @decorator 之前所有内容放到另一个 .py 文件中,在 real_fun 所在文件调用装饰器,优雅地完成日志记录

最后来说前文提到的 @wraps

在完成上面的工作后,还有个问题

print(real_fun.__name__)
# output:fun_in

因为实际上调用是在 fun_in 内部发生的

为了 real_fun 的 __name__ 仍是 real_fun ,在内部函数 fun_in 之前使用 @wraps

现在代码是这样

import logging
from functools import wraps


def decorator(fun):
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                        datefmt='%a, %d %b %Y %H:%M:%S',
                        filename='./test.log',
                        filemode='w')

    @wraps(fun)
    def fun_in():
        logging.debug("{} will run.".format(fun.__name__))
        fun()
        logging.debug("{} finished.".format(fun.__name__))

    return fun_in


@decorator
def real_fun():
    print("real_fun")


real_fun()
print(real_fun.__name__)
# output: real_fun
# output: real_fun

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值