1. 程式人生 > >第31天三層架構,異常

第31天三層架構,異常

目錄:

  1. 三層架構

  2. 異常

三層架構

什麼是三層架構
  使用者檢視層:
    主要是用來和使用者進行互動的,對於使用者的資料進行一些簡單的邏輯判斷之後交給業務邏輯層進行處理。
  業務邏輯層:
    通過對資料的訪問來對檢視層傳遞過來的資料進行判斷。
  資料訪問層:
    專門用來對資料進行儲存和讀取。
為什麼要用三層架構
  一個專案的本質其實就是將使用者互動產生的資料儲存到硬碟或者讀取硬碟的資料的一個過程,但是如果我們把使用者互動的過程和資料
讀取的過程都寫到一塊的話,程式碼的可讀性非常差,而且也不容易進行管理,程式之間的耦合性較高,為了解決這樣的問題,我們牽引出
了業務邏輯層作為使用者互動的資料存取的中間層,也就是介面層,這樣降低了程式之間的程式碼耦合,而且提高了程式的可讀性。
怎麼使用三層架構

 

案例分享: 學生管理系統中以管理員登入和註冊為例說明三層架構究竟是怎麼一步一步往下面走的

start.py程式碼:

import os
import sys
from core import main

BASE_DIR = os.path.normpath(os.path.join(
    __file__,
    os.pardir,
    os.pardir
))

sys.path.append(BASE_DIR)

if __name__ == '__main__':
    main.run()
start.py

去呼叫core下面main的run函式

from interface import common_interface
from core import admin, student, teacher


def init():
    """初始化函式"""
    pass


def run():
    """系統入口函式"""
    init()
    sys_welcome_info = 'COURSE SELECTION SYSTEM'
    sys_func = """1.管理員操作介面\n2.學員操作介面\n3.講師操作介面"""
    sys_func_dict 
= { '1': admin.admin_view, '2': student.student_view, '3': teacher.teacher_view } common_interface.sys_func_inter(sys_welcome_info, sys_func, sys_func_dict)
main.py

以管理員的註冊和登入,在admin.py中只是對使用者輸入的資料進行了一些簡單的處理之後就交給了interface介面:

from functools import wraps
import re
from interface import common_interface, admin_interface


def login_auth(func):
    @wraps(func)
    def inner(*args, **kwargs):
        if not common_interface.admin_status:
            login()
            if common_interface.admin_status:
                return func(*args, **kwargs)
            else:
                print('管理員未登入成功')
        else:
            return func(*args, **kwargs)

    return inner


def register():
    """註冊"""
    temp = ''
    while True:
        name = input(temp + '輸入管理員的名字(Q退出)>>').strip()
        if name.upper() == 'Q':
            return
        pwd1 = input('請輸入管理員的密碼>>').strip()
        pwd2 = input('請確認您的密碼>>').strip()
        if name and pwd1 and pwd2:
            if pwd1 == pwd2:
                msg, state = admin_interface.register(name, pwd1)
                if state:
                    return common_interface.logger('admin', msg)
                else:
                    print(msg)
            else:
                print('兩次密碼不一致')
        else:
            print('輸入不能為空')
        temp = '請重新'


def login():
    """登入"""
    temp = ''
    while True:
        name = input(temp + '輸入使用者名稱(Q退出)>>').strip()
        if name.upper() == 'Q':
            return
        pwd = input('請輸入密碼>>').strip()
        if name and pwd:
            msg, state = admin_interface.login(name, pwd)
            if state:
                common_interface.admin_status = state
                return common_interface.logger('admin', msg)
            else:
                print(msg)
        else:
            print('使用者名稱和密碼不能為空')
        temp = '請重新'
def admin_view():
    """管理員系統介面"""
    sys_welcome_info = 'ADMIN OPERATE SYSTEM'
    sys_func = """1.註冊\n2.登入\n3.建立校區\n4.建立課程\n5.建立班級\n6.建立講師"""
    sys_func_dict = {
        '1': register,
        '2': login,
        '3': create_campus,
        '4': create_course,
        '5': create_grade,
        '6': create_teacher
    }
    common_interface.sys_func_inter(sys_welcome_info, sys_func, sys_func_dict)
admin.py

interface介面下的admin_interface.py對註冊和登入進行了一系列的處理之後去呼叫db去真正的處理資料

from db import models


def register(name, pwd):
    """註冊

    name: 管理員名字 pwd:管理員密碼,如果註冊成功返回(msg,True),如果失敗(msg, False)
    """
    admin_obj = models.Admin.get_obj_from_name(name)
    if admin_obj:
        return 'admin is exists', False
    else:
        models.Admin(name, pwd)
        return 'create %s successful' % name, True


def login(name, pwd):
    """登入

    name: 管理員名字 pwd:管理員密碼,如果註冊成功返回(msg,物件),如果失敗(msg, False)
    """
    admin_obj = models.Admin.get_obj_from_name(name)
    if admin_obj:
        if admin_obj.pwd == pwd:
            return 'admin %s login successfully' % name, admin_obj
        else:
            return 'password is wrong !', False
    else:
        return 'admin is not exists', False
admin_interface.py

去呼叫db下的models,其實就是呼叫了類,但是類中的方法都是通過DBhandlers去實現的,因此有呼叫了DBhandlers下面的方法去實現

from db import DBhandler


class BaseClass:
    """這是一個基類,用來存放一些公共的方法和屬性"""

    @classmethod
    def tell_info(cls):
        """檢視當前類中的所有物件,

        將結果封裝到一個列表中返回回去,如果沒有物件,則返回空列表
        """
        return DBhandler.tell_info(cls)

    @classmethod
    def get_obj_from_name(cls, name):
        """根據名字檢視是否有這個物件,如果存在則返回物件本身,不存在則返回None"""
        return DBhandler.get_obj_from_name(cls, name)

    @classmethod
    def save(cls, obj):
        """將傳入的物件儲存到檔案中"""
        DBhandler.save(cls, obj)

class Admin(BaseClass):
    """管理員類

    會建立一個不同的檔案分別來儲存我們的管理員資訊
    """

    def __init__(self, name, pwd):
        self.name = name
        self.pwd = pwd
        self.save(self)
models.py
import os
import pickle
from conf import settings


def save(cls, obj):
    """將物件儲存到檔案中

    cls:類物件,為了拼接路徑用的,obj: 實際要儲存的物件
    """
    obj_dir = os.path.join(settings.DB_DIR, cls.__name__)
    if not os.path.exists(obj_dir):
        os.mkdir(obj_dir)
    file_path = os.path.join(obj_dir, obj.name)
    with open(file_path, 'wb') as f:
        pickle.dump(obj, f)


def get_obj_from_name(cls, name):
    """根據名字獲得物件

    cls:類本身,name: 傳入的名稱,此方法依然不適用於課程或者班級
    """
    file_path = os.path.join(settings.DB_DIR, cls.__name__, name)
    if os.path.exists(file_path):
        with open(file_path, 'rb') as f:
            return pickle.load(f)


def tell_info(cls):
    """檢視當前類建立的所有物件

    cls: 類物件,此方法是根據在db目錄下是否存在相應的檔案來檢視是否有物件的,因此對於課程或者班級
    這種並沒有儲存到檔案中的類來講是檢視不到的,如果檢視不到的會返回一個空的列表,所以不要用用課程或
    班級類來呼叫此方法
    """
    temp = []
    obj_dir = os.path.join(settings.DB_DIR, cls.__name__)
    if os.path.exists(obj_dir):
        for file_name in os.listdir(obj_dir):
            file_path = os.path.join(obj_dir, file_name)
            with open(file_path, 'rb') as f:
                temp.append(pickle.load(f))
    return temp
DBHandler.py

公共的程式碼庫有:common_interface.py

import logging.config
from conf import log_config

admin_status = None
student_status = None
teacher_status = None


def sys_func_inter(sys_welcome_info, sys_func, sys_func_dict):
    """系統的功能介面函式

    sys_welcome_info: 系統功能分割資訊
    sys_func: 系統的功能資訊
    sys_func_dict: 系統的功能字典
    """
    temp = ''
    while True:
        print(sys_welcome_info.center(50, '*'))
        print(sys_func)
        print(sys_welcome_info.center(50, '*'))
        choice = input(temp + '輸入你要選擇的功能(Q退出)>>').strip()
        if choice.upper() == 'Q':
            return
        if choice not in sys_func_dict:
            print("輸入有誤")
            temp = '請重新'
            continue
        sys_func_dict[choice]()


def my_logger(name='admin'):
    """通過讀取配置檔案獲得一個自己的日誌生成器

    name:可以是三個值,student代表的是學生物件的日誌生成器,teacher代表的是講師物件的日誌生成器,admin代表的是管理員的日誌生成器
    預設的是管理員的生成器
    """
    logging.config.dictConfig(config=log_config.LOGGING_DIC)
    return logging.getLogger(name)


def logger(name, msg):
    """更高層次的一層封裝"""
    log = my_logger(name)
    log.debug(msg)
common_interface.py

結論:

三層架構就像是套娃一樣,一層一層的往下面走,每一層都是一個專門用來負責某一項事物的,主要解決了程式之間耦合性的問題。

異常

什麼是異常
  異常就是程式發生錯誤時的一種訊號,如果不進行處理,程式就會終止。最直觀的顯示就是程式執行的時候報錯了。
異常分類
  1. 語法異常
    是最基本的異常,也是作為程式設計師最不應該出現的異常,這種異常一般在程式執行執行之前就會丟擲,近而解決就是了。
  2. 邏輯上的異常
    邏輯上的異常是在程式執行的時候出現的異常,我們可以通過關鍵字try進行捕獲。
異常組成 異常型別 異常捕獲
  try:
    程式碼塊
  except:
    程式碼塊
主動丟擲異常: raise
  raise 異常型別("異常資訊")
斷言: assert
  assert 條件表示式返回一個布林值 自定義異常

 異常的組成

異常的型別

常見的異常:
異常型別一: SyntaxError: 語法錯誤 異常型別二: NameError: 名字沒有定義 x IndexError: 索引超出範圍 l
= [] l[3] KeyError: key不存在 d = {'a': 1} d['b'] AttributeError: 物件沒有這個屬性 l = [] l.sljg TypeError: 型別錯誤,一般是型別不是可迭代的 for i in 123: ValueError: 傳入一個呼叫者不期望的值,即使值的型別是正確的 int('sjeg') ZeroDivisionError: 除數不能是0 KeyboardInterrupt: Ctrl+C被按下(win平臺可以在終端中試一下) IndentationError: 語法錯誤(的子類) ;程式碼沒有正確對齊 FileNotFoundError: 檔名字找不到 f = open('這是不存在的檔名', 'rt', encoding='utf-8') ModuleNotFoundError: 模組找不到 import 不存在的模組
ArithmeticError
AssertionError
AttributeError
BaseException
BufferError
BytesWarning
DeprecationWarning
EnvironmentError
EOFError
Exception
FloatingPointError
FutureWarning
GeneratorExit
ImportError
ImportWarning
IndentationError
IndexError
IOError
KeyboardInterrupt
KeyError
LookupError
MemoryError
NameError
NotImplementedError
OSError
OverflowError
PendingDeprecationWarning
ReferenceError
RuntimeError
RuntimeWarning
StandardError
StopIteration
SyntaxError
SyntaxWarning
SystemError
SystemExit
TabError
TypeError
UnboundLocalError
UnicodeDecodeError
UnicodeEncodeError
UnicodeError
UnicodeTranslateError
UnicodeWarning
UserWarning
ValueError
Warning
ZeroDivisionError
更多異常

捕獲異常的六種不同的格式

格式一:

try:
    # try後面縮排的程式碼塊是我們正常的程式碼(也就是可能出現錯誤的程式碼)
    l = [1, 2, 3]
    print(l[4])
# except 代表的意思是如果try中的程式碼塊中出現了IndexError型別的異常,就會執行except下的程式碼塊
except IndexError:
    print('索引錯誤了')

# 執行結果:
# 索引錯誤了

格式二:

try:
    a   # 當執行到這一行的時候會發生異常然後被捕捉執行NameError的程式碼
    l = [1, 2, 3]
    print(l[4])
except IndexError:
    print('索引錯誤了')
except NameError:
    print('變數沒有定義')

# 執行結果:
# 變數沒有定義
try:
    a   # 當執行到這一行的時候會發生異常然後被捕捉然後執行except語句
    l = [1, 2, 3]
    print(l[4])
except (IndexError, NameError):
    print('程式發生了未知錯誤')

# 執行結果:
# 程式發生了未知錯誤
方式二的變形

格式三: 萬能捕捉語句

try:
    a   # 當執行到這一行的時候會發生異常然後被捕捉然後執行except語句
    l = [1, 2, 3]
    print(l[4])
except Exception:
    print('上面不管發生什麼異常,我都能夠捕捉(語法錯誤除外)')

# 執行結果:
# 上面不管發生什麼異常,我都能夠捕捉(語法錯誤除外)

格式四: except  NameError  as  n  

try:
    a   # 當執行到這一行的時候會發生異常然後被捕捉然後執行except語句
    l = [1, 2, 3]
    print(l[4])
# as後面的e就是上面產生異常型別的一個物件
# 異常型別其實通過__str__將輸出的結果進行了格式化
# 因此當我們列印e的時候會顯示異常的資訊
except Exception as e:
    print(e)

# 執行結果:
# name 'a' is not defined

格式五: try........else....... 必須和except結合使用

try:
    a
    l = [1, 2, 3]
    print(l[4])
except Exception as e:
    print(e)
else:
    print('當try裡面的程式碼塊沒有異常,也就是正常執行完畢之後才會執行我這裡的程式碼'
          '如果有異常,還是執行except下面的程式碼')
# 執行結果:
# name 'a' is not defined

格式六: try ...... finally .......可以不except結合使用,一般用來回收系統資源

try:
    a
    l = [1, 2, 3]
    print(l[4])
except IndexError as e:
    print(e)
else:
    print('當try裡面的程式碼塊沒有異常,也就是正常執行完畢之後才會執行我這裡的程式碼'
          '如果有異常,還是執行except下面的程式碼')
finally:
    print('無論上面的程式碼是怎麼執行的,都要執行我這裡的程式碼,就算是異常沒有被'
          '捕捉到也要執行我這裡的程式碼')
# 執行結果:
# Traceback (most recent call last):
# 無論上面的程式碼是怎麼執行的,都要執行我這裡的程式碼,就算是異常沒有被捕捉到也要執行我這裡的程式碼
#   File "H:/python_study/day31/測試.py", line 232, in <module>
#     a
# NameError: name 'a' is not defined
try:
    f = open('xxx', 'wt', encoding='utf-8')
    # 當此時出現異常之後,沒有被捕捉到,所以程式終止,但是當前f開啟的系統資源還沒
    # 有被回收掉,所以我們可以通過finally去回收當前系統中還沒有被回收的資源
    f.read()   
    f.close()
except NameError:
    pass
finally:
    f.close()
回收系統資源示例

主動丟擲異常

為什麼要主動丟擲異常
  當程式中有一些限制,但是使用者沒有遵守的時候,我們就可以主動的丟擲異常

主動丟擲異常的方式有兩種
  raise: 優點: 可以自定義異常的資訊 缺點:麻煩
  assert:缺點: 不能自定義異常的資訊 優點: 簡單

raise

class User:
    def __init__(self, name):
        if not isinstance(name, str):
            # 當用戶輸入的內容不是字串的時候,我就會主動的丟擲異常
            raise TypeError('使用者名稱必須是字串')
        print('輸入的使用者名稱是字串,我可以執行下面的程式碼了')
        print('part.....')

User(123)

assert 可以把上面的程式碼通過assert進行簡化

class User:
    def __init__(self, name):
        assert isinstance(name, str)  # 就是不能寫異常資訊
        print('輸入的使用者名稱是字串,我可以執行下面的程式碼了')
        print('part.....')

User(123)

 自定義異常

為什麼要自定義異常
  當系統提供的這些異常型別和你要描述的錯誤不匹配的時候,我們就需要自定義異常型別了。
class RegisterError(BaseException):
    def __init__(self, msg, name):
        self.name = name
        self.msg = msg

    def __str__(self):
        return '<%s: %s>' % (self.msg, self.name)

raise RegisterError('必須是字串', 'teacher')

結論:

當異常發生的條件能夠明確的寫出一個條件的時候,我們就應該用if判斷語句來代替異常處理。