如何用Python制作动态yaml问卷?

问题描述 投票:0回答:2
- 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

上面的 yaml 是问卷的一个非常小的版本,涉及近 70 多个问题。我想设计一个可以读取上述 yaml 的类,并使其具有动态可扩展性和可读性。 该类还必须处理数据输入验证,并且必须为任何阅读它的人提供足够的信息。 我该怎么做?

编辑:

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)
python python-3.x yaml pyyaml
2个回答
0
投票

IMO 你不应该创建一个解释 YAML 的类,而是使用 YAML 的工具来标记每个问题 这导致它作为不同类的适当实例加载并取消

type
键。

这些类需要使用 YAML 加载器注册,并且可以基于公共基类或某些 类层次结构,因此它可以实现常见的行为。

在下面,我在名为

questions.yaml
的文件中添加了更新后的 YAML:

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):
        self._question = question
        self._required = required
        self._value = None  # to store user response to question

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

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

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

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

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):
        super().__init__(question=question, required=required)
        self._min = min_value
        self._max = max_value

    def check(self):
        """return False if value not in range or required and not set"""
        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})')

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

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

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


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)
for q in questions:
    print(q)

给出:

- !Text
  question: "What is your name?"
  required: true

- !Number
  question: "How old are you?"
  required: true
  min_value: 18
  max_value: 100

- !MultipleChoice
  question: "What is your gender?"
  choices:
    - Male
    - Female
    - Other
  required: true

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

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

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

- !TextArea
  question: "Any additional comments or feedback?"
===============
!Text("What is your name?", required=True)
!Number("How old are you?", range=[18, 100], required=True)
!MultipleChoice("What is your gender?", choices=[Male, Female, Other], required=True)
!CheckBoxes("Do you have any dietary restrictions?", choices=[Vegetarian, Vegan, Gluten-free, Dairy-free, None], required=False)
!CheckBoxes("Which programming languages do you know?", choices=[Python, JavaScript, Java, C++, Ruby], required=False)
!Scale("How satisfied are you with our product?", range=[1, 5], required=False)
!TextArea("Any additional comments or feedback?", required=False)

从上面开始,您可以添加适当显示每个类的方法 您的应用程序中,将

check
添加到
NumberQuestion
之外的其他类,等等。 您可以创建一个
Questions
类来保存序列,甚至可以将其注册为加载,但是 在这种情况下,这不是我会做的,只是使用从根加载的列表 级别 YAML 序列。


0
投票

有一项服务可以为您做到这一点 - https://formulosity.com/

© www.soinside.com 2019 - 2024. All rights reserved.