如何重构一个简单的命令行脚本以面向对象?

dwo*_*dwo 3 python oop refactoring

我没有很多面向对象Python的经验,想要重构一个简单的命令行工具.我当前的脚本只是导入了所需的库,有一些函数定义,使用全局变量(我知道这是不好的做法)并在一个.py文件中使用argparse,例如:

import argparse

dict = {}

#Some code to populate the dict, used both in checking the argument and later in the script

def check_value(value):
    if not dict.has_key(value):
        raise argparse.ArgumentTypeError("%s is invalid." % value)
    return value

parser = argparse.ArgumentParser(…)
parser.add_argument('…', type=check_value, help='…')

args = parser.parse_args()

# Some other code that uses the dict
Run Code Online (Sandbox Code Playgroud)

同样,我处理一些参数解析的方式使用类似于"abc"的函数来修改我稍后在脚本中需要的字典.我怎么让这个不那么难看?或者使用简单命令行脚本可接受的全局变量?

Ale*_*rry 5

让我们创建一个模仿我们的dict的单个类,并将其传递给这些方法,而不是对全局变量进行操作,你可以告诉我这是否更符合你的要求.事实是,你已经在使用一些面向对象的编程并且不知道它(dict类)

第1步:Dict类

Python面向对象编程围绕着类,它可以被实例化一次或多次,并且这些使用在它们上定义的函数.然后我们调用方法classname.class_method(input_variables)并获取返回值,就像我们调用非绑定函数一样.事实上,全局函数和显式绑定到类实例的函数之间存在明确的差异.这是"绑定"和"未绑定"方法之间的区别,也是我们获得魔术名称的地方self.

class ExampleDict(object):

    #Called when a new class instance is created
    def __init__(self, test):
        self.dict = {}
        self.dict["test"] = test

    #Called when len() is called with a class instance as an argument
    def __len__(self):
        return len(self.dict)

    #Called when the internal dict is accessed by instance_name[key]
    def __getitem__(self, key):
        return self.dict[key]

    #Called when a member of the internal dict is set by 
    #instance_name[key] = value
    def __setitem__(self, key, value):
        self.dict[key] = value

    #Called when a member of the internal dict is removed by del instance[key]
    def __delitem__(self, key):
        del self.dict[key]

    #Return an iterable list of (key, value) pairs
    def get_list(self):
        return self.dict.items()

    #Replace your global function with a class method
    def check_value(self, key):
        if not self.dict.has_key(value):
            raise argparse.ArgumentTypeError("%s is invalid." % value)
        return value
Run Code Online (Sandbox Code Playgroud)

几点说明:

  • 此类定义必须在创建任何实例之前出现
  • 使用以下语法创建Class实例: d = ExampleDict()

第2步:删除全局变量

现在,我们想要删除全局变量的使用.在许多情况下,如上所述,我们可以将您的方法转换为类方法.如果这不合适,您可以接受一个对象作为全局函数的参数.在这种情况下,我们希望您的方法接受它作为参数操作的字典,而不是直接对全局变量进行操作

def check_value_global(inp_dict, value):
    if not inp_dict.has_key(value):
        raise argparse.ArgumentTypeError("%s is invalid." % value)
    return value
Run Code Online (Sandbox Code Playgroud)

第3步:声明类的实例并使用它们

现在,让我们定义当我们运行脚本并声明一些类实例时会发生什么,然后将它们传递给方法或执行它们的类方法:

if __name__ == "__main__":

    #Declare an instance of the class
    ex = ExampleDict("testing")

    print(ex["test"])
    ex["test2"] = "testing2"
    check_value_global(ex, "test")
    print("At next section")
    ex.check_value("test2")
    print("At final section")
Run Code Online (Sandbox Code Playgroud)

有关Python中面向对象编程功能的更深入讨论,请参见此处

根据评论进行编辑

好的,让我们来看看特别是argparse.这将解析提供给脚本的命令行参数(这里的替代方法只是从sys.argv读取).

从理论上讲,我们可以将它包含在任何地方,但我们确实应该直接包含它if __name__=="__main__":,或者在此语句之后调用的方法中包含它.为什么?

只有在直接从命令行调用python脚本而不是作为模块导入时,才会运行此语句之后的任何内容.假设你想将你的脚本导入另一个python脚本并在那里使用它.在这种情况下,您不需要命令行参数,因此您不希望尝试解析它们.

尽管如此,我们现在知道我们在主段(后if __name__=="__main__":)中初始化了一个dict对象和一个argparse对象.我们如何将它们传递给程序中上面定义的函数和类?

我们有很多选择,我最常用的是:

  • 在适当的情况下,我们可以重新定义我们正在使用的dict类,以允许在初始化之后将方法作为类方法调用
  • 我们可以将对象传递给函数,如上面的步骤2所示
  • 我们可以定义一个单例类,它接受dict和argparse对象的参数并存储它们.然后,相关区域中的所有主要程序流都通过单例运行,并且这些引用始终可用

这是一个例子:

class SingletonExample(object):

    def __init__(self, dict_obj, arg_obj):
        self.dict = dict_obj
        self.args = arg_obj

    def some_script_function(self):
        pass
        #Use your self.dict and self.args arguments
Run Code Online (Sandbox Code Playgroud)

事实上,你真的在​​谈论设计模式,你解决这个问题的方式将由你选择的设计决定.例如,这里的第三种解决方案通常被称为单例模式.确定最适合手头任务的模式,对其进行一些研究,这将告诉您如何构建对象和方法.