Fork me on GitHub

第三个模块

面向对象

1、什么是面向对象?

  i.面向对象和面向过程的区别

 面向过程:核心是过程二字,过程指的是解决问题的步骤,设计一条流水线,机械式的思维方式;优点:复杂的问题流程化,进而简单化;缺点:可扩展性差

面向对象:核心就是对象二字,对象就是特征与技能的结合体;优点:可扩展性强;缺点:编程复杂度高。

  ii简述类、对象、实例化是什么?

一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型、模板。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法;

 一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同;

 实例化:把一个类转变为一个对象的过程就叫实例化

 

2、类与对象

  i.实例和实例化

 实例(对象):一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同;

实例化:把一个类转变为一个对象的过程就叫实例化。

  ii.解释一下构造函数中__init__(self)中的self是什么意思?

self,就是实例本身!你实例化时python解释器会自动把这个实例本身通过self参数传进去。

  iii.类变量和实例变量的区别和作用?

类变量是所有实例共有,实例变量是对象独有的数据;

静态变量在类中,不属于实例对象,属于类所有,只要程序加载了字节码,不用创建实例对象静态变量就会被分配空间,已经可以使用。

实例变量是某个对象的属性,只有实例化对象后,才会被分配空间,才能使用。类变量是所有对象共有,其中一个对象将它值改变,其他对象得到的就是改变后的结果;

而实例变量则属对象私有,某一个对象将其值改变,不影响其他对象;

 

3、继承和派生

class Parent(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age
class Child(Parent):
    def __init__(self,name,age,sex):
        Parent.__init__(self,name,age)
        #super.__init__(name,age)
d = Child('ago',18,'M')
print(d.name)

i. 以下python3代码最终结果是什么?也就是顺序是什么?继承广度优先和深度优先(继承的顺序)

class A(object):
    def test(self):
        print('A')
class B(A):
    def test(self):
        print('B')
class C(A):
    def test(self):
        print('C')
class D(B):
    def test(self):
        print('D')
class E(B):
    def test(self):
        print('E')
class F(D,E,C):
    def test(self):
        print('F')
f1 = F()
f1.test()
print(F.__mro__)

#打印:
F
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

    顺序是F--D--E--B--C--A--object

  py3中继承的是广度优先

ii. 继承和装饰器

在定义类时,可以从已有的类继承, 被继承的类称为基类(父类),新定义的类称为派生类(子类)。 

装饰器(deco):装饰函数的参数是被装饰的函数对象,返回原函数对象 装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象 概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

类装饰器:@property 装饰过的函数返回的不再是一个函数,而是一个property对象装饰过后的方法不再是可调用的对象,可以看做数据属性直接访问。

    @staticmethod (静态方法)把没有参数的函数装饰过后变成可被实例调用的函数, 函数定义时是没有参数的,可以不接收参数

    @classmethod (类方法)把装饰过的方法变成一个classmethod类对象,既能被类调用又能被实例调用。 注意参数是cls代表这个类本身。 一般来说,要使用某个类的方法,需要先实例化一个对象再调用方法。 而使用@staticmethod或@classmethod,就可以不需要实例化,直接类名.方法名()来调用。 这有利于组织代码,把某些应该属于某个类的函数给放到那个类里去,同时有利于命名空间的整洁。

iii. 继承和组合(把一个实例当成参数传给类

通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。

当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人

 用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3

组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合。

4、属性和绑定方法

i. 共有属性和私有属性

  _m:代表私有属性,仅能在实例的内部各方法(函数)中调用;隐藏一些功能的的实现细节,只给外部暴露调用。

  共有属性是所有对象都可以用的

5、多态和多态性

多态指的是一类事物有多种形态。动物有多种形态:人,狗,猪等

多态性指在不考虑实例类型的情况下使用实例;多态性分为静态多态性和动态多态性。

静态多态性:如任何类型都可以用运算符+进行运算;字符串、列表、数字都能用上加上,站在+号的角度。

6、封装

 封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。

 

7、构造方法__init__和析构方法__del__

  __init__()类的初始化,类被实例化的时候执行该函数,放置需要初始化的属性;

  __del__()回收资源的,外部主动调用执行,对象在作用域中调用完毕,跳出作用域就会被执行、释放内存空间。

 

8、以下代码执行后分别得到结果是什么?说明缘由

class A:
    def __test(self): #_A__test
        print('A.test')
    def func(self):
        print('A.func')
        self.__test()#b._A__test()
class B(A):
    def __test(self): #_B__test
        print('B.test')
b = B()
b.func()

#打印:
A.func
A.test

  实例化对象b先从自己这找func,没有再去自己的类里边找,也没有再去父类A里边找,得到A.func

  self.__test()相当于是b._A__test();在A类中,__test(self)将其封装了,实际上是_A__test;在B类中,__test(self)相当于_B__test,所以是A.test

class A:
    def test(self):
        print('A.test')
    def func(self):
        print('A.func')
        self.test()#b.test()
class B(A):
    def test(self):
        print('B.test')
b = B()
b.func()

#打印:
A.func
B.test

  实例化对象b先从自己这找func,没有再去自己的类里边找,也没有再去父类A里边找,得到A.func;self.test()相当于b.test()自己这没有,在自己的类B中找,得到B.test

9、特性property有什么作用?name.setter和name,deleter;什么是property,如何定义,如何使用,给谁用,什么情况下应该将一个属性定义成property,有什么好处?

  property是把一个方法转换成实例的属性。func = property(func) 或者@property来装饰方法(函数)

  此时的name是一个property对象。该对象下面有getter、setter、deleter等方法。

  只有@property表示只读;同时有@property和@x.setter表示可读可写;同时有@property和@x.setter和@x.deleter表示可读可写可删除。

  (参考:http://www.cnblogs.com/yangzhenwei123/p/6759311.html)

10、静态方法和类方法staticmethod和classmethod区别是什么?

class A:
    __role ='CHINA' #_A__role = 'CHINA'
    @classmethod #绑定到类的方法,由类来调用,把类当做第一个参数传进去
    def show_role(cls):
        print(cls.__role) #A._A__role
    @staticmethod #非绑定方法,对象和类都可以调用,没有自动传值了
    def get_role():
        return A.__role # A._A__role
    @property #让使用者使用的时候感知不到他是个调用一个功能,他以为自己是用的数据属性。必须要有个返回值作为它的结果
    def role(self):
        return self.__role #a._A__role
a = A()
print(a.role)
print(a.get_role())
a.show_role()

#打印:
CHINA
CHINA
CHINA

  __role在类中属于类的数据属性,被隐藏起来了,实际上是_A__role

  三个装饰器的作用见上边代码。

11、反射

使用反射的知识点查看类的静态属性role;使用反射的知识点调用类的func方法;请为b对象设置一个name属性,值为你的名字。

class B:
    role = 'CHINA'
    def func(self):
        print('in func')
b = B()


print(getattr(B,'role'))
getattr(b,'func')()
setattr(b,'name','kris')
print(b.name)

#打印:
CHINA
in func
kris

12、__getitem__ __delitem__ __setitem__ 的作用?

   可以让对象变成一个dict一样操作;

13、利用type语法生成下面这个类

class Test(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def sayhi(self,name):
        print('self',name,self.name)


class_name = 'Test'
class_bases = (object,)
class_body ="""
def __init__(self,name,age):
    self.name = name
    self.age = age
def sayhi(self,name):
    print('self',name,self.name)
"""
class_dic = {}
exec(class_body,globals(),class_dic)
Test = type(class_name,class_bases,class_dic)
print(Test)

#打印:
<class '__main__.Test'>

14、__new__()作用是什么,它和__init__ 有什么关系?

  __new__用来创建类对象,实例化一个cls的类对象,表示这个类已经被加载了,解析了,现在可以拿着这个类创建普通实例对象了。如果类对象创建失败,则不会调用init方法;其用处是:自定义方式创建类对象,比如改变这个类的属性等。  

  __new__ 用来创建实例,在返回的实例上执行__init__,如果不返回实例那么__init__将不会执行;实例化一个类时,最先被调用的方法 其实是 __new__ 方法

     __init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。

  参考:http://www.jb51.net/article/48044.htm

 

15、自定义元类利用metaclass   __new__   __init__   __call__创建一个类(定制类)

class MyType(type):
    def __init__(self, *args, **kwargs):
        print("元类init")
        super(MyType, self).__init__(*args, **kwargs)

    def __new__(cls, *args, **kwargs):
        print("new")
        return super().__new__(cls, *args, **kwargs)

    def __call__(self, *args, **kwargs):
        print("call")
        obj = self.__new__(self, *args, **kwargs)  # object.__init__(....)
        obj.__init__(*args, **kwargs)
        # self.__init__(obj,*args, **kwargs)
        return obj  ##没有这一步,结果也是一样的


class Foo(object, metaclass=MyType):
    def __init__(self):
        print('本类init')

x = Foo()


# new
# 元类init
# call
# 本类init

 

16、自定义类

i. 一个圆形类,属性是半径,提供两个方法,计算圆周长和面积

from math import pi
class Circle:
    def __init__(self,r):
        self.r = r
    def area(self):
        return self.r**2*pi
    def perimeter(self):
        return 2*pi*self.r
c1 = Circle(5)
print(c1.area())
print(c1.perimeter())

#打印:
78.53981633974483
31.41592653589793

ii. 实现如图的继承关系,然后验证经典类与新式类在查找一个属性时的搜索顺序

 经典类是深度优先;新式类是广度优先的查找顺序

iii. 基于多态的概念来实现linux中一切皆问题的概念:文本文件,进程,磁盘都是文件,然后验证多态性

import abc
class All_file(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def read(self):
        print('All file')
        pass
    @abc.abstractmethod
    def write(self):
        print('All file')
        pass
class Text(All_file):
    def read(self):
        super().read()
        print('Text read')
    def write(self):
        super().write()
        print('Text write')
class Sata(All_file):
    def read(self):
        super().read()
        print("Sata read")
    def write(self):
        super().write()
        print("Sata write")
def read(obj):
    obj.read()
def write(obj):
    obj.write()

t = Test()
s = Sata()
t.read()
s.write()
read(s)

#打印:

All file
Text read
All file
Sata write
All file
Sata read

 

iv. 定义老师类,把老师的属性:薪资,隐藏起来,然后针对该属性开放访问接口 

class Teacher():
    def __init__(self,name,sex,salary):
        self.name = name
        self.sex = sex
        self.__salary = salary
    def tell_salary(self):
        print('你的工资是%s'%self.__salary)
t1 = Teacher('alex','male',10000)
t1.tell_salary()

v. 定义如下类,并最大程度地重用代码(继承,派生:子类重用父类方法,组合)老师类 学生类 分数类 课程类 日期类

 

class People():
    school = 'luffycity'
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
class Teacher(People):
    def __init__(self,name,age,sex,salary):
        super().__init__(name,age,sex)
        self.salary = salary
    def teach(self):
        print('%s is teaching'%self.name)
class Student(People):
    def __init__(self,name,age,sex,class_time):
        super().__init__(name,age,sex)
        self.class_time = class_time
    def learn(self):
        print('%s is learning'%self.name)
class Course:
    def __init__(self,course_name,course_price):
        self.course_name = course_name
        self.course_price = course_price
    def tell_info(self):
        print("课程名%s,课程价钱%s"%(self.course_name,self.course_price))
class Data:
    def __init__(self,year,month):
        self.year = year
        self.month = month
    def tell_info(self):
        print('%s年%s月'%(self.year,self.month))
tea1 = Teacher('alex',33,'male',10000)
stu1 = Student('kris',22,'male',7)
python = Course('python','8999')
data1 = Data(2018,4)

tea1.course = python
print(python)

 

 

vi. 基于授权定制自己的列表类型,要求定制的自己的__init__方法

vii. 定制自己的append:只能向列表加入字符串类型的值

viii. 定制显示列表中间那个值的属性(提示:property)

 ix. 其余方法都使用list默认的(提示:__getattr__加反射)

class List:
    def __init__(self,value):
        self.x=list(value)
    def append(self,value):
        if not isinstance(value,str):
            raise TypeError('append到列表的内的元素必须是str类型')
        self.x.append(value)
    def insert(self,index,value):
        self.x.insert(index,value)
    def __getattr__(self, item):
        return getattr(self.x,item)
    @property
    def type(self):
        print(self.x)
        t=int(len(self.x)/2)
        print(self.x[t],type(self.x[t]))
l=List([1,2,3])
l.append("egon")
l.append('hello')
l.append('alex')
l.insert(7,5)
l.pop()
l.type

#打印
[1, 2, 3, 'egon', 'hello', 'alex']
egon <class 'str'>

 

 x. 定义用户类,定义属性db,执行obj.db可以拿到用户数据结构

#user.db文件
{
    "egon":{"password":"123",'status':False,'timeout':0},
    "alex":{"password":"456",'status':False,'timeout':0},
}
class User:
    db_path='user.db'
    def __init__(self,username):
        self.username=username
    @property
    def db(self):
        data=open(self.db_path,'r').read()
        return eval(data)
u=User('egon')
print(u.db['egon'])
print(u.db['egon']['password'])

#打印:

{'password': '123', 'status': False, 'timeout': 0}
123

  xii. 根据下述原理,编写退出登录方法(退出前判断是否是登录状态),自定义property,供用户查看自己账号的锁定时间

参考:http://www.cnblogs.com/DragonFire/p/6748082.html

import time
class User:
    db_path='user.db'
    def __init__(self,name):
        self.name=name
    @property
    def db(self):
        with open(self.db_path,'r') as read_file:
            info=read_file.read()
            return eval(info)
    @db.setter
    def db(self,value):
        with open(self.db_path,'w') as write_file:
            write_file.write(str(value))
            write_file.flush()
    def login(self):
        data=self.db
        if data[self.name]['status']:
            print('已登录')
            return True
        if data[self.name]['timeout'] < time.time():
            count=0
            while count < 3:
                passwd=input('password>>: ')
                if not passwd:continue
                if passwd == data[self.name]['password']:
                    data[self.name]['status']=True
                    data[self.name]['timeout']=0
                    self.db=data
                    break
                count+=1
            else:
                data[self.name]['timeout']=time.time()+10
                self.db=data
        else:
            print('账号已锁定10秒')
u1=User('egon')
u1.login()
u2=User('alex')
u2.login()

网络编程

1、三次握手/四次挥手

基于tcp协议,客户端给服务端发一次消息,服务端要回应下并且给发给客户端,然后客户端再发给服务端,中间两步回应下+给客户端发消息可以合成一步,链接建立完成也就是三次握手;客户端说要断开链接PIN=1,服务端确认下ack=1,客户端接收到了,这条管道就断开了,服务端要断开发PIN=1,客户端回一个ack=1,管道就断开了。

客户端说把数据传完了,服务端不一定传完数据了,中间那两步不能合成一步,所以断开链接需要四次挥手。

2、osi七层模型

物理层功能:主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0。

数据链路层的功能:定义了电信号的分组方式按照以太网协议;一组电信号构成一个数据包,叫做一组数据‘帧’;每一数据帧分成:报头head和数据data两部分。head前六个字节和后六个字节是mac地址,基于mac地址来标示对方;在局域网内以广播的方式工作。

网络层功能:引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址。

传输层功能:建立端口到端口的通信,端口即应用程序与网卡关联的编号。tcp和udp

应用层功能:有自己的协议如http、ftp协议,跑应用软件。

 

3、ip

 每台机器配个ip地址,有一个IP头和data数据。IP地址和mac地址就可以找到世界上一台独一无二的机器。IP找到子网,mac找到子网它在哪个位置。

IP协议:

  • 规定网络地址的协议叫ip协议,它定义的地址称之为ip地址,广泛采用的v4版本即ipv4,它规定网络地址由32位2进制表示
  • 范围0.0.0.0-255.255.255.255
  • 一个ip地址通常写成四段十进制数,例:172.16.10.1

4、socket 写一个简单的socket的服务端和客户端

##服务端
import socket
ph = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ph.bind(('127.0.0.1',8081))
ph.listen(5)
print('starting')
conn,client_addr = ph.accept()
print(client_addr)
while True:

    data = conn.recv(1024)
    conn.send(data.upper())
conn.close()
ph.close()



###客户端
import socket
ph = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ph.connect(('127.0.0.1',8081))
while True:
    msg = input('>>>:')
    ph.send(msg.encode('utf-8'))
    data = ph.recv(1024)
    print(data)
ph.close()

 

5、黏包,解释下黏包现象,如何解决。

 客户端只recv(1024), 可结果比1024长只好在服务器端的IO缓冲区里把客户端还没收走的暂时存下来,等客户端下次再来收,所以当客户端第2次调用recv(1024)就会首先把上次没收完的数据先收下来,再收df命令的结果。 这个现象叫做粘包,就是指两次结果粘到一起了。它的发生主要是因为socket缓冲区导致的。问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

 服务端:1收命令;2执行命令,拿到结果;3把命令的结果返回给客户端:3.1制作固定长度的报头;3.2先发送报头的长度;3.3再发报头;3.4再发真实数据

客户端:1发命令;2拿到命令的结果并打印下;2.1先收报头的长度;2.2再收报头;2.3从报头中解析出对真实数据的描述信息;2.4接收真实数据。

报头:字典的形式里有文件名,状态码,文件大小等等

报头长度 struct

6、异常,自定义一个异常

try:
    print('--before---')
    l = [1,2,3]
    l[100]
    print('--->>>')
except Exception as e:
    print('异常发生了:',e)
print('after---')

 

#自定义个异常类型
class Myexception(BaseException):
    def __init__(self,msg):
        super(Myexception,self).__init__()
        self.msg = msg
    def __str__(self):
        return '<%s>'%self.msg
raise Myexception('我自己的异常类型')

7、socket和socketserver的区别是什么?

socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一 般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。 

SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进 程” 专门负责处理当前客户端的所有请求。

8、写一个客户端调用服务端的系统命令的程序

 

import socket
import subprocess
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8081))
phone.listen(5)
print('starting')
while True:
    conn,client_addr = phone.accept()
    print(client_addr)
    while True:
        try:
            #1收命令
            cmd = conn.recv(1024)
            #2执行命令拿到结果
            obj = subprocess.Popen(cmd.decode('utf-8'),shell= True,
                                        stdout = subprocess.PIPE,
                                        stderr = subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            #3把命令结果返回给客户端
            print(len(stdout)+len(stderr))
            conn.send(stdout+stderr)
        except ConnectionResetError:
            break
    conn.close()
phone.close()

 

##服务端

import socket
import subprocess
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8081))
while True:
    #1发命令
    cmd = input('>>>:').strip()
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))
    #2拿命令结果并打印
    data = phone.recv(1024)
    print(data.decode('gbk'))
phone.close()

 

编程题

1.定义两个类(人)实例化出:老王和小明

  i.共同属性及技能:出生地,吃饭

  不同属性及技能:a.属性:名字,年龄;

          b.老王技能1:讲课;

          c.老王技能2:布置作业

          d.小明技能1:罚站

          e老王技能3:打小明(假设小明有100点血,被打之后就掉了50点血了)

        使用到了继承:共同技能和属性

        定义了老王的攻击力:50

        定义了小明的血量:100 

class People1():
    birthday = 'China'
    def __init__(self, name, age, life_value, aggresivity):
        self.name = name
        self.age = age
        self.life_value = life_value
        self.aggresivity = aggresivity
    def eat(self):
        print("%s is eating"%self.name)
    def talk_lesson(self):
        print("%s is talking lesson"%self.name)
    def have_homework(self):
        print("%s could hane_homework"%self.name)
    def attack(self,xm_obj):
        print('%s 打了%s'%(self.name,xm_obj.name))
        xm_obj.life_value -= self.aggresivity

class People2:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        self.life_value = 100
    def stand(self):
        print('%s is standing'%self.name)
lw = People1('老王','32',80,50)
xm = People2('kris',22)

print(xm.life_value)
lw.attack(xm)
print(xm.life_value)

 

 ii.模拟cs游戏

    人物角色分为警察和匪徒两种,定义成两个类;所有的警察角色都是police;每个警察都有自己独有名字,生命值,武器,性别;每个人都可以开枪攻击敌人,且攻击目标不能是police;

    所有的匪徒的角色都是terrorist;每个匪徒都有自己独有名字,生命值,武器,性别;每个人都可以开枪攻击别人,且攻击目标不是是terrorist; 

    a,实例化一个警察,一个匪徒,警察攻击匪徒,匪徒掉血

class Police():
    def __init__(self,name,sex,life_value,weapon,aggresivity):
        self.name = name
        self.sex = sex
        self.life_value = life_value
        self.weapon = weapon
        self.aggresivity = aggresivity
    def attack(self,terro_obj):
        print('%s attack %s'%(self.name,terro_obj.name))
        terro_obj.life_value -= self.aggresivity
class Terrorist(Police):
    def __init__(self, name, sex, life_value, weapon, aggresivity):
        super().__init__( name, sex, life_value, weapon, aggresivity)
    def attack(self,poli_obj):
        print('%s attack %s'%(self.name,poli_obj.name))
        poli_obj.life_value -= self.aggresivity
p1 = Police('kris','male',100,'kk47',50)
t1 = Terrorist('galen','female',100,'倚天剑',20)
print(t1.life_value)
p1.attack(t1)
print(t1.life_value)

 

综合题

1.远程执行命令(subprocess执行命令)saltstack 

2.远程配置管理(传输文件) saltstack

3.根据配置进行执行命令

4.远程传输文件 

 https://blog.csdn.net/chengyuqiang/article/details/78119322  saltstack入门简介

 

 

posted @ 2018-04-17 10:50  kris12  阅读(461)  评论(0编辑  收藏  举报
levels of contents