お子様がいるプログラマーのみなさん
はい。では前回作成した関数を使って計算プリントのPDFファイルをバシバシ出力していきましょう。
Tkinterとreportlabを使います
ここに一番時間がかかったのですが、正直ほかの記事を参照した方がいいと思います。
参考にした記事
-
Tkinterで数値のみの入力制限をする方法
https://kuroro.blog/python/YaHEdMd4ScGvrU44zdT6/#google_vignette -
Tkinterのドキュメント
https://docs.python.org/ja/3/library/tkinter.html -
reportlabのドキュメント
https://docs.reportlab.com/
今回の記事ではとりあえず実践例を載せちゃいます!!!
以下をコピペするだけで計算ドリルのPDF作成できちゃいます!!!
構成
parts.py ・・・ 問題作成関数とPDF作成関数をまとめたpy file
addition.py ・・・ 足し算を出力するpy file
subtraction.py ・・・ 引き算を出力するpy file
multiplication.py ・・・ 掛け算を出力するpy file
division.py ・・・ 割り算を出力するpy file
parts.py
前回作成した問題作成関数と、今回初出のPDF作成関数をまとめたものです。
parts.py
import random
import os
from datetime import datetime
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.pagesizes import A4, mm
# 繰り上がり判定機
def k_up(N, M):
d_N = len(str(N))
d_M = len(str(M))
if d_N > d_M:
l = d_M
else:
l = d_N
for i in range(1, l+1):
if int(str(N)[-i]) + int(str(M)[-i]) >= 10:
return False
else:
continue
# 繰り上がりありならFalse、なしならTrueを返す
return True
def generate_temp_addition(N, M):
retern random.randint(10**(N-1), 10**(N)-1), random.randint(10**(M-1), 10**(M)-1)
def generate_addition(N, M, k):
# 繰り上がりなし
if k is False:
# Trueとなれば抜けられるようにする
while k is False:
n_N, n_M = generate_temp_addition(int(N), int(M))
k = k_up(n_N, n_M)
# 繰り上がりあり
else:
# Falseとなれば抜けられるようにする
while k is True:
n_N, n_M = generate_temp_addition(int(N), int(M))
k = k_up(n_N, n_M)
return {"N": n_N, "M": n_M, "ans": {"value": n_N+n_M}}
def k_down(N, M):
for i in range(1, len(str(M))+1):
if int(str(N)[-i]) < int(str(M)[-i]):
return False
else:
continue
def generate_temp_asubtraction(N, M):
retern random.randint(10**(N-1), 10**(N)-1), random.randint(10**(M-1), n_N)
def generate_subtraction(N, M, k):
# 繰り上がりなし
if k is False:
# Trueとなれば抜けられるようにする
while k is False:
n_N, n_M = generate_temp_subtraction(int(N), int(M))
k = k_down(n_N, n_M)
# 繰り上がりあり
else:
# Falseとなれば抜けられるようにする
while k is True:
n_N, n_M = generate_temp_subtraction(int(N), int(M))
k = k_down(n_N, n_M)
return {"N": n_N, "M": n_M, "ans": {"value": n_N-n_M}}
def generate_multiplication(N, M):
n_N = random.randint(10**(N-1), 10**(N)-1)
n_M = random.randint(10**(M-1), 10**(M)-1)
return {"N": n_N, "M": n_M, "ans": {"value": n_N*n_M}}
def make_divisors(n):
lower_divisors , upper_divisors = [], []
i = 1
while i*i <= n:
if n % i == 0:
lower_divisors.append(i)
if i != n // i:
upper_divisors.append(n//i)
i += 1
return lower_divisors + upper_divisors[::-1]
def generate_division(N, M, s, a):
if a is False:
p_divisors = []
while len(p_divisors) == 0:
n_N = random.randint(10**(N-1), 10**(N)-1)
divisors = make_divisors(n_N)
print(divisors)
if s is False:
del divisors[0]
del divisors[-1]
p_divisors = [i for i in divisors if 10**(M-1) <= i <= 10**(M)-1]
n_M = random.choice(p_divisors)
else:
n_N = random.randint(10**(N-1), 10**(N)-1)
divisors = make_divisors(n_N)
if N == M:
n_M = random.randint(10**(M-1), n_N)
else:
n_M = random.randint(10**(M-1), 10**(M)-1)
while n_M in divisors:
if N == M:
n_M = random.randint(10**(M-1), n_N)
else:
n_M = random.randint(10**(M-1), 10**(M)-1)
return {"N": n_N, "M": n_M, "ans": {"value": int(n_N/n_M), "sho": n_N//n_M, "amari":n_N%n_M}}
def Set_calc(c, x, y, problem, ope, ans=False, a=False):
c.setLineWidth(1.5)
#答えあり
if ans is True:
x_offset = 10
if ope == "÷" and a is True:
c.drawString((x+x_offset)*mm, y*mm, f'{problem["N"]} {ope} {problem["M"]} = {problem["ans"]["sho"]}...{problem["ans"]["amari"]}')
else:
c.drawString((x+x_offset)*mm, y*mm, f'{problem["N"]} {ope} {problem["M"]} = {problem["ans"]["value"]}')
# 答えなし
else:
if H is False:
x_offset = 10
c.drawString((x+x_offset)*mm, y*mm, f'{problem["N"]} {ope} {problem["M"]} =')
# 1ページ分の計算式を生成する関数
# aはあまりあるかどうか
# 答えなしとありをセットで出力します。
def make_page(c, x_list, y_list, list_of_problems, ope, a=False):
# 答えなし
# フォントを指定することも可能です
fontname = "Helvetica"
c.setFont(fontname,30)
# ヘッダー部分の文字列出力
c.drawString(10,800, " Day: / Score: / Time: ")
# Set Value
n = 0
for row in y_list:
for col in x_list:
n += 1
Set_calc(c,col,row, list_of_problems[n-1], ope, ans=False, a=a)
c.showPage()
# 答えあり
c.drawString(10,800, " Day: / Score: / Time: ")
# Set Value
n = 0
for row in y_list:
for col in x_list:
n += 1
Set_calc(c,col,row, list_of_problems[n-1], ope, ans=True, a=a)
c.showPage()
def pdf_export(list_of_paper, x_list, y_list, ope, a=False):
# pathに注意
# 親ディレクトリにpdfというディレクトリを用意してください。
c = canvas.Canvas(f"{os.path.dirname(os.path.dirname(os.path.dirname(__file__)))}/pdf/calc_train_{datetime.now().strftime('%Y%m%d%H%M%S')}.pdf",pagesize=A4)
for l in list_of_paper:
make_page(c, x_list, y_list, l, ope, a)
c.save()
足し算
入力として
N, M, kが必要ですのでそれを取得できるようなTkinterを作成しました。
コードが長いので隠しておきます。
足し算ドリル作成のコード
import random
import re
import tkinter as tk
from tkinter import ttk
import os, sys
from parts.parts import generate_addition, pdf_export
# 値を取得するTkinter
def get_value():
root = tk.Tk()
root.title("足し算")
root.geometry("600x600")
v1, v2, v3, v4 = None, None, None, None
# ウィンドウを閉じた際にPythonを終了するように誘導するためにFalseを返す
def close_window():
root.quit()
return False
# ウィンドウを閉じるイベントの設定
root.protocol("WM_DELETE_WINDOW", close_window)
#桁数の選択肢 とりあえず5桁までにしていますが変更可能
options = [1, 2, 3, 4, 5]
# 一項目
label1=tk.Label(root,text="一項目の桁数:")
label1.pack(padx=10, pady=10)
value1 = ttk.Combobox(root, values=options, state="readonly")
value1.pack(padx=10)
# 二項目
label2=tk.Label(root,text="二項目の桁数:")
label2.pack(padx=10, pady=10)
value2 = ttk.Combobox(root, values=options, state="readonly")
value2.pack(padx=10)
# 繰り上がり
value3 = tk.BooleanVar()
rb1 = ttk.Radiobutton(root, value=True,variable=value3, text="繰り上がりあり")
rb2 = ttk.Radiobutton(root, value=False,variable=value3, text="繰り上がりなし")
rb1.pack(padx=10, pady=10)
rb2.pack()
# 枚数
def invalidText():
print('半角数字を入力してください。')
# 1. 入力制限の条件を設けて検証する関数の名前を決める
# 4. 入力制限の条件を設けて検証する関数を実装する
def onValidate(S):
# 入力された文字が半角数字の場合
if re.match(re.compile('[0-9]+'), S):
return True
else:
# 入力不正のブザーを鳴らす。
root.bell()
return False
# 2. 1で決めた関数名を、register関数を用いて登録する
# register : 入力制限を行うための関数の登録を行う。パラメータと関数を紐づけるために必要。
vcmd = root.register(onValidate)
# validate : 入力制限するオプションの値を設定。
# validatecommand or vcmd : 入力制限用関数の設定。(3. entryのvalidatecommand option or vcmd optionへ、2の戻り値とパラメータを渡す)
# invalidcommand : 入力制限により、入力不正が発生した場合に呼ばれる関数の設定。
label4=tk.Label(root,text="プリントの枚数:")
label4.pack(padx=10, pady=10)
value4 = tk.Entry(root, width=10, validate="key", validatecommand=(vcmd, '%S'), invalidcommand=invalidText)
value4.pack(padx=10, pady=10)
# OKボタン押すと値を返す
def ok_get(event):
nonlocal v1, v2, v3, v4
v1, v2, v3, v4 = value1.get(), value2.get(), value3.get(), value4.get()
root.quit()
#ボタン
button1 = tk.Button(text='OK')
button1.bind("<Button-1>", ok_get)
button1.pack(padx=10, pady=20)
# Enter でも OK
root.bind('<Return>', ok_get)
root.mainloop()
try:
return [v1, v2, v3, v4]
except:
return False
def get_list_problems(d_N, d_M, k, n):
list_of_problems = []
while len(list_of_problems) < n:
list_of_problems.append(generate_addition(d_N, d_M, k))
return list_of_problems
def main():
n = 20 # 1枚の問題数
# 計算式の基本座標
# 2 * 10
# A4 210 297
# 位置のリスト
x_list = list(range(0, 210, 105))
y_list = list(range(260, -10, -27))
if n != len(x_list) * len(y_list):
return False
lst = get_value()
if lst is False:
return None
list_of_paper = []
for i in range(int(lst[3])):
list_of_paper.append(get_list_problems(int(lst[0]), int(lst[1]), lst[2], n))
# ope is "+"
pdf_export(list_of_paper, x_list, y_list, "+")
if __name__ == "__main__":
main()
引き算
入力として
N, M, kが必要ですのでそれを取得できるようなTkinterを作成しました。
コードが長いので隠しておきます。
引き算ドリル作成のコード
import random
import re
import tkinter as tk
from tkinter import ttk
import os, sys
from parts import generate_subtraction, pdf_export
# 値を取得するTkinter
def get_value():
root = tk.Tk()
root.title("引き算")
root.geometry("600x600")
v1, v2, v3, v4 = None, None, None, None
# ウィンドウを閉じた際にPythonを終了するように誘導するためにFalseを返す
def close_window():
root.quit()
return False
# ウィンドウを閉じるイベントの設定
root.protocol("WM_DELETE_WINDOW", close_window)
#pull down
options = [1, 2, 3, 4, 5]
options2 = {"1": [1], "2": [1, 2], "3": [1, 2, 3], "4":[1, 2, 3, 4], "5":[1, 2, 3, 4, 5]}
# コールバック関数
def update_combo2(event):
selected = value1.get()
value2['values'] = options2[str(selected)]
#combo2.set('') # 初期値をリセット
# 一項目
label1=tk.Label(root,text="一項目の桁数:")
label1.pack(padx=10, pady=10)
value1 = ttk.Combobox(root, values=options, state="readonly")
value1.pack(padx=10)
value1.bind("<<ComboboxSelected>>", update_combo2)
# 二項目
label2=tk.Label(root,text="二項目の桁数:")
label2.pack(padx=10, pady=10)
value2 = ttk.Combobox(root, values=options2, state="readonly")
value2.pack(padx=10)
# 繰り下がり
value3 = tk.BooleanVar()
rb1 = ttk.Radiobutton(root, value=True,variable=value3, text="繰り下がりあり")
rb2 = ttk.Radiobutton(root, value=False,variable=value3, text="繰り下がりなし")
rb1.pack(padx=10, pady=10)
rb2.pack()
# 枚数
def invalidText():
print('半角数字を入力してください。')
# 1. 入力制限の条件を設けて検証する関数の名前を決める
# 4. 入力制限の条件を設けて検証する関数を実装する
def onValidate(S):
# 入力された文字が半角数字の場合
if re.match(re.compile('[0-9]+'), S):
return True
else:
# 入力不正のブザーを鳴らす。
root.bell()
return False
# 2. 1で決めた関数名を、register関数を用いて登録する
# register : 入力制限を行うための関数の登録を行う。パラメータと関数を紐づけるために必要。
vcmd = root.register(onValidate)
# validate : 入力制限するオプションの値を設定。
# validatecommand or vcmd : 入力制限用関数の設定。(3. entryのvalidatecommand option or vcmd optionへ、2の戻り値とパラメータを渡す)
# invalidcommand : 入力制限により、入力不正が発生した場合に呼ばれる関数の設定。
label4=tk.Label(root,text="プリントの枚数:")
label4.pack(padx=10, pady=10)
value4 = tk.Entry(root, width=10, validate="key", validatecommand=(vcmd, '%S'), invalidcommand=invalidText)
value4.pack(padx=10, pady=10)
# OKボタン押すと値を返す
def ok_get(event):
nonlocal v1, v2, v3, v4
v1, v2, v3, v4 = value1.get(), value2.get(), value3.get(), value4.get()
root.quit()
#ボタン
button1 = tk.Button(text='OK')
button1.bind("<Button-1>", ok_get)
button1.pack(padx=10, pady=20)
# Enter でも OK
root.bind('<Return>', ok_get)
root.mainloop()
try:
return [v1, v2, v3, v4]
except:
return False
def get_list_problems(d_N, d_M, k, n):
list_of_problems = []
while len(list_of_problems) < n:
list_of_problems.append(generate_subtraction(d_N, d_M, k))
return list_of_problems
def main():
n = 20 # 1枚の問題数
# 計算式の基本座標
# 2 * 10
# A4 210 297
x_list = list(range(0, 210, 105))
y_list = list(range(260, -10, -27))
if n != len(x_list) * len(y_list):
return False
lst = get_value()
if lst is False:
return None
list_of_paper = []
for i in range(int(lst[3])):
list_of_paper.append(get_list_problems(int(lst[0]), int(lst[1]), lst[2], n))
# ope is "-"
pdf_export(list_of_paper, x_list, y_list, "-")
if __name__ == "__main__":
main()
コンボボックスの選択肢を動的に変更するのは
この記事を参考にしました
https://zenn.dev/nuinui/articles/7e7588fe49db40
掛け算
コードが長いので隠しておきます。
掛け算ドリル作成のコード
import random
import re
import tkinter as tk
from tkinter import ttk
import os, sys
from parts import generate_multiplication, pdf_export
# 値を取得するTkinter
def get_value():
root = tk.Tk()
root.title("掛け算")
root.geometry("600x600")
v1, v2, v3 = None, None, None
# ウィンドウを閉じた際にPythonを終了するように誘導するためにFalseを返す
def close_window():
root.quit()
return False
# ウィンドウを閉じるイベントの設定
root.protocol("WM_DELETE_WINDOW", close_window)
#pull down
options = [1, 2, 3, 4, 5]
options2 = [1, 2, 3, 4, 5]
# 一項目
label1=tk.Label(root,text="一項目の桁数:")
label1.pack(padx=10, pady=10)
value1 = ttk.Combobox(root, values=options, state="readonly")
value1.pack(padx=10)
# 二項目
label2=tk.Label(root,text="二項目の桁数:")
label2.pack(padx=10, pady=10)
value2 = ttk.Combobox(root, values=options2, state="readonly")
value2.pack(padx=10)
# 枚数
def invalidText():
print('半角数字を入力してください。')
# 1. 入力制限の条件を設けて検証する関数の名前を決める
# 4. 入力制限の条件を設けて検証する関数を実装する
def onValidate(S):
# 入力された文字が半角数字の場合
if re.match(re.compile('[0-9]+'), S):
return True
else:
# 入力不正のブザーを鳴らす。
root.bell()
return False
# 2. 1で決めた関数名を、register関数を用いて登録する
# register : 入力制限を行うための関数の登録を行う。パラメータと関数を紐づけるために必要。
vcmd = root.register(onValidate)
# validate : 入力制限するオプションの値を設定。
# validatecommand or vcmd : 入力制限用関数の設定。(3. entryのvalidatecommand option or vcmd optionへ、2の戻り値とパラメータを渡す)
# invalidcommand : 入力制限により、入力不正が発生した場合に呼ばれる関数の設定。
label3=tk.Label(root,text="プリントの枚数:")
label3.pack(padx=10, pady=10)
value3 = tk.Entry(root, width=10, validate="key", validatecommand=(vcmd, '%S'), invalidcommand=invalidText)
value3.pack(padx=10, pady=10)
# OKボタン押すと値を返す
def ok_get(event):
nonlocal v1, v2, v3
v1, v2, v3 = value1.get(), value2.get(), value3.get()
root.quit()
#ボタン
button1 = tk.Button(text='OK')
button1.bind("<Button-1>", ok_get)
button1.pack(padx=10, pady=20)
# Enter でも OK
root.bind('<Return>', ok_get)
root.mainloop()
try:
return [v1, v2, v3]
except:
return False
def get_list_problems(d_N, d_M, n):
list_of_problems = []
while len(list_of_problems) < n:
list_of_problems.append(generate_multiplication(d_N, d_M))
return list_of_problems
def main():
n = 20 # 1枚の問題数
# 計算式の基本座標
# 2 * 10
# A4 210 297
x_list = list(range(0, 210, 105))
y_list = list(range(260, -10, -27))
if n != len(x_list) * len(y_list):
return False
lst = get_value()
if lst is False:
return None
list_of_paper = []
for i in range(int(lst[2])):
# 掛け算割り算ではkはなし
list_of_paper.append(get_list_problems(int(lst[0]), int(lst[1]), n))
# ope is "×"
pdf_export(list_of_paper, x_list, y_list, "×")
if __name__ == "__main__":
main()
割り算
コードが長いので隠しておきます。
割り算ドリル作成のコード
import random
import re
import tkinter as tk
from tkinter import ttk
import os, sys
from parts import generate_division, pdf_export
def get_value():
root = tk.Tk()
root.title("割り算")
root.geometry("600x600")
v1, v2, v3, v4, v5 = None, None, None, None, None
# ウィンドウを閉じた際にPythonを終了するように誘導するためにFalseを返す
def close_window():
root.quit()
return False
# ウィンドウを閉じるイベントの設定
root.protocol("WM_DELETE_WINDOW", close_window)
#pull down
options = [3, 4, 5]
options2 = {"3": [1, 2], "4":[1, 2, 3], "5":[1, 2, 3, 4]}
# コールバック関数
def update_combo2(event):
selected = value1.get()
value2['values'] = options2[str(selected)]
#combo2.set('') # 初期値をリセット
# 一項目
label1=tk.Label(root,text="一項目の桁数:")
label1.pack(padx=10, pady=10)
value1 = ttk.Combobox(root, values=options, state="readonly")
value1.pack(padx=10)
value1.bind("<<ComboboxSelected>>", update_combo2)
# 二項目
label2=tk.Label(root,text="二項目の桁数:")
label2.pack(padx=10, pady=10)
value2 = ttk.Combobox(root, values=options2, state="readonly")
value2.pack(padx=10)
# N ÷ N, N ÷ 1を含むかどうか
value3 = tk.BooleanVar()
rb1 = ttk.Radiobutton(root, value=True,variable=value3, text="n ÷ n や n ÷ 1 を含む")
rb2 = ttk.Radiobutton(root, value=False,variable=value3, text="含まない")
rb1.pack(padx=10, pady=10)
rb2.pack()
# 余りあるかどうか
value4 = tk.BooleanVar()
rb3 = ttk.Radiobutton(root, value=True,variable=value4, text="余りあり")
rb4 = ttk.Radiobutton(root, value=False,variable=value4, text="余りなし")
rb3.pack(padx=10, pady=10)
rb4.pack()
# 枚数
def invalidText():
print('半角数字を入力してください。')
# 1. 入力制限の条件を設けて検証する関数の名前を決める
# 4. 入力制限の条件を設けて検証する関数を実装する
def onValidate(S):
# 入力された文字が半角数字の場合
if re.match(re.compile('[0-9]+'), S):
return True
else:
# 入力不正のブザーを鳴らす。
root.bell()
return False
# 2. 1で決めた関数名を、register関数を用いて登録する
# register : 入力制限を行うための関数の登録を行う。パラメータと関数を紐づけるために必要。
vcmd = root.register(onValidate)
# validate : 入力制限するオプションの値を設定。
# validatecommand or vcmd : 入力制限用関数の設定。(3. entryのvalidatecommand option or vcmd optionへ、2の戻り値とパラメータを渡す)
# invalidcommand : 入力制限により、入力不正が発生した場合に呼ばれる関数の設定。
label5=tk.Label(root,text="プリントの枚数:")
label5.pack(padx=10, pady=10)
value5 = tk.Entry(root, width=10, validate="key", validatecommand=(vcmd, '%S'), invalidcommand=invalidText)
value5.pack(padx=10, pady=10)
# OKボタン押すと値を返す
def ok_get(event):
nonlocal v1, v2, v3, v4, v5
v1, v2, v3, v4, v5 = value1.get(), value2.get(), value3.get(), value4.get(), value5.get()
root.quit()
#ボタン
button1 = tk.Button(text='OK')
button1.bind("<Button-1>", ok_get)
button1.pack(padx=10, pady=20)
# Enter でも OK
root.bind('<Return>', ok_get)
root.mainloop()
try:
return [v1, v2, v3, v4, v5]
except:
return False
def get_list_problems(d_N, d_M, s, a, n):
list_of_problems = []
while len(list_of_problems) < n:
list_of_problems.append(generate_division(d_N, d_M, s, a))
return list_of_problems
def main():
n = 20 # 1枚の問題数
# 計算式の基本座標
# 2 * 10
# A4 210 297
x_list = list(range(0, 210, 105))
y_list = list(range(260, -10, -27))
if n != len(x_list) * len(y_list):
return False
lst = get_value()
if lst is False:
return None
list_of_paper = []
for i in range(int(lst[4])):
list_of_paper.append(get_list_problems(int(lst[0]), int(lst[1]), lst[2], lst[3], n))
# ope is "÷", a is ,,,
pdf_export(list_of_paper, x_list, y_list, "÷", lst[3])
if __name__ == "__main__":
main()
おわり
以上!!!
それぞれのpy fileを起動すればTkinterが起動されて値を入力すればPDFファイルがバシバシ出力されます!!!
最高ですね!!!