YOLOv5:Profile、Timeout、WorkingDirectory上下文管理器
前言
前提条件
- 熟悉Python
相关介绍
- Python是一种跨平台的计算机程序设计语言。是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。
- Python OS模块是负责程序与操作系统的交互,提供了访问操作系统底层的接口和非常丰富的方法用来处理文件和目录。
- Python contextlib模块提供了一种方便的方式来管理上下文管理器(context manager),它可以帮助我们简化代码,提高可读性和可维护性。contextlib模块提供了以下几个函数:
- contextlib.contextmanager:这个函数可以用来定义上下文管理器,它可以把一个普通的函数变成一个上下文管理器。
- contextlib.closing:这个函数可以用来包装一个对象,使其可以使用with语句。
- contextlib.suppress:这个函数可以用来抑制指定的异常,使其不会被抛出。
- contextlib.redirect_stdout:这个函数可以用来重定向标准输出流。
- contextlib.ExitStack:这个类可以用来管理多个上下文管理器,它可以让我们在一个with语句中使用多个上下文管理器。
上下文管理器
- 上下文管理器是Python中的一种特殊对象,它可以用来管理上下文,例如打开和关闭文件、访问数据库等。它们通常使用with语句来实现,它可以确保在with语句块执行完毕后,上下文管理器会被正确的关闭。
with open('test.txt', 'w') as f: # 执行完后,文件会关闭close()。
f.write('Hello world')
with open('test.txt','r') as f: # 执行完后,文件会关闭close()。
print(f.readlines())
['Hello world']
- with 方法的实现涉及到两个魔法函数__enter__和__exit__。
- Python魔法函数是Python中的一种特殊函数,它可以用来实现一些特殊的功能,例如访问对象的属性、调用对象的方法等。它可以帮助我们简化代码,提高可读性和可维护性。Python魔法函数有以下几种:
1. __init__:这个函数可以用来初始化一个对象,它会在对象被创建时自动调用。
2. __str__:这个函数可以用来返回一个字符串,它会在对象被打印时自动调用。
3. __call__:这个函数可以用来把一个对象当做函数来调用,它会在对象被调用时自动调用。
4. __getattr__:这个函数可以用来访问一个对象的属性,它会在访问一个不存在的属性时自动调用。
5. __setattr__:这个函数可以用来设置一个对象的属性,它会在设置一个属性时自动调用。
- 所谓魔法函数(Magic Methods),是Python的一种高级语法,允许你在类中自定义函数(函数名格式一般为__xx__),并绑定到类的特殊方法中。
- 比如在类People中自定义__str__函数,则在调用People()时,会自动调用__str__函数,并返回相应的结果。在定义类时,经常使用__init__函数(构造函数)和__del__函数(析构函数),其实这也是魔法函数的一种。
class People(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return self.name + ":" + str(self.age)
if __name__=="__main__":
print(People('FrienshipT',18))
FrienshipT:18
- Python中以双下划线(__)开始和结束的函数为魔法函数。
- 调用类实例化的对象的方法时自动调用魔法函数。
- 在自己定义的类中,可以实现之前的内置函数。
- 执行流进入 with 中的代码块时会执行__enter__方法,它会返回在这个上下文中使用的一个对象。执行流离开 with 块时,则调用这个上下文管理器的__exit__方法来清理所使用的资源。
ContextDecorator源码
有兴趣可以自行查阅。
class ContextDecorator(object):
"A base class or mixin that enables context managers to work as decorators."
def _recreate_cm(self):
"""Return a recreated instance of self.
Allows an otherwise one-shot context manager like
_GeneratorContextManager to support use as
a decorator via implicit recreation.
This is a private interface just for _GeneratorContextManager.
See issue #11647 for details.
"""
return self
def __call__(self, func): #将类变为可调用对象
@wraps(func)
def inner(*args, **kwds):
with self._recreate_cm():
return func(*args, **kwds)
return inner
自己写一个Context上下文管理器
- Python中通过继承 contextlib 里面的 ContextDecorator 类,实现对常规上下文管理器类的支持,其不仅可以作为上下文管理器,也可以作为函数修饰符。
import contextlib
class Context(contextlib.ContextDecorator):
def __init__(self, mode):
self.mode = mode
print('__init__({})'.format(self.mode))
def __enter__(self):
print('__enter__({})'.format(self.mode))
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__({})'.format(self.mode))
@Context('装饰器')
def print_func(info):
print(info)
print_func('装饰器运行')
print("\n--------------\n")
with Context('上下文管理器'):
print('上下文管理器方式运行')
__init__(装饰器)
__enter__(装饰器)
装饰器运行
__exit__(装饰器)
--------------
__init__(上下文管理器)
__enter__(上下文管理器)
上下文管理器方式运行
__exit__(上下文管理器)
YOLOv5 general.py中的上下文管理器
Profile上下文管理器
import time
import torch
class Profile(contextlib.ContextDecorator):
# YOLOv5 Profile class. Usage: @Profile() decorator or 'with Profile():' context manager
def __init__(self, t=0.0):
self.t = t
self.cuda = torch.cuda.is_available()
print("__init__ self.t:",self.t)
def __enter__(self):
self.start = self.time()
print('__enter__ self.start:',self.start)
return self
def __exit__(self, type, value, traceback):
self.dt = self.time() - self.start # delta-time
self.t += self.dt # accumulate dt
print("__exit__ self.time():",self.time())
print("__exit__ self.dt(delta-time):",self.dt)
print("__exit__ self.t(accumulate-dt):",self.t)
def time(self):
if self.cuda:
torch.cuda.synchronize()
return time.time()
if __name__ == "__main__":
context = Profile()
with context:
print('Profile()上下文管理器')
__init__ self.t: 0.0
__enter__ self.start: 1677419715.9893756
Profile()上下文管理器
__exit__ self.time(): 1677419715.9894035
__exit__ self.dt(delta-time): 2.5272369384765625e-05
__exit__ self.t(accumulate-dt): 2.5272369384765625e-05
Timeout上下文管理器
import signal
import platform
class Timeout(contextlib.ContextDecorator):
# YOLOv5 Timeout class. Usage: @Timeout(seconds) decorator or 'with Timeout(seconds):' context manager
def __init__(self, seconds, *, timeout_msg='', suppress_timeout_errors=True):
self.seconds = int(seconds)
self.timeout_message = timeout_msg
self.suppress = bool(suppress_timeout_errors)
print('__init__ self.seconds:',self.seconds)
print('__init__ self.timeout_message:',self.timeout_message)
print('__init__ self.suppress:',self.suppress)
def _timeout_handler(self, signum, frame):
raise TimeoutError(self.timeout_message)
def __enter__(self):
if platform.system() != 'Windows': # not supported on Windows
signal.signal(signal.SIGALRM, self._timeout_handler) # Set handler for SIGALRM
signal.alarm(self.seconds) # start countdown for SIGALRM to be raised
def __exit__(self, exc_type, exc_val, exc_tb):
if platform.system() != 'Windows':
signal.alarm(0) # Cancel SIGALRM if it's scheduled
if self.suppress and exc_type is TimeoutError: # Suppress TimeoutError
print('__exit__ self.suppress:',self.suppress)
print('__exit__ exc_type:',exc_type)
print('__exit__ exc_val:',exc_val)
print('__exit__ exc_tb:',exc_tb)
return True
if __name__ == "__main__":
context = Timeout(seconds=10,timeout_msg='Time Out!')
with context:
print('Timeout()上下文管理器')
print("\n--------------\n")
context = Timeout(seconds=10,timeout_msg='Time Out!')
with context:
print('Timeout()上下文管理器')
context._timeout_handler(signum=0, frame=0)
__init__ self.seconds: 10
__init__ self.timeout_message: Time Out!
__init__ self.suppress: True
Timeout()上下文管理器
--------------
__init__ self.seconds: 10
__init__ self.timeout_message: Time Out!
__init__ self.suppress: True
Timeout()上下文管理器
__exit__ self.suppress: True
__exit__ exc_type: <class 'TimeoutError'>
__exit__ exc_val: Time Out!
__exit__ exc_tb: <traceback object at 0x7fec7f443f50>
WorkingDirectory上下文管理器
import os
import sys
import time
from pathlib import Path
# FILE = Path(__file__).resolve()
FILE = Path("__file__").resolve()
ROOT = FILE.parents[1] # YOLOv5 root directory
class WorkingDirectory(contextlib.ContextDecorator):
# Usage: @WorkingDirectory(dir) decorator or 'with WorkingDirectory(dir):' context manager
def __init__(self, new_dir):
self.dir = new_dir # new dir
self.cwd = Path.cwd().resolve() # current dir
print("__init__ self.dir=新工作目录:",self.dir)
print("__init__ self.cwd=当前工作目录:",self.cwd)
def __enter__(self):
print('__enter__ 执行切换到新工作目录{}命令,'.format(self.dir))
os.chdir(self.dir)
def __exit__(self, exc_type, exc_val, exc_tb):
print("__exit__ 执行切换回原先工作目录{}命令,".format(self.cwd))
os.chdir(self.cwd)
if __name__ == "__main__":
context = WorkingDirectory(ROOT)
with context:
print('WorkingDirectory()上下文管理器')
__init__ self.dir=新工作目录: /mytest
__init__ self.cwd=当前工作目录: /mytest/working
__enter__ 执行切换到新工作目录/mytest命令,
WorkingDirectory()上下文管理器
__exit__ 执行切换回原先工作目录/mytest/working命令,