python第三十一课--选课系统代码总结

昨日内容回顾

  • 面向对象魔法方法

    1.__init__
    	对象添加独有数据自动触发(对象实例化方法)
    2.__str__
    	对象被执行打印操作的时候自动触发
    3.__call__
    	对象被加括号调用的时候自动触发
    4.__getattr__
    	对象查找无法使用到的名字自动触发
    5.__getattribute__
    	对象查找名字就会自动触发
    6.__setattr__
    	对象执行固定语法 对象.名字 = 值
    7.__enter__
    	对象被执行with语法开始自动触发 并且返回的数据会给到as后面的变量名
    8.__exit__
    	对象被执行with语法结束后自动触发
    9.__del__
    	对象被执行删除(主动 被动)操作的时候自动触发
    
  • 魔法方法笔试题

    1.__enter__与__exit__
    2.__getattr__与__setattr__
    
  • 元类的简介

    1.type方法查看数据类型
    2.type方法查看对象所属的类
    3.type其实也是一个类
    4.type是一个元类>>>:产生类的类
    
  • 创建类的两种方式

    1.关键字class
    2.type元类
    	type(类名,父类,名称空间)
    
  • 元类定制类的产生行为

类中的 __init__ 是用来实例化对象的
元类中的 __init__ 就是用来实例化类的

class MyMetaClass(type):
	def __init__(self,what,bases=None,dict={})
		super().__init__(what,bases,dict)  # 继承与调用父类里面的__init__方法,所以这个地方传的是实参,所以别传成what,bases=None,dict={}这样,这样就把要生成的类里面的父类与名称空间写死了。
class C1(metaclass=MyMetaClass):
	pass

.

  • 元类定制对象的产生行为
对象加括号执行产生该对象类中的 __call__
类加括号也应该执行产生该类的元类中的 __call__

class MyMetaClass(type):
	def __call__():
		1.产生空对象
		2.调用类中的__init__实例化对象
		3.返回该对象
class C1(metaclass=MyMetaClass):
	pass
obj = C1()

.
.

  • 双下new方法

    产生空对象
    
  • 设计模式及单例模式

    1.设计模式简介
    	固定问题的固定模板
    2.设计模式分类
    	23种 三大类(创建型、结构型、行为型)
    3.单例模式
    	让类只能产生一个对象
    

今日内容概要

  • 单例模式实现的多种方式

  • pickle序列化模块

  • 选课系统需求分析

  • 选课系统架构设计

  • 选课系统目录搭建

  • 选课系统功能搭建

  • 选课系统管理员功能

今日内容详细

为什么要用单例?什么时候要用单例?

当类里面有很多功能,我们想用类里面的功能,但是类里面的功能都是绑定给对象的,所以得用类产生一个对象,再用对象才能调到类里面得很多方法,假设类里面写了很多牛逼得方法,都需要类的对象才能调,很多py文件都想用类里面的方法,这个时候用类的对象去调类里面的方法比较好,如果直接用类名去调的话,可能会出问题,因为功能里面可能有对象名点东西的句式,这个时候,用类名去调,传参就不好传了。都用对象去调就会产生很多个对象,就会比较占内存!!

单例模式实现的多种方式

第一种---------单例模式实现的方式:定义一个类方法,实现单例模式

class C1:
    __instance = None

    def __init__(self, name1, age1):
        self.name = name1
        self.age = age1

    @classmethod    # 绑定给类的方法语法糖
    def singleton(cls):   # singleton单例的意思
        if not cls.__instance:   # 判断上面的instance有没有值
            cls.__instance = cls('jason', 18)  # 如果没有值,改instance对应的值为一个实例化的对象
        return cls.__instance  # 如果__instance有值,直接返回对应的实例化对象

obj1 = C1.singleton()
obj2 = C1.singleton()
obj3 = C1.singleton()
print(id(obj1), id(obj2), id(obj3))     # 都是一个对象,详解见下图
这种有一个好处就是不是写死的,将来想产生一个全新的对象,就不掉绑定给对象的方法,就直接用类名加括号,触发类里面的__init__方法,这样就又可以产生新的对象了,这种方式比较灵活。
--------------------------------
obj4 = C1('kevin', 28)     # 生成了一个新的对象
obj5 = C1('tony', 38)      # 生成了一个新的对象
print(id(obj4), id(obj5))

image
.
image
.
object其实是一个类的实例,而这个类的名字是Object(默认类的命名首字母大写),它是所有类的父类,换句话说,python是默认所有的类都继承自Object类。而如abc(抽象基类)等其他的内置的类都是基于Object类的一些功能实现的。可以说,Object类规定了类的结构,加载方式,常用函数等。
.

第二种---------单例模式实现的方式:定制元类实现单例模式

class Mymeta(type):    # 定义了一个元类,并继承了type元类
    def __init__(cls, name, bases, dic):  # 定义类Mysql时就触发
# 给类名称空间添加一个新的名字,并让它的值为一个空对象!!!object.__new__()产生空对象
        cls.__instance = object.__new__(cls)
        cls.__init__(cls.__instance, 'jason', 18)  # 给空对象实例化
# 上面两边操作相当于在下面Mysql的类创建之后,立刻往类名称空间里面塞了一个__instance隐藏的名字,相当于就是对象名,就是我们以前用__init__方法括号里面的self,然后该名字对应的是一个对象!!!!!!
        # 上述两步可以合成下面一步
        # self.__instance=super().__call__(*args,**kwargs)
        super().__init__(name, bases, dic)

    def __call__(cls, *args, **kwargs):  # Mysql()时触发
        if args or kwargs:  # args或kwargs内有值
            obj = object.__new__(cls)  # 还是先产生一个空对象
            cls.__init__(obj, *args, **kwargs)  # 给空对象实例化
            return obj  # 然后把对象名返出去,这样就实现了只要你往类名的括号里面传值,就会产生新对象
        return cls.__instance  # 这样就实现了,不忘类名的括号里面传值,一直返回的都是第一次走__init__产生的那个对象

class Mysql(metaclass=Mymeta):  # 指定产生该类的元类为Mymeta!!!
    def __init__(self, name, age):
        self.name = name
        self.age = age

print(Mysql.__dict__)    # 见下图
----------------------
obj1 = Mysql()
obj2 = Mysql()
print(id(obj1), id(obj2))   # 两个对象是一样的
-------------------------
obj3 = Mysql('tony', 321)    # 如果需要产生一个新的对象,往里面传值,就走if下面的代码了
obj4 = Mysql('kevin', 222)
print(id(obj3), id(obj4))
这样后面类名加括号的时候,不往括号里面传参数,__call__函数体代码里面args和kwargs就都为None,就会不走if里面的代码了,直接返回__instance
-----------------------------------

image
.
image
.
image
.

第三种---------单例模式实现的方式:基于模块的单例模式

提前产生一个对象 之后导模块使用

class C1:
    def __init__(self, name):
        self.name = name

obj = C1('jason')    # 产生一个对象
把上面这几行代码,当成一个模块文件,放到单独的文件里面去
以后其他py文件要用的时候,先导该模块文件,然后通过模块名点obj,就永远得到的是一个对象了。为什么?
因为在一个py文件里面导导上面的模块,导了一次以后,就不会再导了,也就是上面的模块里面的代码执行完一次以后就不会再执行了,所以对于一个py文件而言,导入过一次上面的模块后,代码执行完一次以后就不会再执行了,所以在一个py文件里面,通过模块名点obj,拿到的都是一个对象了
----------------------------------------

第四种---------单例模式实现的方式:基于装饰器的单例模式

不看了,有空再看!!!

def outer(cls):
    _instance = cls('jason', 18)
    def inner(*args, **kwargs):
        if args or kwargs:
            obj = cls(*args, **kwargs)
            return obj
        return _instance
    return inner

@outer  # Mysql=outer(Mysql)
class Mysql:
    def __init__(self, host, port):
        self.host = host
        self.port = port

obj1 = Mysql()
obj2 = Mysql()
obj3 = Mysql()
print(obj1 is obj2 is obj3)  # True

obj4 = Mysql('1.1.1.3', 3307)
obj5 = Mysql('1.1.1.4', 3308)
print(obj3 is obj4)  # False
-----------------------------------



pickle序列化模块

优势:能够序列化python中所有的类型,就是什么都能序列化!!!主要用来不能被json序列化的数据。
缺陷: 序列化的结果,只能用python里面的pickle去识别,不能够兼容不同的语言,只能够在python中使用 无法跨语言传输。缺点很致命,注定了应用范围很小。
-------------------------------
需求:产生一个对象并保存到文件中,取出来还是一个对象
-------------------------------
class C1:
    def __init__(self, name1, age1):
        self.name = name1
        self.age = age1

    def func1(self):
        print('from func1')

    def func2(self):
        print('from func2')

obj = C1('jason', 18)

实现步骤:

先试试直接写
class C1:
    def __init__(self, name1, age1):
        self.name = name1
        self.age = age1

    def func1(self):
        print('from func1')

    def func2(self):
        print('from func2')

obj = C1('jason', 18)
---------------------------
with open(r'a.txt','w', encoding='utf8') as f:
    f.write(obj)
# 写不进去,详见下图,write方法要求写入文件的必须是字符串,所以写不了。
---------------------------
利用json模块写
import json
with open(r'a.txt', 'w', encoding='utf8') as f:
    json.dump(obj, f)
还是不行,对象不能被json序列化
---------------------------
利用pickle模块写
import pickle
with open(r'a.txt', 'wb') as f:  # 注意在打开的时候是以二进制的模式打开的!!!
    pickle.dump(obj, f)          # 存,和json的使用方法一致,就模块名不一样
with open(r'a.txt', 'rb') as f:
    data = pickle.load(f)        # 取,和json的使用方法一致,就模块名不一样
print(data)
data.func1()
data.func2()
print(data.name)
这样就实现了,将对象写入文件,并从文件里面取出的操作了!!!
注意:pickle模块在使用的时候,当读出这个对象后,一定要确保当成产生对象的类体代码,和pickle读的代码在一个文件里面!!!因为如果不在一个文件,当对象读出来后,根本不知道对象是由哪个类产生的!!!就没办法真正还原成一个对象!!!
---------------------------

image
.
image
.
image



选课系统需求分析

选课系统
	 角色:学校、学员、课程、讲师
	 要求:
    1. 创建北京、上海 2 所学校
    2. 创建linux , python , go 3个课程 , linux\py 在北京开, go 在上海开
    3. 课程包含,周期,价格,通过学校创建课程
    4. 通过学校创建班级, 班级关联课程、讲师5. 创建学员时,选择学校,关联班级
    5. 创建讲师角色时要关联学校
    6. 提供三个角色接口
    	6.1 学员视图, 可以注册, 交学费, 选择班级
    	6.2 讲师视图, 讲师可管理自己的班级, 上课时选择班级, 查看班级学员列表 , 修改所管理的学员的成绩
    	6.3 管理视图,创建讲师, 创建班级,创建课程
    7. 上面的操作产生的数据都通过pickle序列化保存到文件里

功能提炼

1.管理员功能
	注册功能
 	登录功能
 	创建学校
 	创建课程
 	创建老师
2.讲师功能
	登录功能
 	选择课程
 	查看课程
 	查看学生分数
	修改学生分数
3.学生功能
	注册功能
 	登录功能
 	选择学校
 	选择课程
 	查看课程分数

选课系统架构设计

三层架构
与ATM架构设计的差异:
1.第一层做分层展示
2.第三层创建models.py存储所有的类 只有该py文件内的代码有资格调用db_handler

必须要通过模型层先拿到一个具体的对象,由对象去调db_handler里面封装的方法!!!
image

选课系统目录搭建

基于软件开发目录规范即可

选课系统功能搭建

先把整体的架子搭起来。
空函数,循环,功能字典
-------------------------------
# 先写start
import os
import sys
from core import src

base_dir = os.path.dirname(__file__)
sys.path.append(base_dir)

if __name__ == '__main__':
    src.run()
-------------------------------
# 再写src
from core import admin_view, student_view, teacher_view

# 模块字典
module_dict = {
    '1': admin_view,
    '2': teacher_view,
    '3': student_view
}

def run():
    while True:
        print("""
        ==============选课系统总视图==============
            1.管理员视图
            2.讲师视图
            3.学生视图
        ========================================
        """)
        choice_num = input('请选择视图编号>>>:').strip()
        if choice_num in module_dict:
            module_dict.get(choice_num).run()
        else:
            print('暂无该视图编号')
--------------------------------------------
# 再写admin_view视图代码, teacher_view视图,student_view视图代码也同理操作一下
def register():
    print('管理员注册功能')

def login():
    print('管理员登录功能')

def create_school():
    print('管理员创建学校功能')

def create_course():
    print('管理员创建课程功能')

def create_teacher():
    print('管理员创建老师功能')

func_dict = {
    '1': register,
    '2': login,
    '3': create_school,
    '4': create_course,
    '5': create_teacher
}

def run():
    while True:
        print("""
         ++++++++++++++++管理员视图++++++++++++++++++
        1.管理员注册功能
        2.管理员登录功能
        3.管理员创建学校
        4.管理员创建课程
        5.管理员创建讲师
        +++++++++++++++++++++++++++++++++++++++++++
        """)
        choice_num = input('请选择管理员视图下的功能编号>>>:').strip()
        if choice_num == 'q':
            return
        if choice_num in func_dict:
            func_dict.get(choice_num)()
        else:
            print('暂无该管理员功能编号')
-----------------------------------
# 再开始写管理员里面分视图的里面各个功能的具体代码,先写注册功能
def register():
    username = input('请输入您的用户名>>:').strip()  # 1.获取管理员相关数据
    password = input('请输入您的密码>>>:').strip()
    ag_password = input('请确认您的密码>>>:').strip()
    # 2.先判断两次密码是否一致
    if not password == ag_password:
        print('两次密码不一致')
    else:
        # 3.调用管理员注册接口
        flag, msg = admin_interface.register_interface(username, password)
        print(msg)
--------------------------------------
# 再开始写admin_interface里面的register_interface函数
def register_interface(username, password):
    # 判断管理员用户名是否已存在
    # 密码加密
    # 创建管理员对象并保存
-----------------------------------
# 再开始写
# 创建一个管理员的类,专门将来帮我们产生管理员对象,这个类里面也记录了作为管理员可以拥有的所有的功能!!!
# 只要涉及到数据的增删改查的操作,都必须再db_handler里面进行操作,其他地方只能通过调db_handler里面的函数来操作!!
from db import db_handler

class Admin:
    def __init__(self, name1, pwd1):    # 类产生的对象需要有哪些独特的数据,在__init__里面要先写好!!!
        self.name = name1
        self.pwd = pwd1
        self.save_obj()  # 优化:当给对象添加独有数据后,自动调保存功能,这样就不需要在外面用对象点的方式来运行save_obj方法了!!!

    def save_obj(self):  # 先封装一个保存对象的方法!!!
        db_handler.save(self) # 调db_handler里面专门用来保存数据的方法!!这个地方self是个对象

    @classmethod  # 因为下面的方法是给登录接口层里,通过类名点用的方式调用的,所以下面的方法绑定为类的方法
    def select_obj(cls, username):   # 类里面的查询用户名是否在db目录下对应的分目录下是否已存在功能函数!!
        res = db_handler.select(cls, username)  # 再调db_handler里面的查询方法,这个地方传cls类名的作用是:为了后续的路径拼接!!!
        return res
------------------------------------
# 再开始到db_handler里面写db_handler.save()方法,这个方法以后既要保存学生对象,也要保存老师对象,还要保存管理员对象!!!
------
# 考虑到save()方法的对象要往db的目录下存,但是这个系统里面既有管理员,又有学生,又有老师,如果生成的对象都存放在db目录下就很乱,所以在db目录下再开管理员,学生和老师的目录,这样以后产生的对象,都放到对于的目录文件下,就比较好了!!!通过代码去创建这3个文件
------
需求:models文件里面有几个类,将来db里面就要有几个对应的目录
from conf import settings
import os
import pickle

def save(user_obj):        # 对象保存功能!!!
    # 因为保存的对象可能是学生,老师或者管理员,保存的文件都放在db目录下就很乱,最好就是models里面有几个类,
    # 对应db目录下就有几个类名对应的文件夹专门用来存放类产生的对象!!!!!!怎么样通过代码实现动态创建了?
    # 1.先考虑拼接管理员目录路径 db/admin
    # 注意类名admin不是字符串形式的,但是我们拼接路径的时候需要的是字符串形式的admin
    # 怎么拿到某个对象所属的类名(字符串形式的)???
    # 当有类名的时候,通过类名.__name__ 就能够获取到类名对应的字符串名!!!
    # 当我手上是某个类产生的对象,我们也想拿到对象所属的类的字符串名怎么办???
    # obj.__class__.__name__
    # 先通过obj.__class__拿对象所属的类的类名,再通过.__name__拿类名对应的字符串名!!!
    # 这样终于可以根据传进来的对象,拿到所属的类的字符串名了!!!操!!!拼接出db目录下类对应的目录路径
    admin_dir_name = os.path.join(settings.DB_DIR, user_obj.__class__.__name__)
    # 2.判断目录是否存在,并动态创建!!!
    if not os.path.exists(admin_dir_name):
        os.mkdir(admin_dir_name)
    # 3.拼接存储管理员对象数据的文件路径,user_obj.name对象点的方式拿name对应的值
    admin_file_path = os.path.join(admin_dir_name, user_obj.name)
    # 直接拿对象的用户名作为保存文件的文件名不需要什么后缀了!!!
    with open(admin_file_path, 'wb')as f:
        pickle.dump(user_obj, f)
    # 暂时大功告成了!!!这样就可以根据传进来的对象,拿到产生对象的类名(字符串形式),作为创建db里面分目录的目录名,
    # 这样就完成了对象的保存功能了
----------------------------------
def select(class_name, username):
    admin_dir_name = os.path.join(settings.DB_DIR, class_name.__name__)   # 1. 通过类名拿类名的字符串形式,并拼出分目录路径!!
    # 2.判断目录是否存在,并动态创建!!!
    if not os.path.exists(admin_dir_name):
        os.mkdir(admin_dir_name)
    # 3.拼接存储管理员对象数据的文件路径
    admin_file_path = os.path.join(admin_dir_name,username)   # 这个地方username已经是字符串形式的了
    if os.path.exists(admin_file_path):  # 和购物车套路一样,路径存在,直接将对应的文件返回出去!!!
        with open(admin_file_path, 'rb')as f:
            return pickle.load(f)

这样db_handler里面差不多就写好了
---------------------------
# admin_view里面代码
from interface import admin_interface

def register():
    username = input('请输入您的用户名>>:').strip()  # 1.获取管理员相关数据
    password = input('请输入您的密码>>>:').strip()
    ag_password = input('请确认您的密码>>>:').strip()
    # 2.先判断两次密码是否一致
    if not password == ag_password:
        print('两次密码不一致')
    else:
        # 3.调用管理员注册接口
        flag, msg = admin_interface.register_interface(username, password)   # 管理员简易版本的注册功能写好了
        print(msg)   # 这样注册功能就差不多写好了
--------------
def login():
    # 1.获取管理员用户名与密码
    username = input('请输入您的用户名>>:').strip()
    password = input('请输入您的密码>>>:').strip()
    # 2.调用第二次登录接口
    flag, msg = admin_interface.login_interface(username, password)
    print(msg)  # 这样登录功能就差不多写好了
-------------------------------------------
# admin_interface 里面代码
# 注意第二层只能和models里面的类打交道,不能和db_handler打交道!!!

from db import models
from lib import common

def register_interface(username, password):
    # 1.判断管理员用户名是否已存在(判断在db目录里面的管理员目录里面用户名是否已存在!!!)
    # 这个地方只能调models里面的admin类,所以在管理员的类里面应该有一个方法,用来帮我们查当前用户名在管理员目录下是否已存在!!!
    user_obj = models.Admin.select_obj(username)  # 调用管理员类里面的查询功能!!!
    if user_obj:
        return False, f'当前管理员用户名{username}已存在,请重新注册'

    # 2.密码加密
    hash_pwd = common.get_hash(password)

    # 3.创建管理员对象并保存
    admin_obj = models.Admin(username, hash_pwd)   # 类名加括号生成对象!!
    # admin_obj.save_obj() # 对象通过点调用类里面的方法并运行,保存对象!!,类里面__init__里面有了调用save_obj()方法了,所以该代码不用写了
    return True, f'管理员{username}注册成功!!!'

-----------------------------
def login_interface(username, password):
    # 1.校验当前用户名是否存在!!!
    user_obj = models.Admin.select_obj(username)  # 调用管理员类里面的查询功能!!!
    if not user_obj:
        return False, f'当前管理员用户名{username}不存在,请重新登录!!'
    # 2.判断密码是否一致!!
    if not user_obj.pwd == common.get_hash(password):
        return False, '密码错误'
    return True, f'管理员{username}登录成功!!'
----------------------------------------
# common里面的代码
import hashlib

def get_hash(msg):
    md5 = hashlib.md5()
    md5.update(msg.encode('utf8'))
    return md5.hexdigest()

这样差不多管理员的注册登录功能就写好了!!!!!!
.
.
.
.
.
.
.
.
重来一下

settings 代码:

import os

BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DB_DIR = os.path.join(BASE_DIR, 'db')
if not os.path.exists(DB_DIR):
    os.mkdir(DB_DIR)
-----------------------------------------
admin_view  里面代码


from interface import admin_interface
from lib import common


is_login = {'username': ''}


def register():
    username = input('请输入用户名>>>>:').strip()
    password = input('请输入密码>>>>:').strip()
    ag_pwd = input('请再次输入密码>>>>:').strip()
    if not password == ag_pwd:
        print('两次密码不一致')
        return
    flag, msg = admin_interface.register_interface(username, password)
    print(msg)


def login():
    username = input('请输入用户名>>>>:').strip()
    password = input('请输入密码>>>>:').strip()
    flag, msg = admin_interface.login_interface(username, password)
    if flag:
        is_login['username'] = username
    print(msg)


@common.login_auth('admin')
def creat_school():
    admin_name = is_login['username']
    school_name = input('请输入要创建的学校名称>>>:').strip()
    school_address = input('请输入该学校的地址>>>:').strip()
    flag, msg = admin_interface.creat_school_interface(school_name, school_address, admin_name)
    print(msg)


@common.login_auth('admin')
def creat_course():
    admin_name = is_login['username']
    # 获取学校列表,选择学校后,再创建课程
    flag, school_list1 = admin_interface.get_school_list_interface()
    if not flag:
        print(school_list1)  # '暂无学校,请先创建学校!!!'
        return
    while True:
        for num, school_name in enumerate(school_list1, start=1):
            print(f"学校编号:{num}        |       学校名称:{school_name}")
        school_num = input('请输入要选择的学校编号(q)>>>:').strip()
        if school_num == 'q':
            return
        if not school_num.isdigit():
            print('请输入纯数字!!')
            continue
        school_num = int(school_num)
        if school_num not in range(1, len(school_list1) + 1):
            print('该学校编号不在对应的范围内')
            continue
        target_school_name = school_list1[school_num - 1]  # 拿到选择的学校名称
        course_name = input('请输入要创建的课程名称>>>:').strip()  # 名称_期数_学校   好区分!!
        course_price = input('请输入该课程的价格>>>:').strip()
        course_period = input('请输入该课程的周期>>>:').strip()
        # 调接口
        flag, msg = admin_interface.creat_course_interface(target_school_name,
                                                           course_name,
                                                           course_price,
                                                           course_period,
                                                           admin_name
                                                           )
        print(msg)


@common.login_auth('admin')
def creat_teacher():
    admin_name = is_login['username']
    teacher_name = input('请输入要创建的老师名称>>>:').strip()
    flag, msg = admin_interface.creat_teacher_interface(teacher_name, admin_name)
    print(msg)


func_dict = {
    '1': register,
    '2': login,
    '3': creat_school,
    '4': creat_course,
    '5': creat_teacher
}


def run():
    while True:
        print("""
        -------------管理员视图----------------
                1.注册功能
                2.登录功能
                3.创建学校功能
                4.创建课程功能
                5.创建老师功能
        ----------------end------------------
                """)
        choice_func_num = input('请输入要执行的功能编号(q)>>>:').strip()
        if choice_func_num == 'q':
            return
        if choice_func_num in func_dict:
            func_dict.get(choice_func_num)()
        else:
            print('暂无您要执行的编号>>>:')

----------------------------

作业

1.整理今日内容及博客
2.尝试编写校验用户是否登录装饰器并思考如何区分用户角色 不做出误判
3.尝试编写管理员其他功能
posted @ 2022-11-09 17:07  tengyifan  阅读(205)  评论(0)    收藏  举报