基类 派生类 类的继承与约束、python 项目架构逻辑设计
用法
1、在基类中定义所有的方法,在各个派生类中继承基类,派生类可以直接调用基类中的方法,基类中的方法就是默认配置方法,若项自定义方法可以在派生类中自定义方法。
2、cmdb 资产采集插件disk.py memory.py 每个插件类中都定义了相同的方法,所以为了简约代码相同的方法可以定义在基类中供这些插件去继承调用。每个插件执行时到需要读取settings.py 中的信息,此时可以在基类中import settings.py 中的配置变量信息,在插件中直接self.变量去引用。
3、在基类中定义方法,方法中抛出异常,异常内容为提示必须创建此方法,派生类继承基类,若派生类中为定义基类中方法则调用时抛出异常,提示派生类必须自定义该方法,此为约束。
继承默认配置
class BaseHandler(object): def func(self): print("方法一") def func1(self): print ("方法二") class AgentHandler(BaseHandler): """ 继承BaseHandler 无需自定义fun 等方法可以直接继承使用,若需要自定义新的fun方法可以自定义 """ def handler(self): pass
约束
class BaseHandler(object): def handler(self): """ 约束所有的派生类都必须实现handler方法 :return: """ raise NotImplementedError('handler must be implemented') class AgentHandler(BaseHandler): """ 继承BaseHandler 当调用handler方法时若自身无此方法则会抛出异常提醒 """ def handler(self): pass
实际应用实例(以cmdb 资产采集为例)
功能概述:资产采集分为ssh /salt /agent 三种模式,每种模式对应三种应用模块,每个应用类中都是固定的两个方法cmd和handler,cmd 实现salt/ssh 的远程登录功能,handler 实现主机列表信息获取,调用资产采集模块采集资产信息,发送数据到api。
模块类的继承关系: AgentHandler继承BaseHandler,SaltHandler、SSHHandler通过SaltAndSSHHandler间接继承BaseHandler,BaseHandler 下的cmd handler对它们进行约束;
SaltHandler和SSHHandler 下的handler 函数具有相同的功能及代码,所以handler 函数写在 SaltAndSSHHandler 类下供它们继承,为了能让它们继承Basehandler 类下的约束,SaltAndSSHHandler 继承Basehandler
src/engine/base.py
#!/usr/bin/python # -*- coding:utf-8 -*- import json import requests from config import settings from ..plugins import get_server_info class BaseHandler(object): def __init__(self): self.asset_api = settings.ASSET_API #获取配置文件中资产入库接口地址,供继承BaseHandler 的类调用 def cmd(self,command, hostname=None): #约束所有的派生类都必须实现handler方法 raise NotImplementedError('cmd must be implemented') def handler(self): """ 约束所有的派生类都必须实现handler方法 :return: """ raise NotImplementedError('handler must be implemented') class SaltAndSSHHandler(BaseHandler): def handler(self): """ 处理SSH/SALT模式下的资产采集 说明:由于ssh或salt 模式都是先获取主机列表,在开启线程池批量远程获取数据,具有相同的方法, 所以在SaltAndSSHHandler 基类中定义handler 方法供他们继承 :return: """ #引入线程池 from concurrent.futures import ThreadPoolExecutor # 1. 获取未采集的主机的列表 r1 = requests.get(url=self.asset_api) hostname_list = r1.json() #2、实例一个20个线程的线程池对象,循环主机列表,每次20线程同时执行资产采集发送任务。 pool = ThreadPoolExecutor(20) for hostname in hostname_list: pool.submit(self.task, hostname) def task(self, hostname): """ 资产采集,数据发送 说明:由于开启了线程池,资产采集的操作必须以函数形式放在for 循环中以线程池的方式批量同时执行。 :param hostname: :return: """ info = get_server_info(self, hostname) # 2. 发送到api r1 = requests.post( url=self.asset_api, data=json.dumps(info).encode('utf-8'), headers={ 'Content-Type': 'application/json' } ) print(r1)
src/engine/agent.py
#!/usr/bin/python # -*- coding:utf-8 -*- import json import requests from .base import BaseHandler from ..plugins import get_server_info class AgentHandler(BaseHandler): def cmd(self,command,hostname=None): import subprocess return subprocess.getoutput(command) def handler(self): """ 处理Agent模式下的资产采集:网卡、内存、硬盘,并发送到接口 :return: """ # 1. 通过调用get_server_info获取所有的资产信息:网卡、内存、硬盘 info = get_server_info(self) # 2. 发送到api r1 = requests.post( url=self.asset_api, data=json.dumps(info).encode('utf-8'), headers={ 'Content-Type':'application/json' } ) print(r1)
src/engine/ssh.py
#!/usr/bin/python # -*- coding:utf-8 -*- from config import settings from .base import SaltAndSSHHandler class SSHHandler(SaltAndSSHHandler): def cmd(self, command, hostname=None): """ 调用paramiko远程连接主机并执行命令,依赖rsa :param hostname:主机名 :param command: 要执行的命令 :return: """ import paramiko private_key = paramiko.RSAKey.from_private_key_file(settings.SSH_PRIVATE_KEY) ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(hostname=hostname, port=settings.SSH_PORT, username=settings.SSH_USER, pkey=private_key) stdin, stdout, stderr = ssh.exec_command(command) result = stdout.read() ssh.close() return result
View Code
src/engine/salt.py
#!/usr/bin/python # -*- coding:utf-8 -*- from .base import SaltAndSSHHandler class SaltHandler(SaltAndSSHHandler): def cmd(self, command, hostname=None): """ 调用saltstack远程连接主机并执行命令(saltstack的master) :param hostname:主机名 :param command: 要执行的命令 :return: """ import salt.client local = salt.client.LocalClient() result = local.cmd(hostname, 'cmd.run', [command]) return result[hostname]
python 项目架构设计和类的使用总结
- 相同属性的功能模块放在相同目录(资产采集插件disk.py memory.py 都在放在plugins目录下)
- 每一个功能都放在一个单独的py文件中定义一个对应的类,而不是这些类都写在同一个py文件中。这样做的好处:1、便于维护,如果哪个模块删除修改单独进行不影响整体 2、每个都是独立的一个py 文件,这样可以对这些功能模块在配置文件中以字典的形式注册,此为可插拔可扩展模式。
- 不同的功能定义在各自的模块(py文件)下定义自己的类,每个功能类中要实现的功能通过定义不同的方法(函数)来分步骤实现。
- 各个功能类中每个功能类中相同属性的方法定义成相同的函数名称(AgentHandler、SSHHandler、类中都定义了handler、cmd 函数),有时候是必须名称一致。
- 由于不同条件下调用不同的功能类,根据配置文件配置而导入不同的类,此时类的引入为变量模式,要使用类中的方法时(cls.handler)此时方法名称必须一致。
- 上述请况不仅需要方法名成相同,而且每个类中必须存在handler 类,所以此处采用继承约束,定义一个基类供这些类继承,基类中定义handler 方法,调用此方法时抛出异常(提示派生类必须定义此方法,此为约束)
- 各个功能类中的方法不仅功能属性相同而且代码也一致,此时可在基类中定义此方法,供这些类来继承,这样既简化代码也更有逻辑性。
- A B C 三个类都有相同的方法且代码相同,可定义一个基类D定义这个方法即可,它们功能继承。于此同时A B 两个类也有另一个方法相同代码,此时定义一个基类E ,E中定义这个方法,那么AB 怎么能够同时继承E D 中的方法呢,此时A B 继承E,E继承D,AB 既继承了E 中的方法,也通过E 间接继承了D 中的方法。
- 多个模块中的类需要引入相同的模块中的变量,可以在它们继承的基类中定义 def __init__(self): self.asset_api = settings.ASSET_API ,然后再基类中通过self.asset_api 引入