s14 第6天 subprocess模块 面向对象
subprocess模块
ret = os.system("command") |
输出命令的结果,但是只返回命令的执行状态,因此ret的值在正常返回为0,不正常为非0 |
|
ret = os.popen("command").read() |
此命令返回命令执行的结果,ret是命令的输出,但是没有执行状态 |
|
import commands ret = commands.getstatusoutput("command") |
在python 2.7中存在,3.5中已经不存在的模块,并且在windows下执行会有问题。 |
|
import subprocess |
以列表方式传入命令的每一个参数,例如linux下的命令df -h就写成subprocess.run(["df","-h"]) 但是这种写法不支持有管线"|"的命令,如果有管线的命令就需要写为: subprocess.run("df -h |grep sda1",shell=True),其中shell=True代表前面的字符串将由linux解析 |
|
subprocess.call() |
就是os.system(),只返回程序的执行状态,不返回结果。同样如果有管线则应该使用shell=True参数,其实此参数也可以在无管线的命令中使用 |
|
subprocess.check_call() |
如果命令执行状态正常就返回0,否则抛异常 |
|
subprocess.getstatusoutput("command") |
以元祖形式返回(命令的执行状态,命令的结果)和2.7的commands.getstatusoutput()一样 |
ret=subprocess.getstatusoutput("ipconfig") |
subprocess.getoutput() |
仅获取命令输出 |
ret = subporcess.getoutput("ipconfig")和上面的ret[1]一样 |
subprocess.check_output() |
如果命令正常执行,则返回结果,否则报错 |
|
subprocess.Popen("command",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) |
stdout就是一个标准屏幕输出,stderror是命令执行错误时屏幕上显示的报错内容,stdin是一个屏幕输入
subprocess.PIPE参数代表开辟一个管道(内存空间),用来将Popen的命令输出保存起来
因此当我们需要打印结果时,则需要到stdout中读取,见例子,同时因为他可以将stdout和stderror分别保存在独立的PIPE中,因此我们也就可以分别获取正确的输出和错误的报错。
Popen方法是一个底层封装,上面使用的集中subprocess的方法内部都是封装的Popen()方法,使用这个方法可以自己结合处很多新的方法
Popen方法的参数:
shell:让操作系统自行解析命令,当shell=True时无需像上面一样输入,只需要subprocess.Popen("df -h",shell=True,stdout=subprocess.PIPE)即可
cwd:用于设置子进程的当前目录
env:用于指定子进程的环境变量,如果env = None,子进程的环境变量将从父进程集成
universla_newlines:自动兼容操作系统的换行符,这个设置默认是打开的
|
ret = subprocess.Popen("ipconfig",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
print(ret.stdout.read())打印命令的输出 print(ret.stderr.read())打印报错 |
ret = subprocess.Popen("command",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE)
ret.stdout.read() |
ret.stout.read(),从标准输出中读取结果 |
|
ret.poll() |
用来检查命令的执行状态,因为当我们使用Popen命令时如果命令需要执行很长时间实际我们从输出上是看不到的,命令相当于后台运行我们在命令行里看不到执行进程,而poll()方法实际就是一种可以由手动发起的检测,用来检测当前命令的执行状态,
如果程序没有执行完,则返回None 如果执行完毕,则返回程序执行状态 0为正常,异常为非0 |
|
ret.wait() |
wait()方法则是将程序调用到前台执行,也就是此时什么也做不了只能等待程序运行结束,运行正常返回0否则返回非0 |
|
ret.terminate() |
终止程序的运行 |
|
ret.communicate() |
在调用Popen后,可以向stdin(标准屏幕输入)中输入内容,之后通过communicate()方法得到输入的内容 |
obj = subprocess.Popen("python",shell=True,stdout=subprocess.PIPE,stdin=subprocess.PIPE,stderr=subprocess.PIPE)
obj.stdin.write(b"print(1) \n") obj.stdin.write(b"print(2) \n") obj.stdin.write(b"print(3) \n") obj.stdin.write(b"print(4) \n")
out = obj.communicate() print(out)
输出:(b'1\r\n2\r\n3\r\n4\r\n', b'') |
Python sudo输入密码
在linux中通过一条命令直接sudo 连同输入密码带执行命令的写法是:
echo "password" | sudo -S yum install vim
其中sudo -S就是屏幕标准输入stdin,之前的echo "password" 就是向stdin输入数据
因此当我们通过python需要执行sudo 命令时就可以写为
subprocess.Popen("echo 'password' | sudo -S yum install vim",shell=True)即可,此处password中需要用单引号以避免命令参数的双引号重复
面向对象
class类
是对一类拥有相同属性的对象的抽象、蓝图、圆形。在类中定义了这些对象都具备的属性(varialbes(data))、共同方法
object对象
一个对象是类的实例化后的实例,一个雷必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类指所有人,每个人是指具体的对象,人与人之间的共性,亦有不同
开发规范:
1、类名首字母大写
2、函数全小写
3、无论是类还是函数,在定义完名称后第二行开始应该写注释
面向对象的特性
Encapsulation 封装
在类中对数据的赋值、内部调用,对外部用户是透明的,这使得类变成一个胶囊或者容器,里面包含着类的属性,数据和方法。
封装的作用有2:1、防止数据被随意修改 2、使外部程序不需要关注对象的内部构造,只需要通过对象对外提供的接口进行直接访问即可。
Inheritance 继承
一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
定义时共性可以在父类中定义,而子类中是对象的个性,这样具象化子类即可创建有一个独立的对象
通过 父类-->子类的方式,以最小代码量的方式实现不同角色的共同点和不同点
Polymorphism 多态
一种接口,有不同表现形式
类的定义
定义一个简单的类
class Dog(object): |
# print("hello,i am a dog.") 类和函数一样直接打印那么创建对象就会执行,这样是不对的,因此需要执行应该放到函数中 |
def sayhi(self): |
|
print("hello,i am a dog") |
|
调用类(实例化)
d = Dog() |
# 机遇dog创建一个名叫d的实例 |
d.sayhi() |
# 对象.函数 即可调用 |
但是上面的类有一个问题,无法区分创建的对象,因为他们都是打印hello i am dog,我并不知道那个对象做的,
因此我需要传递一个参数
class Dog(object): |
|
def __init__(self,name): |
|
self.Name = name |
|
def sayhi(self): |
# print("hello,i am a dog,my name is",name) 这样会报错,因为name这个变量时在__init__函数中的局部变量,是不能使用到sayhi函数中的 |
print("hello,i am a dog,my name is",self.Name) |
# 这样是可以的, |
d = Dog("zhangsan") |
# 此处所传递的参数实际是传递给__init__(self,name)中的name,而类中的self实际就是对象,也就是Dog(d,"zhangsan"),而对应的在函数内就变成了d.Name="zhangsan",相当于给d这个对象的Name变量赋值为"zhangsan",所以在后面sayhi方法中我们需要传递的是self.Name,而不是name |
d2 = Dog("lisi") |
|
类中的self |
类是很多函数的组合,但是函数之间不能互相访问局部变量,因此就需要self来打通各个函数。self就是指代的就是类实例化后的实例。 |
def __init__() |
类中定义的这种函数,就是构造函数或构造方法,也称为初始化方法,如果不向类传递参数可以不定义 |
def sayhi() |
类中的其他方法,就是类的方法,此处指代出了构造函数外的其他所有类中的方法 |
所以在类中定义的函数应该始终将self作为第一个参数。
class test(): |
|
def __init__(self,*args,**kwargs): |
# 如果不传递参数可以不定义这个方法 |
def test(self): |
|
self应该使用存在,而这个类的实际形式参数应该写到其构造函数中 def __init__(self,...):
因为在构造函数中创建了变量(self.Name),而且我们也可以在其他的方法中直接使用self.Name来引用,因此我们也可以使用同样的方式来更改变量的值
class test(object):
def __init__(self,name)
self.Name = name
def test(self):
self.Name = new_name
类 --> 实例化 --> 实例对象
def __init__(self): 构造函数
self.name = name # 类的属性,成员变量
def sayhi(self): # 方法,动态属性
私有属性:
self.__heart = "Normal" ,使用__定义的属性是私有属性,私有属性无法被外部访问
class test(object):
def __init__(self):
self.__name = "test"
此时外部访问
a = test()
print(a.__name) 这是无法访问的
但是可以被类中定义的其他方法访问
self.__heart = "Normal" ,使用__定义的属性是私有属性,私有属性无法被外部访问
class test(object):
def __init__(self):
self.__name = "test"
def test(self):
print(self.__name) # 这是在方法中定义的打印,不是外部要求的打印
a = test()
a.test() # 这样运行到test()方法时定义的print可以self.__name打印出来
因此如果要在外部获取私有属性,则需要在类内部定义一个新的方法将私有属性返回,
这是一个比较安全的方法,这样得到的值是只读的
class test(object): |
|
def __init__(self): |
|
self.__name = "test" |
|
def get_name(self): |
|
return self.__name |
|
a = test()
print(a.get_name()) # 这样就可以获得__name的值
如果我不考虑安全性,而直接要求访问类的私有属性,则按照如下的方法获取:
对象._类名__私有属性
例如
a._test__name
公有属性:
直接定义在类中的属性是公有属性,这个属性可以被外部直接调用。公有属性和成员属性是有差异的(私有属性也是成员属性),成员属性是定义在构造函数中的,而公有属性是在类下直接定义的。
class test(object): |
|
nationality = "CN" |
# 公有属性 |
def __init__(self,name,age): |
|
self.name = name |
# 成员属性 |
self.__age = age |
# 私有属性 |
类的公有属性的更改有如下的方法:
1、直接调用类名更高:test.nationality = "US"
2、通过对象调整:
a1 = test("zhangsan",30)
a2 = test("lisi",44)
a1.nationality = "US"
但是如果通过类名调用,则所有实例对象都会被调整。而如果是通过实例对象调整,则只有本对象被调整
这是因为实例对象中的变量实际是类中的参数的引用,因此通过类.变量的调整就会调整所有对象中的公有属性。
但是一旦通过对象.变量的方式调整了,则会在对象的内存中创建一个开辟一个新的内存地址,创建了一个新的变量,因此这样就只改变对象的变量。
系统在对变量进行寻址时,首先选择对象中是否存在这个变量,如果没有则向类中寻找。
从位置看,类的公有属性和类的方法是同级的,因此实际上每个对象在调用类的方法时也并不是将类的方法复制到对象内存中,而是调用的类的方法的引用。因此这也是self存在的原因。因为是引用所以需要区分不同的对象,而self实际就是指代的创建的对象,所以类中的每个方法都必须有self参数
因此类中的方法实际也是公有的,那么如果我希望方法也是私有的,就需要在外部定义一个方法来替换
class test(object): |
|
nationality = "CN" |
# 公有属性 |
def __init__(self,name,age): |
|
self.name = name |
# 成员属性 |
self.__age = age |
# 私有属性 |
def test(self): |
|
print("123") |
|
|
|
a1 = test() |
|
a2 = test() |
|
a1.test() |
# 输出123 |
a1.test() |
# 输出123 |
def test():
print("234")
a1.test = test # 此处不要加括号,因为加了()就是执行函数,不加括号是为了将函数体赋值
a1.test() # 输出234
a2.test() # 输出123
a1.test()变成了私有的
类的析构方法
在类中定义如下方法:
class test(object):
def __del__(self):
print("...")
这个方法就是析构方法,此方法是在对象被删除时自动执行。
r1 = test()
del r1 # 这行的命令是在内存中将r1和所创建的r1的内存空间的关系移除,此时r1的内容并没有被内存回收,在此时会执行析构方法。当内存空间没有被引用(del r1就是这种情况),之后这块内存就会被python的内存回收机制收回。
析构方法的作用是做一些程序的收尾工作,比如当我们的程序连接了很多客户端,当我们del这个对象的时候,可以在__del__这个方法内定义与客户端断开连接等操作
单继承
继承有两种继承方法:
1、实现继承 是指使用父类的属性和方法二无需额外编码的能力
2、接口继承 是指使用父类的属性和方法的名称,但是子类必须提供实现的能力(子类重构父类的方法)
在考虑使用继承时,应注意 父类和子类之间应该是属于的关系,例如manager可以继承person,但是leg不能继承person。因为manager是一个人拥有人的完整特征,manager完全是属于person。但是leg只是人的一部分他没有人的特征(比如人有鼻子眼睛,但是leg没有)
继承的写法
class Person(object): |
|
def talk(self): |
|
print("。。。") |
|
class BlackPerson(Person): |
# 此时就是继承了Person,括号中的就是父类 |
def walk(self): |
|
print("walking") |
|
|
|
b = BlackPerson() |
|
b.talk() |
# 继承父类的方法 |
b.walk() |
# 调用子类自己的方法 |
当需要传递新的参数时,需要对构造函数先继承,后重构
class Person(object): |
|
def __init__(self,name,age): |
# 父类传递2个参数,name和age |
self.name = name |
|
self.age = age |
|
self.say_hi(self) |
# 可以在父类的构造函数中调用父类的方法,这样后续的子类继承父类构造函数时自动执行这个方法,也可以在每个子类中单独调用 |
def say_hi(self): |
|
pass |
|
class BlackPerson(Person): |
|
def __init__(self,sname,sage,a): |
# 此时需要在子类中新增一个a参数传入,那么久需要先继承父类的构造函数,在重构子类的构造函数,此时定义子类的构造函数的形式参数 |
Person.__init__(self,sname,sage) |
# 继承父类的构造函数,注意看此处参数的写法,实际是将子类的参数传递给父类的构造函数,所以这里是调用应该写实际参数名 |
self.a = a |
# 重构构造函数,定义新的属性 |
print(self.name,self.age,self.a) |
# 打印属性,注意看此时所有的参数都可以打印了,但是self.name 和 self.age实际是继承的父类里构造函数的调用,所以得到的结果继承的部分还是父类的,而子类的还是子类的参数 |
self.say_hi() |
# 因为这是继承了Person的类,所以我在子类的构造函数中就可以调用父类的方法 |
a = BlackPerson("zhangsan",30,"xx") |
|
由上面的设置可以看到,无论是如何继承,因为有了self,而self就是等于对象,所以才可以直接使用self.变量来调用,但是如果我在父类中定义一个变量,要计算有多少个“对象”(注意不是子类)调用了这个父类。
例如 一个学院成员的父类,一个老师和一个学生的子类分别继承了学院成员的父类,此时我在学院成员中定义了一个注册变量,没增加一个老师或学生这个变量要加1
那么在学院成员这个变量的定义就不能使用self,而应该是学院成员这个类名,因为如果使用self,只代表对象自己调用的此时,不能代表类被调用的次数
class SchoolMember(object): |
|
member = 0 |
# 定义公有属性,以便后续累加 |
def __init__(self,name,age,sex): |
|
self.name = name |
|
self.age = age |
|
self.sex = sex |
|
self.enroll() |
# 在父类中调用一个父类的方法,这样每个子类都会执行 |
def enroll(self): |
# 注册流程 |
SchoolMember.member += 1 |
# 此处本意是每次创建对象就要+1,但是如果使用self,则不会加因为self指代的是对象自己,对象本身不会重复调用。是类在重复调用,因此只能使用类.menmber才会累加,而这种调用方法实际就要求member必须是公有属性 |
print("number[%s] just enroll a new school membe [%s]".%(SchoolMember.member, self.name)) |
# 此处可以self.member,也可以SchoolMember.member,但决不可直接member |
|
|
class Teacher(SchoolMember): |
|
def __init__(self,name,age,sex,salary): |
|
SchoolMember.__init__(self,name,age,sex) |
|
self.salary = salary |
|
|
|
class Student(SchoolMember): |
|
def __init__(self,name,age,sex,fee): |
|
SchoolMember.__init__(self,name,age,sex) |
|
self.fee = fee |
|
|
|
t1 = Teacher("zhangsan",30,"F",3000) |
|
s1 = Student("lisi",26,"F",2000) |
|
self.__dict__ |
# 将对象的属性作为列表打印出来,类似: |
下面的例子是一个使用self.__dict__的例子
class Person(object):
def __init__(self,name,age):
self.name = name
self.age = age
def tell(self):
print(self.__dict__) # 在父类中定义打印对象属性,打印的格式是{'name': 'zhangsan', 'a': 'xx', 'age': 30}
for i,v in self.__dict__.items():
print(i,v) # 使用self.__dict__.items()的方式将key和value变为元组,并且赋值两个变量输出,结果为name zhangsan\n age 30...
class BlackPerson(Person):
def __init__(self,sname,sage,a):
Person.__init__(self,sname,sage)
self.a = a
print(self.name,self.age,self.a)
a = BlackPerson("liubo",30,"xx")
a.tell()
多继承
多继承时原则来说继承的多个类智能有一个有构造函数,不应该出现多个类多个构造函数对应不同的属性的情况
class Person(object):
def __init__(self,name,age):
self.name = name
self.age = age
def tell(self):
print(self.__dict__)
for i,v in self.__dict__.items():
print(i,v)
class School(object):
def school_addr(self, address):
print("school address is ", address)
class BlackPerson(Person,School): # BlackPerson继承了2个父类
def __init__(self,sname,sage,a):
# Person.__init__(self,sname,sage)
super(BlackPerson,self).__init__(sname,sage)
self.a = a
print(self.name,self.age,self.a)
a = BlackPerson("liubo",30,"xx")
a.tell()
a.school_addr("sh") # 第二个父类的方法自动获得
同时多继承我也可以对两个父类分别继承属性
class Person(object):
def __init__(self,name,age): # Person的构造函数
self.name = name
self.age = age
def tell(self):
print(self.__dict__)
for i,v in self.__dict__.items():
print(i,v)
class School(object):
def __init__(self,addr): # School的构造函数,并制定了一个新的属性
self.addr = addr
def school_addr(self):
print("school address is ", self.addr)
class BlackPerson(Person,School):
def __init__(self,sname,sage,a,addr): # 子类的构造函数
super(BlackPerson,self).__init__(sname,sage) # Person函数继承构造函数,使用新式类继承方法
School.__init__(self,addr) # School函数构造函数继承,使用的是经典类
self.a = a
print(self.name,self.age,self.a)
a = BlackPerson("liubo",30,"xx","sh")
a.tell()
a.school_addr() # 调用父类 School的方法
新式类 vs 经典类
子类继承父类函数并重构构造函数的时候又两种继承父类构造函数的方法
class Person: |
# 经典类创建 |
class Person(object): |
# 新式类创建 |
Person.__init__(self,sname,sage) |
# 经典类继承构造函数 |
super(BlackPerson,self).__init__(sname,sage) |
# 新式类继承构造函数 |
多继承时 继承顺序有区别
概念:
广度查询:函数在继承时从左至右继承,如果父类中都没有再想父类的父类里继承
例如:
祖父: A
父类:B继承A、C继承A
子类:D继承 B和C
此时在调用D时的查询顺序是 B -- C -- A
深度查询:函数在继承时首先在从左侧开始从父类一直查询到父类的父类,如果没有再到第二继承顺序从父类到父类的父类
例如
祖父:A
父类: B继承A、C继承A
子类:D继承 B和C
此时调用D 查询顺序是 B -- A -- C
在Python 3.0以后,无论是新式类还是经典类,继承顺序都是广度查询
在Python2.7以后,经典类是深度查询,而新式类则是广度查询
多态
继承和封装的目的是减少代码的重复性
而多态的目的是接口重用
python不直接支持多态,但是可以间接实现
实现的方式是在全局下定义一个函数,将对象传入,由这个函数来调用对应的接口
class Dog(object): |
# 定义类 |
def __init__(self,name): |
|
self.name = name |
|
|
|
def talk(self): |
|
return "wang!" |
|
|
|
class Cat(object): |
# 定义类 |
def __init__(self,name): |
|
self.name = name |
|
|
|
def talk(self): |
|
return "miao!" |
|
|
|
d = Dog("dahuang") |
|
c = Cat("xiaomi") |
|
|
|
def com_talk(obj): |
# 定义一个通用的方法,然后将创建好的对象传入,然后再调用对象的talk方法 |
print(obj.talk()) |
|
|
|
com_talk(c) |
# 这样之后调用talk方法时只需要调用函数再将对象传入即可,一个方法 根据传入对象的不同而有不通的结果 |
com_talk(d) |
|
1、什么是面向对象编程
- 以前使用函数
- 现在使用 类 + 对象
2、什么是类什么是对象,有什么关系
- 类 是函数的集合
- 对象是类的实例化,obj = 类()
- 对象使用函数,ojb.函数()
- 面向对象编程调用麻烦,同时每次创建一个对象就是将类里的方法全部加载进内存,内存开销也很大
- 有的场景下,函数变成实现一个功能很麻烦,此时面向对象就会比较简单
3、什么时候适用面向对象
- 当用函数时变成需要很多步骤都是重复的时候,面向对象可以将重复的方法抽象成为类中的方法调用
- 函数式
- def upload()
- # 连接服务器
- # 上传文件
- # 断开服务器
- def cmd()
- # 连接服务器
- # 输入命令
- # 断开服务器
- 面向对象
- class SSH(object):
- def __Init__(self,host,port,user,pwd)
- self.host = host
- .......
- def conncetion(self)
# 创建连接
- self.conn = 连接
- def close(self)
- # 断开连接
- self.conn.close()
- def upload(self):
- # 上传文件
obj = SSH(....) # 实例化
obj.conn() # 连接服务器
obj.upload() # 上传
obj.close() # 关闭
上面的方法最后实现起来所有的步骤是可以直接使用随意组合,不像函数式要为每种场景定义方法,并且每次都有重复的操作
- 创建模板,约束对象。同时定义很多个对象时。
- 当面对多个函数传递相同的参数的时候,可以将其定义为类,然后反复传递的参数只需要定义到构造函数中即可
4、self 公有属性 普通属性
self就是调用当前方法的对象
如上图,内存中会为类开辟内存空间,在创建对象后对象的内存空间会有一个类对象指针指向他所实例化的类
同时将自己(对象)传递给类的self参数。类的方法保存在类的内存中,而类的属性则会保存在对象的内存中
当有一种情景,我创建了一个省份的类
class province(object):
def __init__(self,name,count):
self.Name = name
self.Count = count
self.country = "中国"
在这种情况下,我明知每个省都属于中国,但是我仍然将country定义为普通属性,就会造成这个属性在每个对象中保存,无疑会浪费大量的内存空间。
此时我应该将其定义为类的公有属性,一旦定义为公有属性,则无论创建多少个对象这个属性都只创建一次,同时该属性也不再保存在对象中,而是保存在类的内存中
class province(object):
country = "中国" # 公有属性
def __init__(self,name,count):
self.Name = name
self.Count = count
如果每一个对象都会有共同的值,则这些值应该定义为公有属性
5、封装、继承
- 类中封装了公有属性和方法,对象中封装了普通属性的值
class F1(object):
def __init__(self,name):
self.Name = name
class F2(object):
def __init__(self,obj):
self.a = obj
class F3(object):
def __init__(self,obj):
self.b = obj
o1 = F1("ZS")
o2 = F2(o1)
o3 = F3(o2)
print(o3.b.a.Name)
上面的例子,最终输出的是是ZS,对象是可以相互调用的,os.b就是o2,那么o2.a就是o1,而o1.Name就是ZS,因此我通过o3来调用时就会变成o3.b.a.Name
- 继承时的self
class F2(object):
def a1(self):
self.a2() # 下面调用的obj是F3()因此当执行F3.a1()时F3没有会找F2,而这里相当于是obj.a2(),而obj是F3创建的,因此调用F3的a2输出F3A2
print("F2A1") # 之后再打印
def a2(self):
print("F2A2")
class F3(F2):
def a2(self):
print("F3A2")
obj = F3()
obj.a1()
6、
属性
普通属性(保存在对象中)
公有属性(保存在类中)
方法
普通方法(保存在类中,调用者是对象,至少有一个self参数)
静态方法(可以有任意个参数)
静态方法的创建
class F1(object):
def a1(self):
print("123")
当创建上面的对象时,因为没有定义构造函数所以会被提示有问题
此时我们可以通过@staticmethod来装饰这个函数,使这个函数变为静态方法。
class F1(object):
@staticmethod # 变为静态方法
def a1(): # 静态方法self就不需要了
print("123")
调用静态方法无需创建对象,直接通过类调用即可
F1.a1
静态方法可以直接调用无需创建的对象,这点很像原来不建立类而直接定义函数的方法
作业:
选课系统
角色:学校、学员、课程、讲师
要求:
1、北京、上海两所学校
2、创建 linux、python go3个课程,linux\py在北京开,go在上海开
3、课程包含,周期、价格,通过学校创建课程
4、通过学校创建班级,班级关联课程、讲师
5、创建学院时,选择学校,关联班级
创建讲师角色时要关联学校
6、提供两个角色接口
学员视图,可以注册,交学费,选择班级
讲师视图,讲师可以管理自己的班级,上课时选择班级,查看班级学员列表,修改所管理学员的成绩
管理视图,创建讲师,创建班级,创建课程
7、上面的操作产生的数据都通过pickle序列化保存到文件里