乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

Python

今回は最近注目度が上がってきている
簡単UI作成ツールStreamlitのお話です




解説動画はこちら




Streamlitについて


1. Streamlitとは?

一言で言うと…

Pythonコードだけでインタラクティブな
Webアプリを作れるツールです


何がすごいの?

以下のような特徴があります。


HTMLやJavaScriptの知識がほぼ不要で
めちゃくちゃ簡単にGUIが作れる



開発のホットリロード(即時反映)が可能で
主にデータ可視化や機械学習モデルのデモ
ダッシュボード作成に使われる

StreamlitはSnowflake傘下となり
クラウドデータ活用との親和性がより高くなっている
(Snowflakeはフルマネージドなデータウェアハウスサービス)


2. Streamlitの特徴

こんな特徴があります。

PythonだけでOK
WebフレームワークやHTML/CSS/JSを知らなくても作れる


インタラクティブUI
スライダー、チェックボックス
セレクトボックスなど簡単に追加できる


ライブ更新
コードを保存するだけで即座にアプリが更新される


グラフとの相性
matplotlib, plotly, seaborn, altairなどと簡単に連携


デプロイも簡単
streamlit.ioや他のクラウドでも簡単に公開できる


Snowflake統合
Snowflake上でStreamlitアプリをホスティング可能

最近ではSnowflake上で簡単に
Streamlitアプリを作って公開できるので
めちゃくちゃ便利で捗ります!!!!!!

これだけでも使う価値アリかなと思っています。





3. 基本的な使い方

インストール方法

ローカルで使用したい場合は以下のコマンドで
インストールできます。
pip install streamlit


インストールが終わったら
こんな簡単なコードを書いて
app.py で保存します。
import streamlit as st

st.title("こんにちは Streamlit!")
st.write("これは最初のアプリです")
x = st.slider("数字を選んでください", 0, 100, 25)
st.write("選んだ数字は", x)

アプリを起動するには
streamlit run app.py
で起動できます。
(ファイルが有るディレクトリで実行)

実行するとブラウザーが立ち上がり
そこにアプリが表示されます。

スクリーンショット 2025-04-19 15.42.00



4. 使えるウィジェットの例


ウィジェットの紹介
次のようなものが使えます。

    st.button()    ボタン    
    st.slider()    スライダー    
    st.selectbox()    プルダウン    
    st.checkbox()    チェックボックス    
    st.text_input()    テキスト入力    
    st.file_uploader()    ファイルアップロード    
    st.image()    画像表示    
    st.map()    地図表示(pandasの緯度・経度データ)    

以下のコードを書いて実行すると
サンプル的に試すことができます。

import streamlit as st
import pandas as pd
import numpy as np
from PIL import Image

st.title("Streamlit ウィジェットの紹介")

st.header("1. st.button() - ボタン")
if st.button("クリックしてね"):
    st.write("ボタンが押されました!")

st.header("2. st.slider() - スライダー")
slider_val = st.slider("数値を選んでください", 0, 100, 25)
st.write(f"現在の値: {slider_val}")

st.header("3. st.selectbox() - プルダウンメニュー")
option = st.selectbox("好きな果物を選んでください", ["りんご", "バナナ", "ぶどう"])
st.write(f"選択されたのは: {option}")

st.header("4. st.checkbox() - チェックボックス")
agree = st.checkbox("私は利用規約に同意します")
if agree:
    st.write("同意ありがとうございます!")

st.header("5. st.text_input() - テキスト入力")
name = st.text_input("あなたの名前は?")
if name:
    st.write(f"こんにちは、{name}さん!")

st.header("6. st.file_uploader() - ファイルアップロード")
uploaded_file = st.file_uploader("画像をアップロードしてください", type=["jpg", "png"])
if uploaded_file is not None:
    image = Image.open(uploaded_file)
    st.image(image, caption="アップロードされた画像", width=300)

st.header("7. st.image() - 画像表示(サンプル画像)")
st.image("https://cdn.pixabay.com/photo/2024/11/30/15/55/eiffel-tower-9235220_1280.jpg", caption="タワー", width=100)

st.header("8. st.map() - 地図表示")
st.write("ランダムな緯度・経度データを地図に表示します")
df_map = pd.DataFrame(
    np.random.randn(100, 2) / [50, 50] + [35.69, 139.70],  # 東京周辺
    columns=['lat', 'lon']
)
st.map(df_map)

st.markdown("---")
st.write("以上、Streamlitの基本ウィジェットの紹介でした ")


5. どんな人におすすめ?


以下のような悩みを持っている人ならオススメです!!

PythonでGUIを作りたい人
データ可視化を誰かにシェアしたい人
機械学習モデルを他人に使わせたい人
Snowflakeを使おうとしている人
簡単な社内ツールを作りたい人


6. よくある使い道

こんな使われ方が多いです。

データの可視化アプリ
機械学習モデルのデモ用UI
社内ツールや分析ダッシュボード
ちょっとしたプロトタイプ作成


7.社内ツールデモ

デモは動画をご覧ください。


今回は
最近のStreamlitの使われかた等について
解説させていただきました。

ユーザーが増えると
すんごく捗ると思うので
これからも大注目です

今日はここまでです
それでは。




今回は2025年4月現在で
フリーランスのPythonエンジニアが
どれくらい稼げるかを調査してみました。


解説動画はこちら


 
Pythonエンジニアは稼げるのか


フリーランスになって3年目
Python関連のお仕事してますが

世間的にフリーランスの
Pythonエンジニアはどれくらい稼げるのかを
調査してみました。

フリーランスの案件紹介サイトから
700件ほどを抽出して
サマリーを出してみました。

案件の最頻値、一番多い件数の月単価は
65万円

最大では200万円でしたが
流石にこれは一般人では難しそうです。
download

だいたい単価はこんな感じの分布になります。

案件のスキルや内容をワードクラウドにすると
こんな感じでした。

download-1


WEB系の開発に加えて
データ分析やAI系のツール開発や業務支援アプリ開発
みたいなのが増えていますかね

データ分析基盤の開発なんかも
増えている印象でした。




まとめ

月額65万円あたりが一番多いとなると
年収ベースで780万円前後が関の山

100万円を超えるような単価の高額案件は
経験スキルとしてクラウド開発や複数言語の開発経験が必須

AI開発系は当然、AI開発の経験とデータ周りの
知識が必要になってきていました。

単純なPython開発のみだと
少し夢が無いですよねー

簡単なWEB系の開発単価は下がり
AI系の単価はやや高め

だんだんと二極化してきている感じもあります。

これが2025年4月現在の
Python案件の内容でした。

頑張ってもっと稼げるように
スキルアップしていこうと思う次第でした

今日はこれまでです
それでは。


今回は疑似乱数の生成方法についてです


解説動画はこちら




乱数と疑似乱数

今回は疑似乱数についてのお話です

一般的に乱数というものは
予測できない数値のことで
原子核の放射崩壊により放出された放射線のカウント
のように予測が出来ない数値のことです

一方で疑似乱数とは
コンピューターで生成した一定の規則に基づく
乱数のような値のことで

真の乱数列は本来、規則性も再現性もないものであるため
本来は確定的な計算によって求めることはできないです

擬似乱数は計算によって作るので
生成法と内部状態が判れば予測も可能ではあります。

また過去に現れた数と同じ数が現れた際のその長さを
周期と言っています。



疑似乱数の作成手法


一般的には初期値(シード)と
演算法(アルゴリズム)を用いて生成します。



平方採中法 (middle-square method)

1946年頃、数学者ノイマンによって提唱された手法です。

この方法は
適当に初期値を決める 例 : 1234
その数を 2 乗した値の中央にある必要な桁数を採って
次の乱数とする

これを繰り返して乱数列とする方法です。

Pythonでの実装例はこんな感じになります。

# ノイマンの平方採中法による乱数生成
def middle_square_method(seed, num_samples=10, num_digits=4):
    random_numbers = []
    current_value = seed
    for _ in range(num_samples):
        num_digits = len(str(current_value))
        # 2乗して中央の桁を取り出す
        squared_value = current_value ** 2
        squared_str = str(squared_value).zfill(2 * num_digits)
        # 中央の桁を取り出す
        start = len(squared_str) - num_digits - 2
        middle_digits = squared_str[start: start + 4]
        current_value = int(middle_digits)
        random_numbers.append(current_value)
    return random_numbers

# 初期値とパラメータ設定
seed = 1234  # 初期値(任意の4桁の数)
random_nums = middle_square_method(seed)
print(random_nums)



線形合同法 (linear congruential method)

次のような漸化式による生成方法です。

スクリーンショット 2025-04-05 17.35.45

周期の最大は m で
パラメータ次第では周期が短くなり
予想ができてしまう手法です。


class LCG:
    def __init__(self, seed=1, a=1664525, c=1013904223, m=2**32):
        self.a = a
        self.c = c
        self.m = m
        self.state = seed

    def next(self):
        self.state = (self.a * self.state + self.c) % self.m
        return self.state / self.m

# 使用例
seed = 1320
lcg = LCG(seed=seed)
for _ in range(10):
    print(lcg.next())
0.7476371766533703
0.0075369239784777164



Xorshift

George Marsagliaが2003年に提案した手法です

ビット演算を用いた高速な擬似乱数生成法で
基本的な計算ステップは次の3つです。
state ^= (state << a)
state ^= (state >> b)
state ^= (state << c)
a, b, c は定数(一般的には a=13, b=17, c=5)
^= はビット単位の XOR を行って
結果を state に代入です。
class XorShift32:
    def __init__(self, seed=2463534242):
        self.state = seed

    def next(self):
        x = self.state
        x ^= (x << 13) & 0xFFFFFFFF
        x ^= (x >> 17)
        x ^= (x << 5) & 0xFFFFFFFF
        self.state = x & 0xFFFFFFFF
        return self.state / 0xFFFFFFFF

# 使用例
xorshift = XorShift32(seed=123456789)
for _ in range(5):
    print(xorshift.next())
0.6321277193799912
0.5212643641329521


メルセンヌ・ツイスタ (Mersenne Twister : MT)

1996年に松本眞と西村拓士によって
国際会議で発表された手法です。

名前の由来は
周期がメルセンヌ素数(2^19937 - 1)であることからだそうです

•出力:32ビットの整数
•長所:
•非常に長い周期(2^19937 - 1)
•高次元(623次元)でも均一な分布を持つ
•高速(特にビット演算が中心)

以下の3ステップで計算されています。
1.初期化(シードによる状態配列の構築)
2.状態配列の更新(Twist)
3.出力整形(Tempering)


元々Pythonのrandomなどの実装は
この手法を基にしているそうなので
実装する必要は無いのですが
中身の実装を真似るとこんな感じのコードになります
class MT19937:
    # 初期化(シードによる状態配列の構築)
    def __init__(self, seed):
        self.w, self.n, self.m, self.r = 32, 624, 397, 31
        self.a = 0x9908B0DF
        self.u, self.d = 11, 0xFFFFFFFF
        self.s, self.b = 7, 0x9D2C5680
        self.t, self.c = 15, 0xEFC60000
        self.l = 18
        self.f = 1812433253

        self.MT = [0] * self.n
        self.index = self.n
        self.lower_mask = (1 << self.r) - 1
        self.upper_mask = (~self.lower_mask) & 0xFFFFFFFF

        self.MT[0] = seed
        for i in range(1, self.n):
            self.MT[i] = (self.f * (self.MT[i - 1] ^ (self.MT[i - 1] >> (self.w - 2))) + i) & 0xFFFFFFFF

    # 状態配列の更新(Twist)
    # 複数のビットを結合して新しい状態を作り出す処理
    def twist(self):
        for i in range(self.n):
            x = (self.MT[i] & self.upper_mask) + (self.MT[(i + 1) % self.n] & self.lower_mask)
            xA = x >> 1
            if x % 2 != 0:
                xA ^= self.a
            self.MT[i] = self.MT[(i + self.m) % self.n] ^ xA
        self.index = 0

    # 出力整形(Tempering)
    # 出力する前にビットをシャッフルして統計的性質を良くする
    def extract_number(self):
        if self.index >= self.n:
            self.twist()

        y = self.MT[self.index]
        y ^= (y >> self.u) & self.d
        y ^= (y << self.s) & self.b
        y ^= (y << self.t) & self.c
        y ^= (y >> self.l)
        self.index += 1
        return y & 0xFFFFFFFF

    def random(self):
        return self.extract_number() / 0xFFFFFFFF


まとめ

コンピューターでは真の乱数を生成することは
非常に困難ですが乱数に近い値を計算で求める事は出来ます

しかし乱数生成アルゴリズムが分かってしまった場合には
リスクが発生することがあります。


システム内で使用される乱数
(セッションID、認証トークン、パスワードのハッシュなど)が
予測されて攻撃されたり

暗号化や認証に使用される乱数
(例:暗号鍵生成やトークンの生成)が
予測可能だと、暗号が破られやすくなるでしょう。

またゲームやギャンブルサイトでの
不正行為にも使われてしまった例があったようです。

まあ、疑似乱数を1から生成するコードを実装する機会は
ほとんど無いかもしれませんが、アルゴリズムの採択によっては
それがシステム上のリスクになりえる事は
考えておいても良いんじゃ無いでしょうか

今回は疑似乱数についてのお話でした。
それでは。


今回は直感に反する確率の逆説の問題集です


解説動画はこちら



直感に反する確率の問題

なんとなくコレかなと思ったけど
答えはそれと違っていたというような
直感に反する答えが出そうな問題です。


問題とともに
回答に使用したコードを載せておきます。



1.確率の逆説


第1問

サイコロを2回振るとき 少なくとも1回は1が出る確率は
1. 3分の1より小さい
2. ちょうど3分の1(33.3%)
3. 3分の1より大きい

# サイコロを2回振るとき 少なくとも1回は1が出る確率
import random

num = 1000000
count = 0
for _ in range(num):
    dices = [random.choice([1,2,3,4,5,6]),random.choice([1,2,3,4,5,6])]
    if 1 in dices:
        count+=1

print(f"少なくとも1が1回はでた回数 : {count}")
print(f"{count * 100 / num}%")



第2問

学校のクラス30人の生徒の中に誕生日が同じ人がいる確率は
1. 約30%
2. 約50%
3. 約70%
4. 約90%


# n人の生徒の中に誕生日が同じ人がいる確率
import random
from collections import Counter

birthdays = list(range(365))
num = 30
cnt, all_cnt = 0,0
for i in range(1000000):
    all_cnt+=1
    tmp = random.choices(birthdays,k=num)
    c = Counter(tmp)
    mx = c.most_common(1)
    if mx[0][1]>1:
        cnt+=1
print(f"誕生日が同じ人がいた回数 : {cnt}")
print(f"誕生日が同じ人がいる確率 : {cnt * 100 /all_cnt}%")




2.ギャンブラーの誤謬

過去の結果が現在の結果に影響すると誤って考える心理


第3問

コインを4回投げてすべて表が出た場合、次の投げで裏が出る確率は
1. 50%より小さい
2. ちょうど50%
3. 50%より大きい


# コインを投げて4回表が出た後の出目の確率
import random

def simulate_coin_tosses(trials):
    result = {'表':0,'裏':0}
    coins = [random.choice(['表', '裏']) for _ in range(trials)]
    for i in range(0, trials-5):
        if coins[i:i+4] == ['表', '表', '表', '表']:
            next = coins[i+4]
            result[next]+=1
    return result['表'], result['裏']

# 試行回数
k = 1000000
total_heads, total_tails = simulate_coin_tosses(k)

print(f"試行回数: {k}")
print(f"表の合計回数: {total_heads}")
print(f"裏の合計回数: {total_tails}")
print(f"4回表が出た後の裏が出る確率は : {total_tails * 100 / (total_heads + total_tails):.6}%")




第4問

コインを1000回投げた時に、連続して表が出る最高回数は
1. 8回より少ない
2. 8-10回くらい
3. 10回より多い


# コインをnum回投げて、連続して表が出る最高回数
import random

def max_consecutive_heads(trials):
    coins = [random.choice(['表', '裏']) for _ in range(trials)]
    #print(coins)
    #print(f"表の数 : {coins.count('表')}")
    max_count, current_count = 0, 0
    for coin in coins:
        if coin == '表':
            current_count += 1
            max_count = max(max_count, current_count)
        else:
            current_count = 0
    return max_count

# 試行回数
num = 1000
max_heads = [max_consecutive_heads(num) for _ in range(1000)]
mean_heads = sum(max_heads) / len(max_heads)
print(f"試行回数: {num}")
print(f"連続して出現した表の最高回数: {mean_heads}")



答えは
シミュレーションプログラムを実行するか
動画をご覧ください。




そんなに確率高くないだろうと思っていたら
意外と高い確率だったり
意外と低かったり

そんなこともあるんじゃないかと思います。

プログラムを使えば
色々シミュレーションして
おおよその確率を求めることができるので

さまざまな事象の確率を求めたいなら
プログラミングを習得するのが
おすすめです。


今回はここまでです
それでは。

 

今回はZIPとかPDFのパスワードを
総当たりで開けに行くやつのご紹介です

解説動画はこちら




ファイルにパスワード掛かっていて開けられないよーーー
どうにかならない?

そんな時ありますよね!!!!

そんな時はプログラムで突破すればいい

今回は
ZIPやPDFのパスワードを破るプログラム
についての解説です。


 
ライブラリのインストール

Google Colab上で
ZIPやPDFを扱うのに必要な
ライブラリのインストールです。
pip install pyzipper
pip install pypdf
pip install reportlab



パスワード付きファイルの用意

パスワード付きのZIP , PDFファイルを
作成しておきます。

今回のパスワードは
a12
として
PDFファイルや
ZIP用のファイルを先に作っておきます。

lock_password = "a12"

from reportlab.pdfgen import canvas
from PIL import Image

# PDFを作成
pdf_filename = "original.pdf"
c = canvas.Canvas(pdf_filename)
c.drawString(100, 750, "Hello, this is a sample PDF file.")
c.drawString(100, 730, "This PDF was created using Python and reportlab.")
c.save()

# ZIP用の内包ファイルを用意
with open("secret.txt","w") as _w:
    _w.write("This is a secret message")

Image.new("RGB", (100, 100), (255, 255, 255)).save("image.png")


パスワード付きのZIPファイルを作るコード
import pyzipper

# パスワード付きzipファイルを作成する
def create_protected_zip(file_list, zip_name, password):
    with pyzipper.AESZipFile(zip_name, 'w', compression=pyzipper.ZIP_DEFLATED, encryption=pyzipper.WZ_AES) as zf:
        zf.setpassword(password.encode("utf-8"))
        for file in file_list:
            zf.write(file)
    print(f"[+] パスワード付きZIPを作成: {zip_name}")

create_protected_zip(["secret.txt", "image.png"], "protected.zip", lock_password)


PDFにパスワードをかけるコード
from pypdf import PdfReader, PdfWriter

# パスワード付きpdfファイルを作成する
def create_protected_pdf(input_pdf, output_pdf, password):
    reader = PdfReader(input_pdf)
    writer = PdfWriter()
    
    for page in reader.pages:
        writer.add_page(page)

    writer.encrypt(password)  # パスワードを設定
    with open(output_pdf, "wb") as f:
        writer.write(f)
    print(f"[+] パスワード付きPDFを作成: {output_pdf}")

create_protected_pdf("original.pdf", "protected.pdf", lock_password)




ZIPとPDFファイルのパスワードを破るコード


ZIPとPDFに対応
総当たりでパスワードを当てにいくものです
一応辞書にも対応しています。
import itertools
import string
import zipfile
import pyzipper
from pypdf import PdfReader

def extract_zip(file_path, password):
    """ ZIPファイルの解凍 (ZIPCrypto & AES-256対応) """
    password_bytes = password.encode("utf-8")  # バイト列に変換
    try:
        with zipfile.ZipFile(file_path) as zf:
            zf.extractall(pwd=password_bytes)
        print(f"[+] ZIP のパスワード発見: {password}")
        return True
    except (RuntimeError, NotImplementedError):
        pass

    try:
        with pyzipper.AESZipFile(file_path) as zf:
            zf.setpassword(password_bytes)
            zf.extractall()
        print(f"[+] PDF のパスワード発見: {password}")
        return True
    except Exception as e:
        return False

def try_password(file_path, file_type, password):
    """ ZIP または PDF のパスワードを試す """
    try:
        if file_type == "zip":
            return extract_zip(file_path, password)
        elif file_type == "pdf":
            reader = PdfReader(file_path)
            if reader.decrypt(password) == 0:
                raise ValueError("Incorrect password")
            print(f"[+] PDF のパスワード発見: {password}")
            return True
    except Exception:
        return False

def password_cracker(file_path, mode="brute", password_list=None, max_length=4):
    """ ZIP/PDFファイルのパスワードを解析 """
    if file_path.endswith(".zip"):
        file_type = "zip"
    elif file_path.endswith(".pdf"):
        file_type = "pdf"
    else:
        print("[-] サポートされていないファイル形式です")
        return

    print(f"[*] {file_path} のパスワード解析開始 (モード: {mode})")

    count = 0

    # **辞書攻撃**
    if mode == "dictionary" and password_list:
        with open(password_list, "r", encoding="utf-8") as file:
            for password in file:
                count += 1
                if try_password(file_path, file_type, password.strip()):
                    print(f"[+] 試行回数: {count}")
                    return

    # **総当たり攻撃**
    elif mode == "brute":
        characters = string.ascii_lowercase + string.digits  # "abcdefghijklmnopqrstuvwxyz0123456789"
        for length in range(1, max_length + 1):
            for password in itertools.product(characters, repeat=length):
                count += 1
                if try_password(file_path, file_type, "".join(password)):
                    print(f"[+] 総当たり攻撃試行回数: {count}")
                    return

    print("[-] パスワードが見つかりませんでした")

使い方は
# 実行例(ZIP)
password_cracker("protected.zip", mode="brute", max_length=3)
[*] protected.zip のパスワード解析開始 (モード: brute)
[+] PDF のパスワード発見: a12
[+] 総当たり攻撃試行回数: 2333

こんな感じで、開けることができます。

max_length
を増やせば、大きな桁数のパスワードにも対応

文字種を増やしたい場合は
コード上の変数 characters
これに文字列を追加してください。




おまけ


文字種と文字数による
パスワードの強度についてです。

今回は文字種の数が 36種類
パスワード文字数が3桁でした

文字種36個の時の
パスワード桁数による最大試行回数は
以下のようになります。


パスワードの長さによる最大試行回数 : 文字数 01 :  0000000000000036
パスワードの長さによる最大試行回数 : 文字数 02 :  0000000000001296
パスワードの長さによる最大試行回数 : 文字数 03 :  0000000000046656
パスワードの長さによる最大試行回数 : 文字数 04 :  0000000001679616
パスワードの長さによる最大試行回数 : 文字数 05 :  0000000060466176
パスワードの長さによる最大試行回数 : 文字数 06 :  0000002176782336
パスワードの長さによる最大試行回数 : 文字数 07 :  0000078364164096
パスワードの長さによる最大試行回数 : 文字数 08 :  0002821109907456
パスワードの長さによる最大試行回数 : 文字数 09 :  0101559956668416
パスワードの長さによる最大試行回数 : 文字数 10 :  3656158440062976



一般的なPCであれば7-8桁くらいまでなら
開けられそうですね


これ以上となると
少し工夫が必要になってきます。

まあ、会社で使うパスワードは
6桁くらいまでに抑えておいた方が
いざというとき開けられなくなるんで
いいかもしれません


今回はパスワードが開けられなくて困った際の
パスワード開けプログラムについてでした

今日はここまでです
それでは。

このページのトップヘ