Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,28 @@
abc
===
# abc

we start it now
一个使用 Python(Flask)实现的简单网页计算器。

## 功能
- 网页输入算术表达式并计算结果
- 支持 `+ - * / % **` 与括号
- 对非法输入给出错误提示

## 运行方式
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python app.py
```

## 直接运行核心程序
```bash
python calculator_core.py "1 + 2 * (3 - 1)"
```

## 测试
```bash
python -m unittest discover -s tests
```

浏览器访问:`http://127.0.0.1:5000`
25 changes: 25 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from flask import Flask, render_template, request

from calculator_core import evaluate_expression

app = Flask(__name__)


@app.route("/", methods=["GET", "POST"])
def calculator():
expression = ""
result = None
error = None

if request.method == "POST":
expression = request.form.get("expression", "")
try:
result = evaluate_expression(expression)
except Exception as exc:
error = str(exc)

return render_template("calculator.html", expression=expression, result=result, error=error)


if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
88 changes: 88 additions & 0 deletions calculator_core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from __future__ import annotations

import argparse
import ast
import sys


class SafeEvaluator(ast.NodeVisitor):
"""Safely evaluate arithmetic expressions."""

ALLOWED_BINOPS = (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow, ast.Mod)
ALLOWED_UNARYOPS = (ast.UAdd, ast.USub)

def visit_Expression(self, node: ast.Expression) -> float:
return self.visit(node.body)

def visit_BinOp(self, node: ast.BinOp) -> float:
if not isinstance(node.op, self.ALLOWED_BINOPS):
raise ValueError("不支持的运算符")
left = self.visit(node.left)
right = self.visit(node.right)

if isinstance(node.op, ast.Add):
return left + right
if isinstance(node.op, ast.Sub):
return left - right
if isinstance(node.op, ast.Mult):
return left * right
if isinstance(node.op, ast.Div):
if right == 0:
raise ValueError("除数不能为 0")
return left / right
if isinstance(node.op, ast.Pow):
return left**right
if isinstance(node.op, ast.Mod):
return left % right

raise ValueError("不支持的运算")

def visit_UnaryOp(self, node: ast.UnaryOp) -> float:
if not isinstance(node.op, self.ALLOWED_UNARYOPS):
raise ValueError("不支持的一元运算符")
value = self.visit(node.operand)
return +value if isinstance(node.op, ast.UAdd) else -value

def visit_Constant(self, node: ast.Constant) -> float:
value = node.value
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError("只允许数字")
return float(value)

def visit_Num(self, node: ast.Num) -> float: # pragma: no cover (compat)
return float(node.n)

def generic_visit(self, node: ast.AST):
raise ValueError("表达式包含不允许的内容")


def evaluate_expression(expression: str) -> float:
if not expression.strip():
raise ValueError("请输入表达式")

parsed = ast.parse(expression, mode="eval")
evaluator = SafeEvaluator()
return evaluator.visit(parsed)


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Safely evaluate arithmetic expressions.")
parser.add_argument("expression", nargs="?", help="要计算的算术表达式")
args = parser.parse_args(argv)

if not args.expression:
parser.print_help()
return 0

try:
result = evaluate_expression(args.expression)
except Exception as exc:
print(f"错误:{exc}", file=sys.stderr)
return 1

print(result)
return 0


if __name__ == "__main__":
raise SystemExit(main())
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Flask==3.0.3
68 changes: 68 additions & 0 deletions templates/calculator.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Python 网页计算器</title>
<style>
:root { font-family: Arial, sans-serif; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: linear-gradient(135deg, #dbeafe, #f5f3ff);
}
.card {
width: min(92vw, 460px);
background: white;
border-radius: 16px;
box-shadow: 0 12px 32px rgba(0,0,0,.12);
padding: 24px;
}
h1 { margin-top: 0; font-size: 1.4rem; }
p.hint { color: #4b5563; margin-top: 4px; }
input[type="text"] {
width: 100%;
font-size: 1.1rem;
padding: 12px;
border: 1px solid #d1d5db;
border-radius: 10px;
box-sizing: border-box;
}
button {
margin-top: 12px;
width: 100%;
background: #2563eb;
color: white;
border: none;
padding: 12px;
font-size: 1rem;
border-radius: 10px;
cursor: pointer;
}
.result { margin-top: 14px; font-weight: bold; color: #047857; }
.error { margin-top: 14px; font-weight: bold; color: #b91c1c; }
</style>
</head>
<body>
<div class="card">
<h1>Python 网页计算器</h1>
<p class="hint">支持 + - * / % ** 和括号,如: (2+3)*4 / 5</p>

<form method="post">
<label for="expression">请输入表达式:</label>
<input id="expression" type="text" name="expression" value="{{ expression }}" placeholder="例如:1 + 2 * (3 - 1)" required>
<button type="submit">计算</button>
</form>

{% if result is not none %}
<div class="result">结果:{{ result }}</div>
{% endif %}

{% if error %}
<div class="error">错误:{{ error }}</div>
{% endif %}
</div>
</body>
</html>
29 changes: 29 additions & 0 deletions tests/test_calculator_core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import io
import unittest
from contextlib import redirect_stdout

from calculator_core import evaluate_expression, main


class CalculatorCoreTests(unittest.TestCase):
def test_evaluate_expression_basic_math(self):
self.assertEqual(evaluate_expression("1 + 2 * (3 - 1)"), 5.0)

def test_evaluate_expression_rejects_bool_constants(self):
with self.assertRaisesRegex(ValueError, "只允许数字"):
evaluate_expression("True")

def test_evaluate_expression_rejects_division_by_zero(self):
with self.assertRaisesRegex(ValueError, "除数不能为 0"):
evaluate_expression("1 / 0")

def test_main_prints_result_for_cli_argument(self):
buffer = io.StringIO()
with redirect_stdout(buffer):
code = main(["2 + 3"])
self.assertEqual(code, 0)
self.assertEqual(buffer.getvalue().strip(), "5.0")


if __name__ == "__main__":
unittest.main()
Loading