diff --git a/README.md b/README.md index b3947df..f4c8043 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/app.py b/app.py new file mode 100644 index 0000000..20589c2 --- /dev/null +++ b/app.py @@ -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) diff --git a/calculator_core.py b/calculator_core.py new file mode 100644 index 0000000..d660b11 --- /dev/null +++ b/calculator_core.py @@ -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()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..95fef4e --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Flask==3.0.3 diff --git a/templates/calculator.html b/templates/calculator.html new file mode 100644 index 0000000..37e42d2 --- /dev/null +++ b/templates/calculator.html @@ -0,0 +1,68 @@ + + + + + + Python 网页计算器 + + + +
+

Python 网页计算器

+

支持 + - * / % ** 和括号,如: (2+3)*4 / 5

+ +
+ + + +
+ + {% if result is not none %} +
结果:{{ result }}
+ {% endif %} + + {% if error %} +
错误:{{ error }}
+ {% endif %} +
+ + diff --git a/tests/test_calculator_core.py b/tests/test_calculator_core.py new file mode 100644 index 0000000..131f803 --- /dev/null +++ b/tests/test_calculator_core.py @@ -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()