Skip to content

13. 泛型类 [BaseEntity] [MyException]

现在,我们将定义两个类,后续将经常使用它们。

Image

13.1. 类 MyException

[MyException] (MyException.py) 提供了一个专有异常类:


# 一个从 [BaseException] 派生的专有异常类
class MyException(BaseException):
    # 构造函数
    def __init__(self: object, code: int, message: str):
        # 父类
        BaseException.__init__(self, message)
        # 错误代码
        self.code = code

    # toString
    def __str__(self):
        return f"MyException[{self.code}, {super().__str__()}]"

    # 获取器
    @property
    def code(self) -> int:
        return self.__code

    # setter
    @code.setter
    def code(self, code: int):
        # 错误代码必须为正整数
        if isinstance(code, int) and code > 0:
            self.__code = code
        else:
            # 异常
            raise BaseException(f"code erreur {code} incorrect")

注释

  • 第 2 行:类 [MyException] 继承自预定义类 [BaseException]
  • 第4行:该函数接受两个参数:
    • [code]:一个整数错误代码;
    • [message]:一条错误消息;
  • 第 6 行:将错误消息传递给父类;
  • 第14-27行:通过getter/setter操作[code]属性;
  • 第 23-24 行:验证属性 [code] 的有效性:必须为大于 0 的整数;

13.2. [BaseEntity]

[BaseEntity] 类将成为我们为封装对象信息而创建的大多数类的父类。接下来,我们将主要使用两种类型的类:

  • 仅用于将同一对象的信息封装在同一位置的类。这些类除了 getter/setter 以及一个显示函数(__str__)外,不会包含其他行为(方法)。 如果需要管理 N 个对象,这些类将被实例化 N 次。[BaseEntity] 将作为此类类的父类;
  • 其主要作用是封装方法及极少量信息的类。此类仅实例化一次(单例)。其作用是实现应用程序的算法;

[BaseEntity] 类如下:


# 导入
import json
import re

from MyException import MyException


class BaseEntity(object):
    # 从类状态中排除的属性
    excluded_keys = []

    # 类的属性
    @staticmethod
    def get_allowed_keys() -> list:
        # id:对象标识符
        return ["id"]

    # toString
    def __str__(self) -> str:
        return self.asjson()

    # 获取器
    @property
    def id(self) -> int:
        return self.__id

    #  设置器
    @id.setter
    def id(self, id):
        # id 必须是 >=0 的整数
        try:
            id = int(id)
            erreur = id < 0
        except:
            erreur = True
        # 错误?
        if erreur:
            raise MyException(1, f"L'identifiant d'une entité {self.__class__} doit être un entier >=0")
        else:
            self.__id = id

    def fromdict(self, state: dict, silent=False):
        

    def set_value(self, key: str, value, new_attributes) -> dict:
        

    def asdict(self, included_keys: list = None, excluded_keys: list = []) -> dict:
        

    def asjson(self, excluded_keys: list = []) -> str:
        

    def fromjson(self, json_state: str):
        

注释

  • [BaseEntity] 类的目的是简化对象与字典之间的转换,以及对象与 jSON 之间的转换。因此,我们提供了以下方法:
    • [asdict]:返回对象的属性字典;
    • [fromdict]:根据字典构建对象;
    • [asjson]:返回对象的 jSON 字符串,其作用与函数 [__str__] 相同;
    • [fromjson]:根据其字符串 jSON 构建对象;
  • [BaseEntity] 旨在被派生,而非直接使用;
  • 第 22-25 行:类 [BaseEntity] 仅有一个属性,即整数 [id]。该属性是对象的标识符。在实际应用中,能够区分同一类的不同实例通常非常有用。 我们将利用该属性(每个实例唯一)来实现这一点。此外,对象通常来自数据库,在数据库中它们通过主键(通常为整数)进行标识。在这种情况下,[id]将作为主键;
  • 第27-40行:属性[id]的setter方法。此处验证该值是否为大于等于0的整数。若不满足条件,则抛出类型为[MyException]的异常(第39行);
  • 第 10 行:[excluded_keys] 是类属性而非实例属性。因此应写为 [BaseEntity.excluded_keys]。 该类属性是一个列表,包含不参与对象/字典及对象/jSON转换的类属性;
  • 第 12-16 行:[get_allowed_keys] 返回该类的属性列表。在字典 -> 对象或 jSON -> 对象的转换中,仅接受该列表中存在的键。 每个继承自类 [BaseEntity] 的子类都必须重新定义该列表;

需注意的是,[BaseEntity]类的属性与函数对[BaseEntity]的派生类是可访问的。这是需要重点理解的关键点。

接下来我们将详细解析 [BaseEntity] 类的代码。该类代码较为复杂。初学者只需阅读各函数的作用说明,无需过多关注具体代码。

13.2.1. [BaseEntity.fromdict]方法

13.2.1.1. Définition

方法 [fromdict] 允许通过字典初始化 [BaseEntity] 对象或其派生对象:


def fromdict(self, state: dict, silent=False):
        # 正在更新对象
        # 授权密钥
        allowed_keys = self.__class__.get_allowed_keys()
        # 遍历状态键
        for key, value in state.items():
            # 该键是否被授权?
            if key not in allowed_keys:
                if not silent:
                    raise MyException(2, f"la clé {key} n'est pas autorisée")
            else:
                # 尝试将值赋给键
                # 允许可能的异常向上抛出
                setattr(self, key, value)
        # 返回对象
        return self

注释

  • 第 1 行:该函数接收字典 [state] 作为参数,当前对象将基于该字典进行初始化;
  • 第4行:调用调用[fromdict]函数的类中的静态函数[get_allowed_keys]。 如果处理的是从 [BaseEntity] 派生的类,且该派生类重定义了静态函数 [get_allowed_keys],则调用的是函数 [get_allowed_keys]。 每个派生类都会重定义该静态函数,并在其中声明其属性;
  • 第 6 行:遍历字典 [state] 的键和值;
  • 第 8 行:如果键 [key] 不属于该类的属性,则:
    • 忽略该键;
    • 则抛出异常(第 10 行)。 开发者通过传递正确的参数 [silent](第 1 行)来指定其需求。[silent] 的默认值会导致在尝试用对象不具备的属性初始化该对象时抛出异常;
  • 第 14 行:如果键属于对象的属性,则使用预定义函数 [setattr] 将其赋值给对象 [self]
  • 第 16 行:该函数返回已初始化的对象;

13.2.1.2. Exemples

Image

13.2.1.2.1. [Utils]

[Utils](Utils.py)如下:


class Utils:
    # 静态方法
    @staticmethod
    def is_string_ok(string: str) -> bool:
        # string 是否为字符串
        erreur = not isinstance(string, str)
        if not erreur:
            # 字符串是否为空?
            erreur = string.strip() == ''
        # 结果
        return not erreur

它在第3至11行定义了一个静态方法,如果其参数[str]是一个非空字符串,则返回布尔值true;

13.2.1.2.2. [Personne]

[Personne] (Personne.py) 继承自类 [BaseEntity]


# 导入
from BaseEntity import BaseEntity
from MyException import MyException
from Utils import Utils


# Personne 类
class Personne(BaseEntity):
    # 类状态中排除的属性
    excluded_keys = []

    # 类属性
    # id:人员标识符
    # 名字:人员的名字
    # 姓氏:个人姓氏
    # 年龄:人员的年龄
    @staticmethod
    def get_allowed_keys() -> list:
        # id:对象标识符
        return BaseEntity.get_allowed_keys() + ["nom", "prénom", "âge"]

    # 获取器
    @property
    def prénom(self) -> str:
        return self.__prénom

    @property
    def nom(self) -> str:
        return self.__nom

    @property
    def âge(self) -> int:
        return self.__âge

    # 设置器
    @prénom.setter
    def prénom(self, prénom: str):
        # 名字字段不能为空
        if Utils.is_string_ok(prénom):
            self.__prénom = prénom.strip()
        else:
            raise MyException(11, "Le prénom doit être une chaîne de caractères non vide")

    @nom.setter
    def nom(self, nom: str):
        # 名字不能为空
        if Utils.is_string_ok(nom):
            self.__nom = nom.strip()
        else:
            raise MyException(12, "Le nom doit être une chaîne de caractères non vide")

    @âge.setter
    def âge(self, âge: int):
        # 年龄必须是大于等于0的整数
        erreur = False
        if isinstance(âge, int):
            if âge >= 0:
                self.__âge = âge
            else:
                erreur = True
        else:
            erreur = True
        # 错误?
        if erreur:
            raise MyException(13, "L'âge doit être un entier >=0")
  • 第 8 行:类 [Personne] 派生自类 [BaseEntity]
  • 第 8-65 行:保留了之前已出现的 [Personne] 类的核心内容。差异如下:
    • 该类不再具有构造函数;
    • 该类使用了 [MyException] 异常,例如第 65 行;
    • 它有一个静态方法 [get_allowed_keys](第 17-20 行),用于定义其属性列表。类 [Personne] 的专属属性被添加到父类 [BaseEntity] 的属性中;
    • 它有一个静态列表 [excluded_keys],我们稍后将再次讨论;
13.2.1.2.3. [Enseignant]

[Enseignant] (Enseignant.py) 继承自类 [Personne]


# 导入
from MyException import MyException
from Personne import Personne
from Utils import Utils


# 教师类
class Enseignant(Personne):
    # 类状态中排除的属性
    excluded_keys = []

    # 类属性
    # id:人员标识符
    # 名字:个人名字
    # 姓氏:该人的姓氏
    # 年龄:该人的年龄
    # 学科:所授学科
    @staticmethod
    def get_allowed_keys() -> list:
        # ID:对象标识符
        return Personne.get_allowed_keys() + ["discipline"]

    # 属性
    @property
    def discipline(self) -> str:
        return self.__discipline

    @discipline.setter
    def discipline(self, discipline: str):
        # 学科必须为非空字符串
        if Utils.is_string_ok(discipline):
            self.__discipline = discipline
        else:
            raise MyException(21, "La discipline doit être une chaîne de caractères non vide")

    # show方法
    def show(self):
        print(f"Enseignant[{self.id}, {self.prénom}, {self.nom}, {self.âge}]")
  • 第 8 行:类 [Enseignant] 扩展(或派生)自类 [Personne]
  • 第 18-21 行:定义了该类的属性列表;
  • 第37-38行:方法[show]显示教师的身份;
13.2.1.2.4. 配置 [config]

示例脚本使用以下 [config] 配置:


def configure():
    import os

    # 配置文件所在的目录
    script_dir = os.path.dirname(os.path.abspath(__file__))

    # 要放入 syspath 的文件夹的绝对路径
    absolute_dependencies = [
        # BaseEntity 类
        f"{script_dir}/entities",
    ]

    # 更新 syspath
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # 配置
    return {}
  • 第 8-10 行:包含项目依赖项的文件夹;
  • 第 14-15 行:构建 Python 路径;
  • 第 18 行:返回一个空字典(除了 syspath 之外没有其他配置);
13.2.1.2.5. 脚本 [fromdict_01]

脚本 [fromdict_01] 如下:


# 正在配置应用程序
import config

config = config.configure()

# 系统路径已配置 - 可以进行导入
from Enseignant import Enseignant

# 一位教师
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1.show()
  • 第10行:基于一个字典创建一个[Enseignant]对象。为此,我们使用该类的默认构造函数创建一个[Enseignant]对象,并对其调用[fromdict]方法。 需要注意的是,此处执行的 [fromdict] 方法实际上是父类 [BaseEntity] 中的方法。具体来说:
    • 系统首先会在类 [Enseignant] 中查找方法 [fromdict]该方法不存在
    • 随后在父类 [Personne] 中查找该方法。该方法不存在
    • 接着在父类 [BaseEntity] 中查找。该方法存在
  • 第 11 行:显示对象 [Enseignant]

结果如下:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_01.py
Enseignant[1, paul, lourou, 56]

Process finished with exit code 0
13.2.1.2.6. 脚本 [fromdict_02]

脚本 [fromdict_02] 如下:


# 正在配置应用程序
import config

config = config.configure()

# 系统路径已配置 - 可以进行导入
from Enseignant import Enseignant

# 一位教师
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
enseignant1.show()
  • 第 10 行:创建了一个名字为空的教师。这应该会引发异常,因为类 [Personne] 不接受空名字。此示例展示了字典与对象之间的区别。对象可以验证其属性的有效性,而字典则不能;

结果如下:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_02.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_02.py", line 10, in <module>
    enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "", "âge": 56})
  File "C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\classes\02/entities\BaseEntity.py", line 55, in fromdict
    setattr(self, key, value)
  File "C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\classes\02/entities\Personne.py", line 42, in prénom
    raise MyException(11, "Le prénom doit être une chaîne de caractères non vide")
MyException.MyException: MyException[11, Le prénom doit être une chaîne de caractères non vide]

Process finished with exit code 1
13.2.1.2.7. 脚本 [fromdict_03]

脚本 [fromdict_03] 如下:


# 配置应用程序
import config

config = config.configure()

# 系统路径已配置 - 可以进行导入
from Enseignant import Enseignant

# 一位教师
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
enseignant1.show()
  • 第 10 行:尝试从包含一个不属于 [Enseignant] 类的键(性别)的字典中创建一名教师。此时应抛出异常;

结果如下:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_03.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_03.py", line 10, in <module>
    enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"})
  File "C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\classes\02/entities\BaseEntity.py", line 51, in fromdict
    raise MyException(2, f"la clé [{key}] n'est pas autorisée")
MyException.MyException: MyException[2, la clé [sexe] n'est pas autorisée]

Process finished with exit code 1
13.2.1.2.8. 脚本 [fromdict_04]

脚本 [fromdict_04][fromdict_03] 的副本,仅有一处细微差别:


# 正在配置应用程序
import config

config = config.configure()

# 系统路径已配置 - 可以进行导入
from Enseignant import Enseignant

# 一位教师
enseignant1 = Enseignant().fromdict({"id": 1, "nom": "lourou", "prénom": "albert", "âge": 56, "sexe": "M"}, silent=True)
enseignant1.show()
  • 第 10 行:我们使用了参数 [silent=True] 来指定,如果字典中的某个键不是类 [Enseignant] 的属性,则应直接忽略该键。在这种情况下,不会抛出任何异常;

结果如下:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromdict_04.py
Enseignant[1, albert, lourou, 56]

Process finished with exit code 0

13.2.2. 方法 [BaseEntity.asdict]

13.2.2.1. Définition

方法 [BaseEntity.asdict] 返回一个字典,其键为对象的属性:


    def asdict(self, included_keys: list = None, excluded_keys: list =[]) -> dict:
        # 对象属性
        attributes = self.__dict__
        # 新属性
        new_attributes = {}
        # 遍历属性
        for key, value in attributes.items():
            # 如果显式要求键
            if included_keys and key in included_keys:
                self.set_value(key, value, new_attributes)
            # 否则,如果未排除该键
            elif not included_keys and key not in self.__class__.excluded_keys and key not in excluded_keys:
                self.set_value(key, value, new_attributes)
        # 返回属性字典
        return new_attributes

注释

  • 第 1 行:函数 [asdict] 返回对象属性的字典;
  • 第 1 行:[included_keys]:要包含在字典中的键列表;
  • 第 1 行:[excluded_keys]:要从字典中排除的键列表;
  • 第 3 行:属性 [self.__dict__] 返回对象的属性字典。 属性名称是字典的键,其值是字典的值。一个对象可能包含对其他对象的引用。此时,属性名称会加上其所属类的名称作为前缀。这是我们不希望看到的。我们希望属性不带前缀;
  • 第 3 行:需注意,若函数 [asdict][BaseEntity] 的派生类中执行,则属性 [self.__dict__] 将返回该派生对象的属性字典;
  • 第 5 行:即将构建的字典;
  • 第 7 行:以 (键, 值) 的形式遍历 [self.__dict__] 的值;
  • 第9行:如果当前密钥属于待包含密钥列表,则通过我们稍后将要描述的函数[set_value]将其添加到字典[new_attributes]中
  • 第 12 行:如果未提供参数 [included_keys],则使用参数 [excluded_keys]。如果该属性不在应排除的属性列表中,则将其添加到字典 [new_attributes] 中;
  • 第 12 行:将属性从字典中排除有多种方式:
    • 该属性已在类属性 [excluded_keys] 级别定义;
    • 该属性已在传递给函数 [asdict] 的列表 [excluded_keys] 中定义;
    • 存在参数 [included_keys] 且该参数不包含该属性;
  • 第 15 行:返回字典 [new_attributes]

第 10 行和第 13 行中的 [set_value] 函数如下:


    @staticmethod
    def set_value(key: str, value, new_attributes: dict):
        # 键可以是 __Class__key 这种形式
        match = re.match("^.*?__(.*?)$", key)
        if match:
            # 记录新密钥
            newkey = match.groups()[0]
        else:
            # 密钥保持不变
            newkey = key
        # 将新密钥插入字典中 [new_attributes]
        # 如有必要,将关联值转换为以下类型之一
        # 字典、列表、简单类型
        new_attributes[newkey] = BaseEntity.check_value(value)

注释

  • 第 4 行:检查键是否为 __Class_key 形式。若该键属于主对象中的子对象,则呈现此形式。在此情况下,仅保留字符串 [key]
  • 第7行:仅保留字符串中最后两个下划线字符之后的字符串;
  • 第8-10行:如果键不是__Class_key这种形式,则保留其原样;
  • 第11-14行:与键[newkey]关联的值由静态方法[BaseEntity.check_value]计算得出;

静态方法 [BaseEntity.check_value] 如下:


    @staticmethod
    def check_value(value):
        # 该值可以是 BaseEntity、list、dict 或简单类型
        # value 是否是 BaseEntity 的实例?
        if isinstance(value, BaseEntity):
            value2 = value.asdict()
        # value 是否为 list 类型
        elif isinstance(value, list):
            value2 = BaseEntity.list2list(value)
        # value 是否为字典类型?
        elif isinstance(value, dict):
            value2 = BaseEntity.dict2dict(value)
        # value 是一个简单类型
        else:
            value2 = value
        # 返回结果
        return value2
  • 第 1 行:方法 [check_value] 是静态的(类方法而非实例方法)。它接收一个参数,该参数是需与字典键关联的值:
    • 第 17 行:如果该值是简单类型,则保持不变;
    • 第 5-6 行:如果该值是 BaseEntity 类型,则该值将被其字典替换。此时会发生递归调用;
    • 第8-9行:如果该值是一个列表,则将其替换为值 [BaseEntity.list2list]
    • 第11-12行:如果该值是一个字典,则将其替换为值 [BaseEntity.dict2dict]

静态方法 [BaseEntity.list2list] 如下:


    @staticmethod
    def list2list(liste: list) -> list:
        # 检查列表中的元素
        newlist = []
        for value in liste:
            newlist.append(BaseEntity.check_value(value))
        # 返回新列表
        return newlist
  • 第 2 行:该方法接收一个列表并返回一个列表;
  • 第 5-6 行:将作为参数接收的列表中的每个值替换为静态方法 [BaseEntity.check_value] 返回的值。 因此这里存在递归调用。静态方法 [BaseEntity.check_value] 会被调用,直到其参数 [value] 成为简单类型(而非 BaseEntity、列表或字典类型);

静态方法 [BaseEntity.dict2dict] 如下所示:


    @staticmethod
    def dict2dict(dictionary: dict) -> dict:
        # 检查字典中的元素
        newdict = {}
        for key, value in dictionary.items():
            newdict[key] = BaseEntity.check_value(value)
        # 返回新字典
        return newdict
  • 第 2 行:该方法接收一个字典并返回一个字典;
  • 第 5-6 行:将作为参数接收的字典中的每个值替换为静态方法 [BaseEntity.check_value] 返回的值。 因此这里存在递归调用。静态方法 [BaseEntity.check_value] 会被调用,直到其参数 [value] 成为简单类型(而非 BaseEntity、列表或字典类型);

13.2.2.2. Exemples

脚本 [asdict_01] 展示了 [asdict] 方法的多种用法:


# 配置应用程序
import config
config = config.configure()

# 系统路径已配置 - 可以进行导入
from Enseignant import Enseignant
from BaseEntity import BaseEntity

# 一位教师
enseignant1 = Enseignant().fromdict({"id"1"nom""lourou""prénom""paul""âge"56})
dict1 = enseignant1.asdict()
print(type(dict1))
print(enseignant1.__dict__)
print(dict1)
print(enseignant1.asdict(excluded_keys=["_Personne__âge"]))
Enseignant.excluded_keys = ["_Personne__prénom"]
print(enseignant1)
# 另一位教师
enseignant2 = Enseignant().fromdict({"id"2"nom""abélard""prénom""béatrice""âge"57})
print(enseignant2.asdict())
print(enseignant2.asdict(included_keys=["_Personne__nom"]))
# 实体内的实体列表
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asdict())
# 实体内的实体字典
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asdict())

执行结果如下:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/asdict_01.py
<class 'dict'>
{'_BaseEntity__id': 1, '_Personne__nom': 'lourou', '_Personne__prénom': 'paul', '_Personne__âge': 56}
{'id': 1, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}
{'id': 1, 'nom': 'lourou', 'prénom': 'paul'}
{"id": 1, "nom": "lourou", "âge": 56}
{'id': 2, 'nom': 'abélard', 'âge': 57}
{'nom': 'abélard'}
{'enseignants': [{'id': 1, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}, {'id': 2, 'nom': 'abélard', 'prénom': 'béatrice', 'âge': 57}]}
{'enseignants': [{'id': 1, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}, {'id': 2, 'nom': 'abélard', 'prénom': 'béatrice', 'âge': 57}], 'matières': {'maths': {'id': 1, 'nom': 'lourou', 'prénom': 'paul', 'âge': 56}, 'français': {'id': 2, 'nom': 'abélard', 'prénom': 'béatrice', 'âge': 57}}}

Process finished with exit code 0
  • 第 4 行展示了 [asdict] 方法相较于使用 [__dict__] 属性的优势。属性已去除了其类的前缀,这更适合显示;
  • 使用方法 [asdict] 有多种方式:
    • 若需获取所有属性:使用无参数的 [asdict] 方法;
    • 若仅需获取部分属性:
      • 需要包含的属性比需要排除的更多:只需使用参数 [excluded_keys]
      • 需要包含的属性少于需要排除的属性:仅使用参数 [included_keys]

13.2.3. 方法 [BaseEntity.asjson]

该方法用于从 [BaseEntity] 对象或其派生对象中获取字符串 jSON。它显示由方法 [asdict] 返回的字典中的字符串 jSON。 其代码如下:


def asjson(self, included_keys: list = None, excluded_keys: list = []) -> str:
        # JSON字符串
        return json.dumps(self.asdict(included_keys=included_keys, excluded_keys=excluded_keys), ensure_ascii=False)
  • 第 1 行:方法 [asjson] 的参数与方法 [asdict] 的参数相同;

以下是一个使用该方法的示例(asjson_01):


# 配置应用程序
import config
config = config.configure()

# 已配置 syspath - 可以进行导入
from Enseignant import Enseignant
from BaseEntity import BaseEntity

# 一位教师
enseignant1 = Enseignant().fromdict({"id"1"nom""lourou""prénom""paul""âge"56})
print(type(enseignant1.asjson()))
print(enseignant1.asjson(excluded_keys=["_Personne__âge"]))
Enseignant.excluded_keys = ["_Personne__prénom"]
print(enseignant1.asjson())
# 另一位教师
enseignant2 = Enseignant().fromdict({"id"2"nom""abélard""prénom""béatrice""âge"57})
print(enseignant2.asjson())
print(enseignant2.asjson(included_keys=["_Personne__nom"]))
# 实体内的实体列表
Enseignant.excluded_keys = []
entity1 = BaseEntity()
enseignants = [enseignant1, enseignant2]
setattr(entity1, "enseignants", enseignants)
print(entity1.asjson())
# 某个实体中的实体字典
matières = {"maths": enseignant1, "français": enseignant2}
setattr(entity1, "matières", matières)
print(entity1.asjson())

结果如下:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/asjson_01.py
<class 'str'>
{"id": 1, "nom": "lourou", "prénom": "paul"}
{"id": 1, "nom": "lourou", "âge": 56}
{"id": 2, "nom": "abélard", "âge": 57}
{"nom": "abélard"}
{"enseignants": [{"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56}, {"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57}]}
{"enseignants": [{"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56}, {"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57}], "matières": {"maths": {"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56}, "français": {"id": 2, "nom": "abélard", "prénom": "béatrice", "âge": 57}}}

Process finished with exit code 0

方法 [BaseEntity.__str__] 使用方法 [asjson] 来显示对象 [BaseEntity] 或其派生对象的标识:


# toString
    def __str__(self) -> str:
        return self.asjson()

13.2.4. 方法 [BaseEntity.fromjson]

方法 [BaseEntity.fromjson] 允许从字典 jSON 初始化类型为 [BaseEntity] 或其派生对象。其代码如下:


def fromjson(self, json_state: str, silent: bool = False):
        # 根据字符串更新对象状态 jSON
        return self.fromdict(json.loads(json_state), silent=silent)
  • 第 1 行:该方法接受两个参数:
    • [json_state]:用于初始化 [BaseEntity] 对象的 jSON 字典;
    • [silent]: 用于指定当字典 jSON 中存在无法作为对象 [BaseEntity] 属性的键时,是触发异常(silent=False)还是直接忽略(silent=True);
  • 第 3 行:首先构建 Python 字典(作为 jSON 字典的映射),然后使用 [fromdict] 方法基于该 Python 字典初始化 [BaseEntity] 对象;

以下是一个示例(fromjson_01):


# 配置应用程序
import config

config = config.configure()

# 系统路径已配置——可以进行导入
from Enseignant import Enseignant
import json

# 一位教师
json1 = json.dumps({"id": 1, "nom": "lourou", "prénom": "paul", "âge": 56})
enseignant1 = Enseignant().fromjson(json1)
enseignant1.show()
  • 第 11 行:创建字符串 jSON 并将其存储在字典中;
  • 第 12 行:使用该字符串初始化 [Enseignant] 对象;
  • 第 13 行:显示教师信息;

结果如下:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/fromjson_01.py
Enseignant[1, paul, lourou, 56]

Process finished with exit code 0

13.2.5. 脚本 [main]

脚本 [main] 总结了遇到的各种方法:


# 正在配置应用程序
import config

config = config.configure()

# 系统路径已配置 - 可以进行导入
from BaseEntity import BaseEntity
from MyException import MyException


# 一个班级
class ChildEntity(BaseEntity):
    # 从类状态中排除的属性
    excluded_keys = []

    @staticmethod
    def get_allowed_keys():
        return ["att1", "att2", "att3", "att4"]

    @property
    def att1(self) -> int:
        return self.__att1

    @att1.setter
    def att1(self, value: int):
        if 10 >= value >= 1:
            self.__att1 = value
        else:
            raise MyException(1, f"L'attribut [att1] attend une valeur dans l'intervalle [1,10] ({value})")


# 配置 ChildEntity
ChildEntity.excluded_keys = []
# 实例 ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2})
# 注意属性名称
# 这些名称在 [excluded_keys] 和 [included_keys] 中被使用
print(child.__dict__)
# 属性未以所属类为前缀
print(child)

# 实例 ChildEntity
try:
    child = ChildEntity().fromdict({"att1": 1, "att5": 5})
    print(child)
except MyException as erreur:
    print(erreur)

# 实例 ChildEntity
child = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
print(child)

# 从实例状态中排除某些键
ChildEntity.excluded_keys = ['att3']
print(child)

# 显式将某键从显示中排除
# 该键被添加到类级别全局排除的键中
print(child.asdict(excluded_keys=["_ChildEntity__att1"]))
print(child.asjson(excluded_keys=["att2"]))

# 类与字典之间的关联
# 可验证其内容的有效性
try:
    child = ChildEntity().fromdict({"att1": 20})
except MyException as erreur:
    print(erreur)

# 实例 ChildEntity
child1 = ChildEntity().fromdict({"att1": 1, "att2": 2, "att3": 3, "att4": 4})
# 实例 ChildEntity 包含另一个实例 ChildEntity
child2 = ChildEntity().fromdict({"att1": 10, "att2": 20, "att3": 30, "att4": child1})
print(child2)

# included_keys 优先于 excluded_keys,后者因此被忽略
ChildEntity.excluded_keys = ['_ChildEntity__att1', 'att2']
print(child.asdict(included_keys=["_ChildEntity__att1", "att3"], excluded_keys=["att3", "att4"]))

执行结果如下:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/02/main.py
{'_ChildEntity__att1': 1, 'att2': 2}
{"att1": 1, "att2": 2}
MyException[2, la clé [att5] n'est pas autorisée]
{"att1": 1, "att2": 2, "att3": 3, "att4": 4}
{"att1": 1, "att2": 2, "att4": 4}
{'att2': 2, 'att4': 4}
{"att1": 1, "att4": 4}
MyException[1, L'attribut [att1] attend une valeur dans l'intervalle [1,10] (20)]
{"att1": 10, "att2": 20, "att4": {"att1": 1, "att2": 2, "att4": 4}}
{'att1': 1, 'att3': 3}

Process finished with exit code 0

请注意结果中的第 2 行:正是属性 [ChildEntity.__dict__](代码第 38 行)让我们得以知晓应放入列表 [included_keys][excluded_keys] 中的属性名称。 同样在结果的第2行,需要注意的是:根据属性是在类内部通过getter/setter定义,还是像创建字典键那样创建的,其名称前是否带有类名[ChildEntity]