【设计模式】使用闭包实现数据集加载过程的依赖倒转

重点

数据集和数据集使用到的参数可以保持不同,将这些不同放到配置文件中进行处理而不是修改获取数据集的加载代码,优点是:

  • 减少修改代码的出错
  • 统一数据加载的接口格式


前言

为了方便地加载异构的数据集,同时保持模型层面和训练层面代码中抽象层的一致性,我们可以利用python中的闭包机制。
这个设计将使用的数据集和参数交给配置文件,而不是和代码中调用数据集的部分发生耦合;
如此的设计符合模式设计中七大原则的依赖倒转原则:依赖于抽象,不依赖于具体实现。


一、什么是装饰器

当使用@my_decorator语法时,是在应用一个以单个函数作为参数的一个包裹函数
装饰器能够方便地给一个函数加上前后的钩子,用于给函数的行为添加附属操作。下面是分别是原始的装饰器定义和使用。

1、装饰器的定义

def a_new_decorator(a_func):
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrapTheFunction

2、装饰器的使用:把当前函数看作装饰器的一个参数

def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")
 
a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"
 
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()
 
a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
#        I am the function which needs some decoration to remove my foul smell
#        I am doing some boring work after executing a_func()

3、使用@简化装饰器的使用

为了简化装饰器的使用,在当前函数前使用@a_new_decorator来代表对装饰器的引用,简化后的代码表示如下

@a_new_decorator
def a_function_requiring_decoration():
    """Hey you! Decorate me!"""
    print("I am the function which needs some decoration to "
          "remove my foul smell")
 
a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
#         I am the function which needs some decoration to remove my foul smell
#         I am doing some boring work after executing a_func()
 
#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

其中,在装饰器的内部函数前经常使用@wraps(a_func)来重写函数的名字和注释文档(docstring)

这样能够在print(a_function_requiring_decoration.name)时候,正确打印出a_function_requiring_decoration

4、使用@并带有参数的装饰器

from functools import wraps
 
def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打开logfile,并写入内容
            with open(logfile, 'a') as opened_file:
                # 现在将日志打到指定的logfile
                opened_file.write(log_string + '\n')
            return func(*args, **kwargs)
        return wrapped_function
    return logging_decorator
 
@logit()
def myfunc1():
    pass
 
myfunc1()
# Output: myfunc1 was called
# 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串
 
@logit(logfile='func2.log')
def myfunc2():
    pass
 
myfunc2()
# Output: myfunc2 was called
# 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串

5、应用:日志、使用类封装的装饰器

日志

from functools import wraps
 
def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging
 
@logit
def addition_func(x):
   """Do some math."""
   return x + x
 
result = addition_func(4)
# Output: addition_func was called

print(result)
# Output: 8

使用类进行封装

from functools import wraps
 
class logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile
 
    def __call__(self, func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打开logfile并写入
            with open(self.logfile, 'a') as opened_file:
                # 现在将日志打到指定的文件
                opened_file.write(log_string + '\n')
            # 现在,发送一个通知
            self.notify()
            return func(*args, **kwargs)
        return wrapped_function
 
    def notify(self):
        # logit只打日志,不做别的
        pass

可以新建装饰器的子类

class email_logit(logit):
    '''
    一个logit的实现版本,可以在函数调用时发送email给管理员
    '''
    def __init__(self, email='admin@myproject.com', *args, **kwargs):
        self.email = email
        super(email_logit, self).__init__(*args, **kwargs)
 
    def notify(self):
        # 发送一封email到self.email
        # 这里就不做实现了
        pass

使用

@logit()
def myfunc1():
    pass

除此之外,还经常使用装饰器用于Web应用的授权检测

二、数据集参数与加载过程之间的解耦

1、使用python闭包和字典来存储不同的数据集

闭包侧重描述函数访问的变量范围,即在嵌套的两个函数中,内层函数能够访问外层函数的变量。
闭包允许一个函数捕获其所在的词法作用域的局部变量,即使该函数在其声明之外的范围内被调用。
这个例子中,decorator 函数接收一个类作为参数,并将其添加到 datasets 字典中。decorator 函数内部可以访问外层函数 register 的变量,即name 的值。

比如下面是dataset.py文件中的内容

import os

DEFAULT_ROOT = './materials'

datasets = {}
def register(name):
    def decorator(cls):
        datasets[name] = cls
        return cls
    return decorator
    
def make(name, **kwargs):
    if kwargs.get('root_path') is None:
        kwargs['root_path'] = os.path.join(DEFAULT_ROOT, name)
    dataset = datasets[name](**kwargs)
    return dataset

这段代码首先定义了一个装饰器函数register,它接受一个名称作为参数。这个装饰器函数返回另一个函数decorator,decorator接受一个类cls作为参数。在decorator内部,将类cls注册到datasets字典中,键为name,值为cls。最后返回类cls。

然后定义了一个工厂函数make,它接受一个名称name和任意数量的关键字参数kwargs。函数首先检查kwargs中是否包含root_path,如果没有,则使用默认的根目录和名称来构建root_path。然后,通过调用datasets[name]来获取注册的类,并使用kwargs创建这个类的实例。最后返回这个实例。

这段代码的主要用途是简化数据集类的注册和实例化过程。用户可以通过装饰器注册自己的数据集类,然后使用工厂函数make来创建这些数据集的实例,而不需要每次都手动指定类名和参数。

2、使用python高阶函数:进行数据加载类的指定、调用

高阶函数是接受函数作为参数或返回函数的函数。虽然它们本身不是闭包,但它们经常与闭包一起使用。

def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

add_five = outer_function(5)
print(add_five(3))  # 输出: 8

高阶函数可以先使得嵌套函数的外层先发生作用,并返回未被执行的内层函数,给返回的内层函数起别名后,能够通过别名单独调用内层函数。

3、注意事项

  1. 注册时需要使用装饰器:用户需要使用@register(‘name’)装饰器来注册数据集类。
  2. 默认根目录:如果没有指定root_path,将使用默认的根目录和名称来构建路径。
  3. 数据集类:注册的数据集类需要接受任意数量的关键字参数,以便在创建实例时传递。

4、使用示例

@register('my_dataset')  
class MyDataset:  
    def __init__(self, root_path):  
        self.root_path = root_path  
        # 初始化代码  

# 创建数据集实例  
dataset = make('my_dataset', root_path='/custom/path')

优势分析:数据集的加载被包装成统一的make函数,方面调用。

config文件的书写示例

model: meta-baseline
model_args: 
    encoder: resnet18
    encoder_args: {}
load_encoder: ./save/classifier_image-folder_resnet18/epoch-last.pth

freeze_bn: True

n_way: 5
n_shot: 1
n_query: 15
train_batches: 500
ep_per_batch: 4

config文件的加载

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--config')
    parser.add_argument('--name', default=None)
    parser.add_argument('--tag', default=None)
    parser.add_argument('--gpu', default='0')
    args = parser.parse_args()

    config = yaml.load(open(args.config, 'r'), Loader=yaml.FullLoader)
    if len(args.gpu.split(',')) > 1:
        config['_parallel'] = True
        config['_gpu'] = args.gpu

三、其他python编程技巧

1、 偏函数应用

偏函数应用(Partial Function Application)允许你固定函数的一些参数,从而创建一个新的函数。

from functools import partial  

def multiply(x, y):
	print(x)
    return x * y  

double = partial(multiply, 2)  
print(double(5))  

# 输出: 
# 2 
# 10  

2、生成器函数

生成器函数是使用yield语句的函数,它们可以生成一系列值,而不是一次性返回一个值。

def count_up_to(n):  
    count = 1  
    while count <= n:  
        yield count  
        count += 1  

for number in count_up_to(5):  
    print(number)  

3、类方法

类方法是一种特殊的方法,它绑定到类而不是类的实例。它们可以通过类本身或类的实例调用。

class MyClass:  
    @classmethod  
    def class_method(cls):  
        print("This is a class method.")  

MyClass.class_method()  # 输出: This is a class method.

4、静态方法

静态方法是一种特殊的方法,它不绑定到类或类的实例。静态方法可以通过类本身或类的实例调用。

class MyClass:  
    @staticmethod  
    def static_method():  
        print("This is a static method.")  

MyClass.static_method()  # 输出: This is a static method.

5、上下文管理器

上下文管理器是一种使用with语句的类,它定义了进入和退出上下文时的行为。

class MyContextManager:
    def __enter__(self):
        print("Entering the context")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Exiting the context")

with MyContextManager():
    print("Inside the context")
# Output::
# Entering the context
# Inside the context
# Exiting the context

总结

参考链接 :

(1)https://www.runoob.com/w3cnote/python-func-decorators.html

设计模式的总结:
https://developer.aliyun.com/article/950358

  • 29
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值