boto3でS3のバケットの操作

前回の記事でboto3を使ったファイル操作について紹介したので、ついでにバケットの操作についても紹介しておこうという記事です。
前回同様、公式ドキュメントはこちらです。
S3 — Boto3 Docs 1.17.84 documentation

バケット名の一覧取得

存在するバケットの一覧は以下のコードで取得できます。


import boto3


s3 = boto3.resource("s3")
for bucket in s3.buckets.all():
    print(bucket.name)

"""
blog-work-sample1
blog-work-sample2
  ~ 以下略 ~
"""

バケットの作成

新規バケットの作成は以下のコードで実行できます。
引数は必ず名前付き引数で渡す必要があります。また、CreateBucketConfigurationは省略できないようです。


s3.create_bucket(
    Bucket="blog-work-sample3",
    CreateBucketConfiguration={
        'LocationConstraint': 'ap-northeast-1'
    }
)
"""
s3.Bucket(name='blog-work-sample3')
"""

バケット名の名前空間は全てのユーザーで共有されており、しかも全てのリージョンでも共有されています。
そのため、誰かが既に作っているバケット名を指定しているとエラーになるので注意が必要です。
以下のようなエラーが出ます。


try:
    s3.create_bucket(
        Bucket="sample",
        CreateBucketConfiguration={
            'LocationConstraint': 'ap-northeast-1'
        }
    )
except Exception as e:
    print(e)
"""
An error occurred (BucketAlreadyExists) when calling the CreateBucket operation:
The requested bucket name is not available. The bucket namespace is shared by all users of the system.
Please select a different name and try again.
"""

バケットの削除

中身が空のバケットであれば、単純にdelete()メソッドを呼び出すだけで消えます。
ただし、中にオブジェクトがある場合はエラーになるので、先にそれらを消しておく必要があります。


bucket = s3.Bucket("blog-work-sample3")
bucket.delete()

boto3でS3のファイル操作

boto3を使ってAWS S3のファイルを操作する方法をあれこれまとめておきます。
公式ドキュメントはこちらです。
S3 — Boto3 Docs 1.17.84 documentation
この記事ではリソースAPIの方を使います。

準備

とりあえず、この記事のために次の名前でS3にバケットを作っておきました。
– blog-work-sample1
– blog-work-sample2

さらに、 blog-work-sample1 の方には、
– sample-folder1
というフォルダーを掘っておきます。

また、サンプルとしてアップロードするファイルが必要なのでローカルに作っておきます。


!echo Hello S3! > samplefile.txt

ファイルのアップロード

まずはファイルのアップロードです。
アップロードしたいバケットを、 s3.Bucket(“バケット名”) で取得し、
upload_file(“アップロードしたいファイルのパス”, “アップロード先のファイルのパス”)でアップロードできます。
次のサンプルコードではアップロード時にファイル名を変更していいますがもちろんローカルのファイルと同じままのファイル名でも大丈夫です。


import boto3


s3 = boto3.resource("s3")
bucket = s3.Bucket("blog-work-sample1")
# バケットの直下に samplefile1.txt という名前でアップロードする場合
bucket.upload_file("samplefile.txt", "samplefile1.txt")
# フォルダ配下にアップロードする場合
bucket.upload_file("samplefile.txt", "sample-folder1/samplefile2.txt")

S3内でのファイルのコピー

ファイルのコピーには、 copy()というメソッドを使うのですが少しクセのある使い方をします。
どういうことかというと
{コピー先のバケットオブジェクト}.copy({コピー元の情報を辞書型で指定}, “コピー先のパス”)
という使い方をするのです。

まず、同じバケット内でコピーしてみます。


bucket = s3.Bucket("blog-work-sample1")  # コピー先のバケット

# 元のファイルを辞書型で指定
copy_source = {
    'Bucket': 'blog-work-sample1',
    'Key': 'samplefile1.txt'
}
bucket.copy(copy_source, "samplefile3.txt")

そして、別のバケットにコピーする時はこうです。copyメソッドの引数ではなく、元のバケットオブジェクトの取得が変わっているのがポイントです。


bucket = s3.Bucket("blog-work-sample2")  # コピー先のバケット

# 元のファイルを辞書型で指定
copy_source = {
    'Bucket': 'blog-work-sample1',
    'Key': 'samplefile1.txt'
}
bucket.copy(copy_source, "samplefile4.txt")

バケット内のオブジェクトの一覧を取得

バケット内のオブジェクトの一覧を取得するには、
bucketが持っている、objectsというプロパティの、all()メソッドでイテレーターとして取得します。
取得したオブジェクトのkey(S3において、ファイルパスやフォルダパスに相当する概念)を表示するコードが次です。


bucket = s3.Bucket("blog-work-sample1")
for obj in bucket.objects.all():
    print(obj.key)

"""
sample-folder1/
sample-folder1/samplefile2.txt
samplefile1.txt
samplefile3.txt
"""

フォルダとファイルを分けて表示したい場合は、Keyの末尾が/で終わっているかどうかで区別するしかないようです。
そもそもS3においてはフォルダという概念が存在せず、全てKey(パスのような概念)と値(ファイルの中身に相当する概念)で管理されているためこうなっているようです。
ただし、AWSの管理コンソールでは気を利かせてくれて、フォルダっぽく表示してくれています。

ファイルのダウンロード

ファイルのダウンロードには、 download_file(“ダウンロードしたいファイルのキー”, “ローカルに保存するパス”)
というメソッドを使います。

したのコードで、 blog-work-sample1/samplefile3.txt がダウンロードされ、
ローカルに、samplefile5.txt という名前で保存されます。


bucket = s3.Bucket("blog-work-sample1")
bucket.download_file("samplefile3.txt", "samplefile5.txt")

ファイルの削除

最後にファイルの削除です。
これには、delete_objectsというメソッドを使います。
一見、{バケットオブジェクト}.delete_objects{“消したいファイルのキー”} で消せそうな気がしますが、なぜかこれはかなり特殊な形の引数で渡す必要があります。

blog-work-sample1/samplefile1.txt
を消したい場合の使い方は以下の通りです。
必ず名前付き引数(Delete=)で、サンプルのような辞書を渡す必要があります。


bucket = s3.Bucket("blog-work-sample1")
bucket.delete_objects(Delete={"Objects": [{"Key": "samplefile1.txt"}]})

以上で、S3へのファイルのアップロード、コピー、リストアップ、ダウンロード、削除ができるようになりました。

複数の確率変数の最大値が従う分布について

確率密度関数が$f(x)$の同一の確率分布に従う$n$個の確率変数$X_1, \dots, X_n$について、これらの最大値が従う分布を考える機会がありました。
初めは少々苦戦したのですが、綺麗に定式化できたので記録として残しておこうと思います。
元々は最大値が従う確率密度関数を直接求めようとしてちまちまと場合分けなど考えていたのですが、
確率密度関数ではなく、累積分布関数を先に求めて、それを微分して確率密度関数を得るようにするとスムーズに算出できました。

最初に記号を導入しておきます。
まず、$X_i$たちが従う確率分布の分布関数を$F(x)$とします。
そして、$Y=\max(X_1, \dots, X_n)$が従う確率分布の確率密度関数を$g(y)$,累積分布関数を$G(y)$とします。

最終的に知りたいのは$g(y)$なのですが、まず$G(y)$の方を算出していきます。
$$
\begin{align}
G(y) &= \text{Yがy以下になる確率}\\
&= X_1, \cdots, X_n \text{が全てy以下になる確率}\\
&= (X_1\text{がy以下になる確率}) \times \cdots \times (X_n\text{がy以下になる確率})\\
&= F(y)^n
\end{align}
$$

こうして、最大値$Y$の累積分布関数が$F(y)^n$であることがわかりました。
確率密度関数は累積分布関数を1回微分することで得られるので次のようになります。
$$
\begin{align}
g(y) &= \frac{d}{dy}G(y)\\
&= \frac{d}{dy}F(y)^n\\
\therefore g(y) &= nF(y)^{n-1}f(y)
\end{align}
$$

ついでに、最小値$Z=\min(X_1, \dots, X_n)$が従う分布の確率密度関数$h(z)$と累積分布関数$H(z)$についても同様に算出できるのでやっておきます。
最大値の場合と同じように$H(z)$の方を求めます。
$$
\begin{align}
H(z) &= \text{Zがz以下になる確率}\\
&= 1-(\text{Zがz以上になる確率})\\
&= 1-(X_1, \cdots, X_n \text{が全てz以上になる確率})\\
&= 1-(X_1\text{がz以上になる確率}) \times \cdots \times (X_n\text{z以上になる確率})\\
&= 1-(1-(X_1\text{がz以下になる確率})) \times \cdots \times (1-(X_n\text{z以下になる確率}))\\
&= 1-(1-F(z))^n
\end{align}
$$
これで、最小値が従う分布の累積分布関数が求まりました。あとはこれを微分して、確率密度関数にします。
$$
\begin{align}
h(z) &= \frac{d}{dz}H(z)\\
&= -n(1-F(z))^{n-1}(-F'(z))\\
\therefore h(z) &= n\{1-F(z)\}^{n-1}f(z)
\end{align}
$$
最大値より若干複雑に見えますが、これで最小値が従う分布も得られました。