{
const scripts = Array.from(
document.querySelectorAll("script.fcpython-ojs-quiz-config")
);
const script = scripts.find(
(node) => node.dataset.fcpythonRendered !== "true"
);
if (!script) {
return html`<div class="fcpython-quiz fcpython-quiz-warning">
Quiz configuration was not found.
</div>`;
}
script.dataset.fcpythonRendered = "true";
const quiz = JSON.parse(script.textContent);
const container = html`<div class="fcpython-quiz"></div>`;
const title = document.createElement("h3");
title.textContent = quiz.title;
container.appendChild(title);
const instructions = document.createElement("p");
instructions.textContent = quiz.instructions;
container.appendChild(instructions);
const progress = document.createElement("div");
progress.className = "fcpython-quiz-progress";
const counter = document.createElement("p");
counter.className = "fcpython-quiz-counter";
counter.setAttribute("aria-live", "polite");
progress.appendChild(counter);
const tabs = document.createElement("div");
tabs.className = "fcpython-quiz-steps";
tabs.setAttribute("role", "tablist");
tabs.setAttribute("aria-label", "Quiz questions");
progress.appendChild(tabs);
container.appendChild(progress);
const questions = document.createElement("div");
questions.className = "fcpython-quiz-questions";
const feedbackNodes = [];
const questionPanels = [];
const stepButtons = [];
let currentQuestion = 0;
function setStepStatus(questionIndex, status) {
const step = stepButtons[questionIndex];
const question = quiz.questions[questionIndex];
const statuses = {
answered: "answered",
correct: "correct",
incorrect: "incorrect",
unanswered: "not answered",
};
step.classList.remove(
"is-answered",
"is-correct",
"is-incorrect",
"is-unanswered"
);
if (status) {
step.classList.add(`is-${status}`);
}
const statusLabel = status ? `, ${statuses[status]}` : "";
step.setAttribute(
"aria-label",
`Question ${questionIndex + 1}: ${question.prompt}${statusLabel}`
);
}
function showQuestion(questionIndex, focusPanel = false) {
currentQuestion = Math.max(
0,
Math.min(questionIndex, quiz.questions.length - 1)
);
questionPanels.forEach((panel, index) => {
panel.hidden = index !== currentQuestion;
});
stepButtons.forEach((step, index) => {
const isCurrent = index === currentQuestion;
step.classList.toggle("is-current", isCurrent);
step.setAttribute("aria-selected", String(isCurrent));
step.tabIndex = isCurrent ? 0 : -1;
});
counter.textContent = `Question ${currentQuestion + 1} of ${quiz.questions.length}`;
const selected = questionPanels[currentQuestion].querySelector(
"input[type='radio']:checked"
);
const isLastQuestion = currentQuestion === quiz.questions.length - 1;
previous.hidden = currentQuestion === 0;
next.hidden = isLastQuestion || !selected;
check.hidden = !isLastQuestion;
if (focusPanel) {
questionPanels[currentQuestion].focus();
}
}
quiz.questions.forEach((question, questionIndex) => {
const tabId = `${quiz.id}-question-tab-${questionIndex + 1}`;
const panelId = `${quiz.id}-question-panel-${questionIndex + 1}`;
const step = document.createElement("button");
step.type = "button";
step.className = "fcpython-quiz-step";
step.id = tabId;
step.textContent = String(questionIndex + 1);
step.setAttribute("role", "tab");
step.setAttribute("aria-controls", panelId);
step.setAttribute("aria-selected", "false");
step.tabIndex = -1;
step.addEventListener("click", () => showQuestion(questionIndex));
step.addEventListener("keydown", (event) => {
let destination = null;
if (event.key === "ArrowRight") {
destination = (questionIndex + 1) % quiz.questions.length;
} else if (event.key === "ArrowLeft") {
destination =
(questionIndex - 1 + quiz.questions.length) % quiz.questions.length;
} else if (event.key === "Home") {
destination = 0;
} else if (event.key === "End") {
destination = quiz.questions.length - 1;
}
if (destination !== null) {
event.preventDefault();
showQuestion(destination);
stepButtons[destination].focus();
}
});
stepButtons.push(step);
tabs.appendChild(step);
setStepStatus(questionIndex, "");
const panel = document.createElement("div");
panel.className = "fcpython-quiz-panel";
panel.id = panelId;
panel.setAttribute("role", "tabpanel");
panel.setAttribute("aria-labelledby", tabId);
panel.tabIndex = -1;
const fieldset = document.createElement("fieldset");
fieldset.className = "fcpython-quiz-question";
const legend = document.createElement("legend");
legend.textContent = question.prompt;
fieldset.appendChild(legend);
question.options.forEach((option, optionIndex) => {
const label = document.createElement("label");
label.className = "fcpython-quiz-option";
const input = document.createElement("input");
input.type = "radio";
input.name = `${quiz.id}-${question.id}`;
input.value = String(optionIndex);
input.addEventListener("change", () => {
setStepStatus(questionIndex, "answered");
feedbackNodes[questionIndex].textContent = "";
feedbackNodes[questionIndex].className = "fcpython-quiz-feedback";
score.textContent = "";
showQuestion(questionIndex);
});
const text = document.createElement("span");
text.textContent = option;
label.appendChild(input);
label.appendChild(text);
fieldset.appendChild(label);
});
const feedback = document.createElement("p");
feedback.className = "fcpython-quiz-feedback";
feedback.setAttribute("aria-live", "polite");
feedbackNodes.push(feedback);
fieldset.appendChild(feedback);
panel.appendChild(fieldset);
questionPanels.push(panel);
questions.appendChild(panel);
});
container.appendChild(questions);
const actions = document.createElement("div");
actions.className = "fcpython-quiz-actions";
const previous = document.createElement("button");
previous.type = "button";
previous.className = "fcpython-quiz-secondary";
previous.textContent = "Previous";
previous.addEventListener("click", () => {
showQuestion(currentQuestion - 1, true);
});
const next = document.createElement("button");
next.type = "button";
next.textContent = "Next question";
next.addEventListener("click", () => {
showQuestion(currentQuestion + 1, true);
});
const check = document.createElement("button");
check.type = "button";
check.textContent = "Check answers";
const reset = document.createElement("button");
reset.type = "button";
reset.className = "fcpython-quiz-secondary fcpython-quiz-reset";
reset.textContent = "Reset";
const score = document.createElement("p");
score.className = "fcpython-quiz-score";
score.setAttribute("aria-live", "polite");
check.addEventListener("click", () => {
let correctCount = 0;
let firstQuestionToReview = null;
quiz.questions.forEach((question, questionIndex) => {
const selected = container.querySelector(
`input[name="${quiz.id}-${question.id}"]:checked`
);
const feedback = feedbackNodes[questionIndex];
if (!selected) {
feedback.textContent = "Choose an answer before checking.";
feedback.className = "fcpython-quiz-feedback";
setStepStatus(questionIndex, "unanswered");
if (firstQuestionToReview === null) {
firstQuestionToReview = questionIndex;
}
return;
}
const selectedIndex = Number(selected.value);
if (selectedIndex === question.answer_index) {
correctCount += 1;
feedback.textContent = `✅ Correct. ${question.explanation}`;
feedback.className = "fcpython-quiz-feedback is-correct";
setStepStatus(questionIndex, "correct");
} else {
const answer = question.options[question.answer_index];
feedback.textContent = `❌ Not yet. Correct answer: ${answer}. ${question.explanation}`;
feedback.className = "fcpython-quiz-feedback is-incorrect";
setStepStatus(questionIndex, "incorrect");
if (firstQuestionToReview === null) {
firstQuestionToReview = questionIndex;
}
}
});
score.textContent = `Score: ${correctCount}/${quiz.questions.length}`;
if (firstQuestionToReview !== null) {
showQuestion(firstQuestionToReview, true);
}
});
reset.addEventListener("click", () => {
container.querySelectorAll("input[type='radio']").forEach((input) => {
input.checked = false;
});
feedbackNodes.forEach((feedback) => {
feedback.textContent = "";
feedback.className = "fcpython-quiz-feedback";
});
stepButtons.forEach((_, questionIndex) => {
setStepStatus(questionIndex, "");
});
score.textContent = "";
showQuestion(0, true);
});
actions.appendChild(previous);
actions.appendChild(next);
actions.appendChild(check);
actions.appendChild(reset);
container.appendChild(actions);
container.appendChild(score);
showQuestion(0);
return container;
}
FreeCampus Python
Comments and Docstrings That Help
Write comments and introductory docstrings that explain purpose, units, constraints, and decisions without merely translating the code.
python-foundations
python-syntax
comments
docstrings
Course progress
0%
Python code has two audiences. Python needs exact syntax. People need purpose, context, units, assumptions, and reasons. Clear names and structure should carry most of the explanation; comments and docstrings fill the gaps that code alone cannot express.
NoteQuestions you will answer
- What information belongs in a comment rather than in a name?
- How is a docstring different from a
#comment? - When does Python make a docstring discoverable through
help()? - Why can an outdated comment be more dangerous than no comment?
2. Prefer comments that explain why
This comment merely translates visible syntax:
Anyone who recognizes + can read that. It adds maintenance work without adding knowledge.
This comment supplies context that the expression cannot:
Useful comments often explain:
- why a non-obvious rule exists;
- which unit a number uses;
- where a requirement or constraint came from;
- why an apparently simpler approach is unsafe;
- what a temporary workaround is waiting for;
- what a surprising boundary means.
Put meaning in names first
Weak:
Stronger:
The stronger version does not require three comments because the names carry the meaning.
A comment can still add a domain rule:
TipUse this order
First improve the names and structure. Then add a comment only for important information that still is not visible.
3. Place comments where readers need them
A full-line comment introduces a decision
The comment sits immediately above the decision it explains.
An inline comment labels a compact fact
Inline comments work best when both the code and explanation remain short. Avoid pushing a long paragraph to the right of a long expression.
A short comment can divide a small script
Section comments can help in a small educational script. In larger programs, functions and modules become better structural tools.
Too many comments interrupt reading
Avoid narrating every line:
The code already tells that story. A single relevant comment may be enough:
Check your understanding
4. Comments must change when the code changes
An outdated comment lies:
Python uses 0.20; it ignores the conflicting comment. A reader may trust the wrong explanation and make a bad decision later.
Possible repairs depend on the real requirement:
or:
The absence of a syntax error does not decide which requirement is correct.
Review comments as part of every behavior change
Suppose the first version excludes breaks:
A later requirement includes the entire scheduled time:
The old comment must be removed or rewritten. Comments are part of the product even though Python does not execute them.
Temporary comments need an owner or condition
Vague:
Useful:
The second comment states what is missing and what condition allows the work to continue. A real project may also include an issue reference or owner.
Do not use TODO to avoid finishing a requirement that the current lesson or challenge expects.
5. A docstring documents a surrounding object
A docstring is a string literal placed as the first statement in a module, function, class, or method body. Python records it as that object’s documentation.
At the top of a Python file or first code cell:
The opening string is the module docstring because it is the first statement.
A comment and module docstring have different jobs:
- The docstring identifies the module’s overall purpose.
- The comment explains one local domain decision.
- The assignments perform the calculation.
A random triple-quoted string is not automatically a docstring
The triple-quoted text creates a string value, but it is not the module’s docstring because another statement came first.
Triple quotes allow multiline strings. Position, not triple-quote spelling alone, gives a string its docstring role.
Inspect the current module docstring
In a notebook cell, run:
Many notebook environments expose the cell or interactive module documentation differently, so the exact surrounding value can vary. The reliable language rule is that a module’s first string statement becomes its __doc__ value when the module is loaded.
A .py file makes this easiest to observe:
6. Function and class docstrings are previews
Functions are introduced fully in Unit 5. For now, read the placement:
The levels are:
def ...:opens the function body.- The indented string is the first body statement, so it becomes the function docstring.
- The indented
returnprovides the function’s behavior.
Move an assignment above the string:
The string is now an unused expression inside the function, not its docstring. The code is syntactically valid, but documentation discovery changes.
A class follows the same position rule:
You do not need to design classes yet. Recognize the class header, indented body, first string, and placeholder pass.
NoteDocstrings describe a public promise
A useful introductory docstring says what the module, function, or class provides. Detailed parameter formats, exceptions, examples, documentation toolchains, and publishing belong to Unit 15.
7. help() makes docstrings discoverable
Run:
The output includes the function’s name, signature, and docstring.
You can also inspect the recorded text directly:
The dot selects the __doc__ attribute. Attribute and method syntax receive more attention in later units; here it shows that docstrings are data Python preserves, not comments Python discards.
Compare with a comment-only function
The output is None because a # comment is not recorded as a docstring.
Check your understanding
8. Write a small docstring with a clear promise
For a module, begin with one direct sentence:
For a simple function preview:
Prefer a verb that describes the result or action:
Return ...Calculate ...Load ...Display ...Represent ...
Avoid empty wording:
The weak docstring repeats the name and says nothing about the result.
Document units and boundaries when they matter
This is more useful because a caller can distinguish minutes from seconds and knows what “focused” excludes.
Do not promise validation or error handling the function does not provide. A docstring must match actual behavior.
9. Review good and harmful explanations
Example A
The comment repeats the operator but not the reason. Improve it:
Example B
“Never” has no context. Improve it:
Example C
Improve the module docstring:
Example D
Remove the comment. The names and expression already explain the line.
10. Prepare a program for a classmate
Start with:
The behavior is small, but the program lacks context. Revise it so that:
- a module docstring states the program’s purpose;
- names express course, scheduled time, break time, and focused time;
- one comment explains why break time is excluded;
- no comment merely translates an assignment;
- output labels identify both displayed values;
- the result remains
35focused minutes; - changing scheduled time to
60requires changing only one input assignment.
Then give only the revised source to another person. Ask them:
- What unit is time measured in?
- Why is the break subtracted?
- Which value should they edit for a longer session?
- What output should appear for a 60-minute session?
If the code and documentation do not answer those questions, revise them.
Hint: let names carry the basic story
Use names such as course_name, session_minutes, break_minutes, and focused_minutes. Reserve the comment for the reporting rule that excludes the break.
Show one documented version
"""Display the active study time for one planned course session."""
course_name = "Python Foundations"
session_minutes = 45
break_minutes = 10
# The study report measures active work, so scheduled breaks are excluded.
focused_minutes = session_minutes - break_minutes
print("Course:", course_name)
print("Focused minutes:", focused_minutes)
assert focused_minutes == 3511. Find documentation drift
Run this first version:
Now imagine the event discount changes to 20 percent. Change only the numeric value and observe that the code runs while the docstring becomes false.
Your repair must update both behavior and documentation:
The revised docstring avoids duplicating a value that already has a clear name. Another valid choice is to retain “20 percent” and commit to updating it whenever the rule changes. Decide which promise will be safer for the next reader.
12. Check the next-reader experience
Key points
TipKey points
- A
#comment is ignored by Python outside a string. - Clear names and structure should explain ordinary mechanics.
- Useful comments record purpose, units, constraints, and non-obvious decisions.
- Comments must be updated when the behavior or requirement changes.
- A docstring is a string in the first statement position of a module, function, class, or method.
- Triple quotes alone do not make a string a docstring.
help()and.__doc__expose recorded docstrings.- Documentation should promise only behavior the code actually provides.
1. Comments speak to people reading the source
A
#begins a comment outside a string. Python ignores the comment from that point to the end of the physical line.The output is
45. The comment does not create a value, perform assignment, or become part of the output.A comment can follow code:
Python performs the assignment and ignores the comment.
The
#character inside a string is ordinary text:Expected output:
Quotation marks determine that
#1belongs to the string rather than starting a comment.Remove a comment and compare behavior
Run:
Then remove only the comment and rerun. The output stays
200. A normal comment does not change program behavior.This makes comments useful for explanation, but unsuitable for disabling important logic without careful review.