You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python实现可动态扩展的YAML问卷?

问题:哪种Python实现方案更适合处理YAML问卷?

需求背景

需要实现Python类处理包含70余个问题的YAML问卷,核心要求如下:

  • 读取YAML格式的问卷内容
  • 支持动态扩展(比如新增问题类型)
  • 代码具备良好可读性
  • 处理用户输入的数据验证

YAML问卷示例

- question: "What is your name?"
  type: text
  required: true

- question: "How old are you?"
  type: number
  required: true
  min_value: 18
  max_value: 100

- question: "What is your gender?"
  type: multiple_choice
  choices:
    - Male
    - Female
    - Other
  required: true

- question: "Do you have any dietary restrictions?"
  type: checkboxes
  choices:
    - Vegetarian
    - Vegan
    - Gluten-free
    - Dairy-free
    - None

- question: "Which programming languages do you know?"
  type: checkboxes
  choices:
    - Python
    - JavaScript
    - Java
    - C++
    - Ruby

- question: "How satisfied are you with our product?"
  type: scale
  min_value: 1
  max_value: 5

- question: "Any additional comments or feedback?"
  type: textarea

两种实现方案

方案一

from enum import Enum
import yaml


class QuestionKind(Enum):
    TEXT = "text"
    NUMBER = "number"
    MULTIPLE_CHOICE = "multiple_choice"
    CHECKBOXES = "checkboxes"
    SCALE = "scale"
    TEXTAREA = "textarea"


class Question:
    def __init__(self, prompt, kind, choices=None):
        self.prompt = prompt
        self.kind = kind
        self.choices = choices


class Quiz:
    def __init__(self, questions_file):
        self.questions = self.load_questions(questions_file)
        self.current_index = 0

    def load_questions(self, questions_file):
        with open(questions_file, "r") as file:
            data = yaml.safe_load(file)
        
        questions = []
        for item in data:
            prompt = item["question"]
            kind = QuestionKind(item["type"])
            choices = item.get("choices")
            question = Question(prompt, kind, choices)
            questions.append(question)
        
        return questions

    def current_question(self):
        if self.current_index < len(self.questions):
            return self.questions[self.current_index]
        else:
            return None

    def provide_answer(self, answer):
        self.current_index += 1
        # Process the answer as needed

        return self.current_question()


# Usage example
quiz = Quiz("questions.yaml")
current_question = quiz.current_question()
while current_question is not None:
    print("Question:", current_question.prompt)
    answer = input("Your answer: ")
    current_question = quiz.provide_answer(answer)

方案二

import sys
from pathlib import Path
import ruamel.yaml

file_in = Path('questions.yaml')
print(file_in.read_text(), end='===============\n')


class BaseQuestion:
    def __init__(self, question, required=False, conditions=None):
        self._question = question
        self._required = required
        self._value = None  # to store user response to question
        self._conditions = conditions or []

    @classmethod
    def from_yaml(cls, constructor, node):
        kw = ruamel.yaml.CommentedMap()
        constructor.construct_mapping(node, kw)
        return cls(**kw)

    def check_conditions(self, responses):
        for condition in self._conditions:
            question_id = condition.get('id')
            operator = condition.get('operator')
            value = condition.get('value')
            if question_id in responses and self._compare_values(responses[question_id], operator, value):
                return False
        return True

    def _compare_values(self, value1, operator, value2):
        """Compare two values based on the given operator"""
        if operator == "==":
            return value1 == value2
        elif operator == "!=":
            return value1 != value2
        elif operator == ">":
            return value1 > value2
        elif operator == "<":
            return value1 < value2
        elif operator == ">=":
            return value1 >= value2
        elif operator == "<=":
            return value1 <= value2
        else:
            raise ValueError(f"Invalid operator: {operator}")

    def __repr__(self):
        return f'{self.yaml_tag}("{self._question}", required={self._required}, conditions={self._conditions})'


class BaseTextQuestion(BaseQuestion):
    yaml_tag = '!Text'

    def __init__(self, question, required=False, conditions=None):
        super().__init__(question=question, required=required, conditions=conditions)


class TextQuestion(BaseTextQuestion):
    yaml_tag = '!Text'


class TextAreaQuestion(BaseTextQuestion):
    yaml_tag = '!TextArea'


class NumberQuestion(BaseQuestion):
    yaml_tag = '!Number'

    def __init__(self, question, min_value, max_value, required=False, conditions=None):
        super().__init__(question=question, required=required, conditions=conditions)
        self._min = min_value
        self._max = max_value

    def check(self, responses):

        if not self.check_conditions(responses):
            return False
        if self._required and self._value is None:
            return False
        if self._value is None:
            return True
        return self._min <= self._value <= self._max

    def __repr__(self):
        return f'{self.yaml_tag}("{self._question}", range=[{self._min}, {self._max}], required={self._required}, conditions={self._conditions})'


class ScaleQuestion(NumberQuestion):
    yaml_tag = '!Scale'


class ChoiceQuestion(BaseQuestion):
    def __init__(self, question, choices, required=False, conditions=None):
        super().__init__(question=question, required=required, conditions=conditions)
        self._choices = choices

    def __repr__(self):
        return f'{self.yaml_tag}("{self._question}", choices=[{", ".join(self._choices)}], required={self._required}, conditions={self._conditions})'


class MultipleChoiceQuestion(ChoiceQuestion):
    yaml_tag = '!MultipleChoice'


class CheckBoxesQuestion(ChoiceQuestion):
    yaml_tag = '!CheckBoxes'


yaml = ruamel.yaml.YAML()
yaml.register_class(TextQuestion)
yaml.register_class(TextAreaQuestion)
yaml.register_class(NumberQuestion)
yaml.register_class(ScaleQuestion)
yaml.register_class(MultipleChoiceQuestion)
yaml.register_class(CheckBoxesQuestion)

questions = yaml.load(file_in)


def display_questions(questions):
    """Display the questions based on the conditions and user responses"""
    responses = {}
    for question in questions:
        if question.check_conditions(responses):
            response = input(question._question + " ")
            responses[question.__dict__.get("_id")] = response
    print("User Responses:", responses)


display_questions(questions)

方案对比与选择

方案一优缺点

  • 优点:结构简单,代码量少,入门门槛低,用Enum统一管理问题类型,逻辑直观。
  • 缺点:完全缺失输入验证逻辑,没有处理required、min_value等问卷字段;动态扩展需要修改Enum和Question类,后续维护会越来越臃肿;单个Question类承担所有问题类型的属性,不符合单一职责原则,面对70+问题的规模,代码会变得难以维护。

方案二优缺点

  • 优点:
    1. 模块化设计:采用类继承体系,每个问题类型对应独立子类,职责单一,可读性极强,新增问题类型只需继承对应父类即可,完美支持动态扩展。
    2. 内置验证逻辑:每个子类实现了对应的数据验证(比如NumberQuestion的范围检查、必填项校验),直接匹配问卷中的required、min_value等字段。
    3. 条件支持:内置了问题显示的条件判断逻辑,能实现问卷的分支流程,适合复杂问卷场景。
    4. YAML映射清晰:通过ruamel.yaml的自定义标签实现YAML字段到Python类的直接映射,处理大规模问卷时逻辑更清晰。
  • 缺点:需要熟悉ruamel.yaml的自定义标签用法,学习成本略高于方案一,但对于长期维护来说完全值得。

最终结论

方案二更适合处理这个需求。它完美覆盖了所有核心要求:读取YAML内容、支持动态扩展、代码可读性强、内置输入验证,尤其是面对70+问题的规模,方案二的模块化设计能大幅降低后续维护和扩展的成本。

内容的提问来源于stack exchange,提问作者ProxilityProblemSolver

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 10:37:06