AWS Lambdaで、Pythonを使用する


pythonの使い方
--

__目次__

AWS Lambdaで、Pythonを使用する(概要)
AWS Lambdaで、Pythonを使用する(具体例)
 (AWS LambdaとCloudWatchを使用して、Apacheのプロセス数が200を超えたときにSlack通知を送信する)

最初に実行する　main
enumurate関数
python formatとは
f-stringsとは
splitとは
joinとは
Pythonでは、値渡しと参照渡しの両方があります。
listのコピー
in not演算子の使い方について
if文とwhile文
__と_は、特殊な慣例を持つ識別子（identifier）
forループ
zip()関数
ジェネレータ（Generator）とは
クラス（Class）
ファイルを操作する
CSVファイルを操作する
RESTful APIを作成する
socketモジュールを使用する
MySQLを操作する

AWS LambdaとCloudWatchを使用して、Apacheのプロセス数が200を超えたときにSlack通知を送信するAWS CDKアプリケーションを作成する
CloudWatch LogsのSlack通知自動化(AWS-CDK python)

__AWS Lambdaで、Pythonを使用する(概要)__
=

1.	AWS Lambdaコンソールにログイン。
2.	「関数の作成」をクリックして、新しいLambda関数を作成。
3.	「ランタイム」でPythonを選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2920213/c42e5924-ccd7-4898-81e8-83d6b29e3bfa.png)
4.	コードエディタでPythonコードを作成。
既存のPythonコードをアップロードすることもできます。
5.	必要に応じて、Lambda関数が依存するライブラリを含むzipファイルをアップロード。
6.	「トリガーの追加」をクリックして、Lambda関数をトリガーするイベントを設定。
7.	「保存」をクリックして、Lambda関数をデプロイ。
Pythonを使用する場合、Lambda関数のエントリーポイントは、Pythonファイル内のlambda_handler関数にする必要があります。lambda_handler関数は、Lambda関数が呼び出されたときに最初に実行される関数です。


例えば、次ようなPythonコードを使用してLambda関数を作成できます。
~~~
def lambda_handler(event, context):
    # ここにLambda関数の本体を実装する
    return "Hello from Lambda!"
~~~
この例では、lambda_handler関数が定義され、Lambda関数が呼び出されたときに「Hello from Lambda!」というメッセージが返されます。必要に応じて、eventパラメーターを使用してLambda関数に情報を渡すことができます。また、contextパラメーターを使用して、Lambda関数の実行に関する情報を取得することができます。

__AWS Lambdaで、Pythonを使用する(具体例)__
=
(AWS LambdaとCloudWatchを使用して、Apacheのプロセス数が200を超えたときにSlack通知を送信する)
--


__1.SNS Topicの作成__

まず、SNS Topicを作成します。
これは、CloudWatch AlarmやEventBridgeルールなどからトリガーされた通知をSNS Topicに送信するためのものです。
__SNS トピックの作成__
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2920213/df0385df-1f66-9d7f-fe40-a18bdfb893ea.png)
__サブスクリプションの作成__
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2920213/171e0c4f-d9d4-4816-b46d-6174e8881c00.png)
__「プロトコル」に Eメール を選択し、エンドポイントに送信したいアドレスを入力後、「サブスクリプションの作成」を選択します。__
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2920213/6c9b7e34-0432-3371-53ef-db56fcad0c62.png)

__2.AWS Chatbotの設定__
AWS Chatbotを設定します。Chatbotは、SNS Topicからの通知をSlackなどのメッセージングサービスに転送するためのものです。

AWS Chatbotコンソールにアクセスし、「Create a new Slack bot」をクリックします。
Slackのワークスペースとチャンネルを選択します。
SNS TopicのARNを指定します。

__3.Lambda関数の作成__

AWS Lambdaコンソールで「関数の作成」をクリックし、新しいLambda関数を作成します。
•	「ランタイム」でPythonを選択します。
•	関数名を入力します。
•	「新しいロールの作成」をクリックして、新しいIAMロールを作成し、必要な権限を割り当てます。
•	「関数の作成」をクリックして、Lambda関数を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2920213/b8c4d3f0-ff6d-462b-7b60-b27c321c7ade.png)

__4.Lambda関数のコードを作成__

このコードは、Apacheのプロセス数を取得し、200を超えた場合にSlackに通知を送信します。
コード内のWEBHOOK_URLには、SlackのWebhook URLを設定します。
~~~
import json
import boto3

def lambda_handler(event, context):
    message = event['Records'][0]['Sns']['Message']
    client = boto3.client('sns')
    response = client.publish(
        TopicArn='SNS TopicのARNを指定',
        Message=message
    )
    return {
        'statusCode': 200,
        'body': json.dumps('Notification sent successfully')
    }

~~~

__5.Lambda関数の環境変数を設定する__
Lambda関数の環境変数に、SlackのWebhook URLを設定する必要があります。
AWS Lambdaコンソールで、関数の「環境変数」タブに移動し、WEBHOOK_URLのキーとSlackのWebhook URLの値を設定します。

__6.Lambda関数をトリガーするCloudWatchアラームを作成する__

AWSマネジメントコンソールで、CloudWatchを開きます。
左側のナビゲーションメニューから、「アラーム」を選択します。
「アラームの作成」をクリックします。
「メトリックスを選択」をクリックして、サービス「EC2」、メトリックス「ProcessCount」を選択します。
「全てのEC2インスタンス」を選択し、「次へ」をクリックします。
「閾値」を設定します。例えば、プロセス数が200を超えた場合に通知を送信する場合は、「大なり記号(>)」を選択して「200」と入力します。
「アクション」を選択します。ここでは、「新しいアクションの作成」を選択します。
「アクション」で「Lambda関数」を選択します。その後、作成済みのLambda関数を選択します。
「任意の追加タグ」で必要に応じてタグを設定し、「次へ」をクリックします。
アラームの名前と説明を入力して、アラームを作成します。
これで、プロセス数が200を超えた場合に、Lambda関数がトリガーされ、Slackに通知が送信されるようになります。


pythonの使い方
--

__最初に実行する　main__
__name__というPythonの特殊な変数には、現在のモジュールがスクリプトとして実行された場合には、値として文字列"main"が設定されます。

つまり、__name__が"main"という値を持つ場合には、そのスクリプトが直接実行されたことを示します。
逆に、モジュールが他のモジュールからインポートされた場合には、__name__にはモジュール名が設定されます。

 __name__が"main"の場合にだけ実行されるコードの例です。
~~~
if __name__ == "__main__":
    # ここにスクリプトが実行されたときに実行されるコードを書く
~~~
このコードでは、if文によって__name__が"main"である場合にのみ、その下のブロックのコードが実行されます。これにより、スクリプトが直接実行された場合にだけ、特定のコードを実行することができます。

python formatとは
--
formatは、Pythonで文字列をフォーマットするための方法の1つであり、文字列内に変数の値や式の結果を挿入するために使用されます。

formatメソッドは、文字列オブジェクトのメソッドとして提供され、以下のように使用されます。
~~~
string.format(args)
~~~
ここで、stringはフォーマットする文字列を表し、argsは文字列に挿入する値を表す変数または式のタプル、または辞書です。

__enumurate関数__
enumerate() 関数は、Python で反復可能なオブジェクト（リスト、タプル、文字列、辞書など）の各要素に対して、そのインデックスと要素のペアを返すために使用される組み込み関数です。以下は、enumerate() 関数の基本的な使い方です。
~~~
for index, item in enumerate(sequence):
    # do something with index and item
~~~
上記のコードでは、enumerate() 関数が sequence という反復可能なオブジェクトの各要素に対して、その要素のインデックスと要素のペアを生成します。index は各要素のインデックスを表し、item は各要素を表します。

具体的な例
~~~
fruits = ['apple', 'banana', 'cherry']
for index, fruit in enumerate(fruits):
    print(index, fruit)
~~~
fruits リストの各要素に対して、そのインデックスと要素を出力します。

出力結果
~~~
0 apple
1 banana
2 cherry
~~~

enumerate() 関数には、オプションの引数 start を指定することもできます。
この引数を指定すると、インデックスが start から始まります。
~~~
fruits = ['apple', 'banana', 'cherry']
for index, fruit in enumerate(fruits, start=1):
    print(index, fruit)
~~~

出力結果
~~~
1 apple
2 banana
3 cherry
~~~
enumerate() 関数を使用すると、反復可能なオブジェクトの要素にアクセスしながら、その要素のインデックスも簡単に取得できます。

__formatメソッドの例__

引数を順に挿入する方法
~~~
name = "John"
age = 25
message = "My name is {} and I am {} years old".format(name, age)
print(message)
~~~
 出力結果
~~~
 My name is John and I am 25 years old
~~~
 引数にインデックスを指定して挿入する方法
~~~
message = "My name is {0} and I am {1} years old".format(name, age)
print(message)
~~~
出力結果
~~~
My name is John and I am 25 years old
~~~

辞書を使用して引数を指定する方法
~~~
person = {'name': 'Alice', 'age': 30}
message = "My name is {name} and I am {age} years old".format(**person)
print(message)
~~~

出力結果
~~~
My name is Alice and I am 30 years old
~~~

formatメソッドは、文字列内に挿入する値のフォーマットをより細かく制御するためにも使用できます。例えば、整数の場合には桁数を指定したり、小数の場合には小数点以下の桁数を指定したりすることができます。

f-stringsとは
--
~~~
a = 'a'
print(f'a is {a}')
 
x, y, z = 1, 2, 3
print(f'a is {x}, {y}, {z}')
print(f'a is {z}, {y}, {x}')
 
name = 'tarou'
family = 'kimura'
print(f'My name is {name} {family}. Watashi ha {family} {name}')
~~~
このコードは、Pythonのf文字列（formatted string literal）を使用して、変数の値を文字列に挿入して出力するものです。

最初の行では、変数aに文字列'a'を代入しています。次の行では、f文字列を使用して、変数aの値を文字列に挿入しています。{a}の部分が変数aの値に置き換えられ、
~~~
'a is a'
~~~
という文字列が出力されます。

2番目のセットの行では、x、y、zに1、2、3の値をそれぞれ代入しています。次に、f文字列を使用して、3つの変数の値を文字列に挿入しています。最初のf文字列の場合、{x}、{y}、{z}の部分がそれぞれ1、2、3に置き換えられ、
~~~
'a is 1, 2, 3'
~~~
という文字列が出力されます。次のf文字列の場合、{z}、{y}、{x}の部分がそれぞれ3、2、1に置き換えられ、
~~~
'a is 3, 2, 1'
~~~
という文字列が出力されます。

最後の行では、nameに'tarou'、familyに'kimura'を代入しています。f文字列を使用して、2つの変数の値を文字列に挿入しています。{name}と{family}の部分がそれぞれ'tarou'と'kimura'に置き換えられ、
~~~
'My name is tarou kimura. Watashi ha kimura tarou'
~~~
という文字列が出力されます。ただし、最後の日本語の部分は「私は木村太郎です」という意味です。

splitとは
--
split() はPythonの組み込み関数で、文字列を指定された区切り文字で分割し、分割された部分文字列をリストとして返します。

以下は split() 関数の基本的な使い方です。
~~~
string = "Hello World"
result = string.split()  # デフォルトはスペースで分割
print(result)  # ['Hello', 'World']
~~~
split() はデフォルトでスペース文字（空白、タブ、改行）を区切り文字として使用しますが、任意の区切り文字を指定することもできます。例えば、コンマで区切られた文字列を分割する場合は以下のようにします。
~~~
string = "apple,banana,orange"
result = string.split(",")  # コンマで分割
print(result)  # ['apple', 'banana', 'orange']
~~~
また、分割される回数を指定することもできます。以下は最初の2つだけを分割する例です。
~~~
string = "apple,banana,orange"
result = string.split(",", 2)  # 最初の2つだけ分割
print(result)  # ['apple', 'banana', 'orange']
~~~
split() は文字列だけでなく、リストやタプルなどのイテラブルオブジェクトにも適用することができます。

joinとは
--
join() はPythonの文字列メソッドで、リストやタプルなどのイテラブルオブジェクトを指定された文字列で結合して、1つの文字列にします。

以下は join() メソッドの基本的な使い方です。
~~~
my_list = ["apple", "banana", "orange"]
result = "-".join(my_list)
print(result)  # "apple-banana-orange"
~~~
この例では、join() メソッドを使って、 my_list にある各要素を連結して "apple-banana-orange" という1つの文字列を作成しています。join() メソッドの引数には、連結する際に使用する区切り文字を指定します。

リスト以外のイテラブルオブジェクトでも同じように使うことができます。また、join() メソッドは文字列の連結に限らず、任意のイテラブルオブジェクトに対して適用することができます。

__辞書型オブジェクトを join() メソッドで連結する例__
~~~
my_dict = {"a": "apple", "b": "banana", "o": "orange"}
result = "-".join(my_dict.values())
print(result)  # "apple-banana-orange"
~~~
join() メソッドは、文字列を結合するだけでなく、任意のオブジェクトを文字列に変換して結合することもできます。ただし、結合するオブジェクトはすべて文字列に変換可能である必要があります。

Pythonでは、値渡しと参照渡しの両方があります。
--
値渡しは、関数に渡される引数の値がコピーされ、元の変数に影響を与えずに関数内で使用されることを意味します。これは、不変のオブジェクト（例えば、整数、浮動小数点数、文字列）に対して機能します。

参照渡しは、関数に渡された引数の変数自体が関数内で使用されることを意味します。これは、変更可能なオブジェクト（例えば、リスト、辞書、クラスのインスタンス）に対して機能します。

例えば
~~~
def change_list(mylist):
    mylist.append(4)
    print("List inside function: ", mylist)

mylist = [1, 2, 3]
change_list(mylist)
print("List outside function: ", mylist)
~~~
この関数は、リストを受け取り、そのリストに新しい要素を追加して、関数内でリストをprintします。この関数を呼び出すと、リストは参照渡しされ、関数によって変更されます。したがって、関数内でリストが変更されると、リストの内容が変更されたまま、関数の外側でもそのまま残ります。以下は上記のコードの出力です。
~~~
List inside function:  [1, 2, 3, 4]
List outside function:  [1, 2, 3, 4]
~~~
一方、以下のコードを考えてみましょう。
~~~
def change_number(num):
    num += 1
    print("Number inside function: ", num)

num = 3
change_number(num)
print("Number outside function: ", num)
~~~
この関数は、数値を受け取り、その数値に1を加えて、関数内で数値をprintします。この関数を呼び出すと、数値は値渡しされ、関数によって変更されません。したがって、関数内で数値が変更されても、関数の外側の変数には影響を与えません。以下は上記のコードの出力です。
~~~
Number inside function:  4
Number outside function:  3
~~~
このコードは、change_numberという関数を定義し、引数としてnumを取ります。
関数の中では、numに1を加えています。そして、その結果を表示しています。

その後、numという変数に3を代入し、change_number関数を呼び出しています。
この関数は、引数としてnumを受け取っていますが、numの値を変更するだけで、返り値はありません。
そのため、numの値は3のままで、関数の中で加算された値はprint()関数で表示されますが、実際に変更されたわけではありません。

最後に、print()関数を使用して、変数numの値を表示します。
この場合、numの値は関数の呼び出し前と同じであるため、Number outside function: 3という出力が得られます。

つまり、このコードは関数内でのみ変更が行われ、関数の外での変数の値は変わらないことがわかります。

listのコピー
--
Pythonのリストのコピーには2つの方法があります。

__1.スライスを使用する方法__
~~~
original_list = [1, 2, 3, 4, 5]
copy_list = original_list[:]
~~~
この方法では、スライスを使って、リストの最初から最後までのすべての要素を新しいリストにコピーします。この方法は、元のリストとコピーしたリストが別々のオブジェクトであることを保証します。

__2.copy()メソッドを使用する方法__
~~~
original_list = [1, 2, 3, 4, 5]
copy_list = original_list.copy()
~~~
この方法では、copy()メソッドを使って、リストを新しいリストにコピーします。この方法も、元のリストとコピーしたリストが別々のオブジェクトであることを保証します。

注意すべき点として、リスト内にリストがある場合には、これらの方法で作成されたコピーは浅いコピーになることがあります。つまり、元のリストとコピーしたリストは同じリスト内のリストオブジェクトを共有してしまいます。これを回避するには、copyモジュールのdeepcopy()関数を使用する必要があります。

例えば、以下のコードでは、スライスを使ってリストをコピーし、元のリストとコピーしたリストが別々のオブジェクトであることを確認します。
~~~
original_list = [1, 2, 3, 4, 5]
copy_list = original_list[:]
original_list[0] = 10
print(original_list) # [10, 2, 3, 4, 5]
print(copy_list) # [1, 2, 3, 4, 5]
~~~


__浅いコピーは、元のリストとコピーしたリストが同じオブジェクトを共有するため、元のリストの変更がコピーに反映されてしまう可能性があります。__

例えば
~~~
original_list = [[1, 2], [3, 4]]
copy_list = original_list[:]
original_list[0][0] = 10
print(original_list) # [[10, 2], [3, 4]]
print(copy_list) # [[10, 2], [3, 4]]
~~~
この場合、元のリストの最初の要素である[1, 2]を変更しているため、元のリストとコピーしたリストが同じオブジェクトを共有しているため、コピーも変更されます。このような問題を避けるために、copyモジュールのdeepcopy()関数を使用する必要があります。deepcopy()関数は、リスト内のリストなどのオブジェクトを再帰的にコピーするため、完全なコピーを作成することができます。

in not演算子の使い方について
--
Pythonのin not演算子は、ある要素がリストやタプル、セット、文字列、辞書などの中に含まれていない場合にTrueを返します。以下はin not演算子の使い方の例です。
~~~
# リストに要素が含まれていない場合にTrueを返す
my_list = [1, 2, 3, 4]
if 5 not in my_list:
    print("5は含まれていません")

# 文字列に文字が含まれていない場合にTrueを返す
my_string = "Hello, world!"
if "a" not in my_string:
    print("aは含まれていません")
~~~
in not演算子は、not演算子と一緒に使うこともできます。以下はnot演算子とin not演算子を一緒に使った例です。
~~~
# リストに要素が含まれていない場合にTrueを返す（not演算子とin not演算子を一緒に使う）
my_list = [1, 2, 3, 4]
if not (5 in my_list):
    print("5は含まれていません")
~~~

if文とwhile文
--
条件分岐と繰り返し処理を実現するための制御構造

if文は、指定された条件式が真（True）である場合に、その下に書かれたコードブロックを実行します。条件分岐を行うために使用され、プログラムのフローを制御することができます。

一方、while文は、指定された条件式が真（True）である限り、その下に書かれたコードブロックを繰り返し実行します。繰り返し処理を行うために使用され、プログラムのフローを制御することができます。

両方の制御構造を組み合わせて、プログラムを柔軟に制御することができます。例えば、以下のようなコードでは、whileループとif文を組み合わせて、1から10までの偶数を表示します。
~~~
x = 1

while x <= 10:
    if x % 2 == 0:
        print(x)
    x += 1
~~~
このコードでは、xが1から10の範囲で繰り返し増加し、if文によってxが偶数かどうかを判断しています。偶数であれば、print文によってxの値を表示します。奇数であれば、print文は実行されず、次のループに進みます。
~~~
2
4
6
8
10
~~~
whileループが最初に実行されたとき、xは1であり、条件式x <= 10は真（True）となります。if文によって、xが偶数かどうかが判断されますが、1は偶数ではないため、print文は実行されず、次のループに進みます。

次のループでは、xは2に増加し、条件式x <= 10はまだ真（True）です。if文によって、xが偶数かどうかが再度判断されますが、2は偶数であるため、print文によってxの値が表示されます。

同様に、whileループが10まで繰り返され、偶数の2, 4, 6, 8, 10が順番に表示されます。11になると、条件式x <= 10は偽（False）となり、whileループが終了します。


__と_は、特殊な慣例を持つ識別子（identifier）
--

__は、Pythonにおいて「name mangling（名前変換）」と呼ばれる機能を持つ特殊な識別子です。この機能により、属性名がクラス外部からアクセスできなくなります。これにより、クラスの内部に変数や属性を定義することができ、外部からのアクセスを制限することができます。

一方、_は、クラスの内部で使用される「プライベート」な属性やメソッドを示す慣習的な識別子です。これらは、Pythonにおいて公式のプライベート属性やメソッドとして扱われませんが、クラスの利用者に対して、これらの属性やメソッドはクラス内部で使用されることを示すことができます。

例えば、以下のように定義されたクラスがあるとします。
~~~
class Person:
    def __init__(self, name):
        self.__name = name
        self._age = 0

    def get_name(self):
        return self.__name

    def set_age(self, age):
        self._age = age

    def get_age(self):
        return self._age
~~~
このクラスでは、__nameは外部からアクセスできないプライベートな属性であり、_ageは内部で使用されるプライベートな属性です。また、get_name、set_age、get_ageはそれぞれ、__name、_ageにアクセスするためのgetterとsetterメソッドです。

これらのgetterとsetterを使って、属性にアクセスすることができます。例えば、以下のようにインスタンスを作成して、set_age()メソッドとget_age()メソッドを使用して、age属性の値を設定・取得できます。
~~~
person = Person("John")
person.set_age(30)
print(person.get_age()) # 30
~~~
また、get_name()メソッドを使用して、name属性の値を取得することもできます。
~~~
print(person.get_name()) # "John"
~~~
このように、getterとsetterを使用することで、クラスの属性に対して適切な制御を加えることができます。

forループ
--
指定した範囲内の値を反復処理するために使用されます。for i in range()は、連続した数値の範囲を反復処理するために使用されます。以下に、for i in range()の使い方を説明します。

range()関数は、整数の範囲を生成するために使用されます。range()関数は、start（省略可能）、stop（必須）、step（省略可能）の3つの引数を取ります。startは範囲の開始値、stopは範囲の終了値、stepは値の間隔を指定します。

例えば、range(1, 5)は、1から4までの整数を生成します。

forループとrange()関数を組み合わせて、使用することができます。
~~~
for i in range(1, 5):
    print(i)
~~~
この場合、iには、1から4までの整数が順番に代入され、各値が標準出力に表示されます。

また、step引数を使用して、範囲内の値の間隔を指定することもできます。例えば、range(0, 10, 2)は、0から8までの偶数を生成します。
~~~
for i in range(0, 10, 2):
    print(i)
~~~
この場合、iには、0から8までの偶数が順番に代入され、各値が標準出力に表示されます。

range()関数は、start、stop、step引数を使用して、様々な範囲を生成することができます。forループと組み合わせて、範囲内の値を反復処理することができます。

enumerate()関数は、リスト、タプル、文字列などのイテラブルオブジェクトの要素にインデックスを付けて反復処理するために使用されます。以下に、enumerate()関数の使い方を説明します。

enumerate()関数は、イテラブルオブジェクトを引数として取り、enumerate()オブジェクトを返します。enumerate()オブジェクトは、各要素のインデックスと値のタプルを返すイテレーターとして機能します。

例えば、以下のようなリストがあるとします。
~~~
fruits = ['apple', 'banana', 'orange']
~~~
このリストをenumerate()関数で反復処理する場合、以下のように使用します。
~~~
for index, fruit in enumerate(fruits):
    print(index, fruit)
~~~
この場合、enumerate()関数が各要素にインデックスを付けて反復処理します。indexには、各要素のインデックスが代入され、fruitには各要素の値が代入されます。上記のコードの出力は以下のようになります。
~~~
0 apple
1 banana
2 orange
~~~
enumerate()関数には、start引数を指定してインデックスの開始値を設定することもできます。
例えば、以下のように指定することができます。
~~~
for index, fruit in enumerate(fruits, start=1):
    print(index, fruit)
~~~
この場合、出力は以下のようになります。
~~~
1 apple
2 banana
3 orange
~~~
enumerate()関数を使用することで、イテラブルオブジェクトの各要素にインデックスを付けて反復処理することができます。

zip()関数
--
複数のイテラブルオブジェクトをまとめてひとつのイテラブルオブジェクトとして扱い、対応する要素をまとめて返します。

__使い方__
~~~
# 2つのリストを zip()関数でまとめる
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
zipped = zip(list1, list2)

# zip()関数でまとめたオブジェクトをリストとして表示
print(list(zipped))
# 出力: [(1, 'a'), (2, 'b'), (3, 'c')]

# forループを使って要素を取り出す
for x, y in zip(list1, list2):
    print(x, y)
# 出力:
# 1 a
# 2 b
# 3 c
~~~
zip()関数は、2つ以上のイテラブルオブジェクトをまとめることができます。引数に渡すイテラブルオブジェクトの要素数が異なる場合は、最短のイテラブルオブジェクトの要素数まで要素がまとめられます。余った要素は無視されます。
~~~
# 3つのリストを zip()関数でまとめる
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
list3 = ['x', 'y', 'z']
zipped = zip(list1, list2, list3)

# zip()関数でまとめたオブジェクトをリストとして表示
print(list(zipped))
# 出力: [(1, 'a', 'x'), (2, 'b', 'y'), (3, 'c', 'z')]

# 2つのリストを zip()関数でまとめる
list1 = [1, 2, 3]
list2 = ['a', 'b']
zipped = zip(list1, list2)

# zip()関数でまとめたオブジェクトをリストとして表示
print(list(zipped))
# 出力: [(1, 'a'), (2, 'b')]

# 最短のイテラブルオブジェクトの要素数までまとめられる
~~~
zip()関数は、タプル、リスト、セットなどのイテラブルオブジェクトを返します。返されるオブジェクトはイテレータブルなので、一度forループなどで中身を使うと、次に使うためには再度zip()関数を呼び出す必要があります。また、リストやタプルに変換するにはlist()やtuple()関数を使います。

辞書型（Dictionary）は、キー（key）と値（value）をペアにして格納するデータ型です。for文を使うことで、辞書型に格納されているキーと値のペアを取り出すことができます。

__辞書型に格納されているキーと値のペアをfor文を使って順に取り出し、キーと値を表示する例__
~~~
my_dict = {"apple": 100, "banana": 200, "orange": 300}

for key, value in my_dict.items():
    print(key, value)
~~~
上記のコードでは、my_dictに格納されているキーと値のペアをitems()メソッドを使って取り出し、それぞれkeyとvalueに代入しています。そして、for文でキーと値のペアを順に取り出し、print文を使って表示しています。

もし、辞書型のキーだけを取り出したい場合は、以下のようにkeys()メソッドを使います。
~~~
for key in my_dict.keys():
    print(key)
~~~
また、辞書型の値だけを取り出したい場合は、以下のようにvalues()メソッドを使います。
~~~
for value in my_dict.values():
    print(value)
~~~

ジェネレータ（Generator）とは
--
イテレータ（Iterator）を作成するための一種の関数です。
ジェネレータを使うことで、複雑な反復処理をシンプルに書くことができます。

ジェネレータは、yield文を含む関数として定義されます。yield文は、関数から値を返すと同時に、関数の状態を保持します。次にyield文が実行されるときに、以前に中断された箇所から処理を再開します。これにより、関数を呼び出すたびに新しい値を生成することができます。

__ジェネレータの例__
このジェネレータは、引数で指定された範囲の数値を順に返すものです。
~~~
def my_generator(start, stop):
    while start < stop:
        yield start
        start += 1
~~~
このジェネレータは、以下のようにして呼び出すことができます。
~~~
for i in my_generator(0, 10):
    print(i)
~~~
my_generator()関数を呼び出すことで、ジェネレータを取得しています。
そして、for文を使ってジェネレータから値を取り出し、それぞれprint文を使って表示しています。

ジェネレータは、イテレータと同様に、反復処理を行うための方法として利用できます。しかし、ジェネレータはイテレータとは異なり、すべての値を事前に計算してメモリに格納する必要がないため、メモリ効率が良く、大量のデータを扱う場合に有用です。

クラス（Class）
--
オブジェクト指向プログラミングの概念の1つで、データとそのデータを操作するメソッドを1つのまとまりにしたものです。
メソッドは、クラス内で定義された関数のことで、クラス内で定義されたデータにアクセスすることができます。

Pythonのクラスには、インスタンスメソッド、クラスメソッド、スタティックメソッドの3つの種類があります。ここでは、クラスメソッドについて詳しく説明します。

クラスメソッドは、インスタンスメソッドと同様にクラス内で定義されたメソッドですが、第1引数としてclsという名前が予約されており、クラスオブジェクト自身を参照することができます。クラスメソッドは、クラスオブジェクトに対して呼び出すことができます。

__クラスメソッドの例__
このクラスは、cls_counterというクラス変数を持ち、この変数をクラスメソッドから操作することができます。
~~~
class MyClass:
    cls_counter = 0

    @classmethod
    def increment_counter(cls):
        cls.cls_counter += 1

    @classmethod
    def get_counter(cls):
        return cls.cls_counter
~~~
上記のコードでは、@classmethodデコレータを使って、クラスメソッドを定義しています。increment_counter()メソッドは、cls_counterクラス変数をインクリメントするためのメソッドで、get_counter()メソッドは、cls_counterクラス変数の値を取得するためのメソッドです。

クラスメソッドは、以下のようにして呼び出すことができます。
~~~
MyClass.increment_counter()
print(MyClass.get_counter())
~~~
上記のコードでは、increment_counter()メソッドを呼び出してcls_counterクラス変数をインクリメントし、get_counter()メソッドを呼び出してcls_counterクラス変数の値を取得しています。


Pythonのプロパティを使用すると、クラスの属性を設定することができます。プロパティは、クラスのインスタンス変数にアクセスするためのgetterとsetterを定義することで実現されます。以下は、プロパティを使用して属性を設定する方法の例です。
~~~
class Person:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        print("Getting name...")
        return self._name

    @name.setter
    def name(self, value):
        print("Setting name...")
        self._name = value

person = Person("Alice")
print(person.name)   # Getting name... Alice
person.name = "Bob"  # Setting name...
print(person.name)   # Getting name... Bob
~~~
上記の例では、@propertyデコレーターを使用してname()メソッドをプロパティ化しています。これにより、name属性にアクセスすると、自動的にname()メソッドが呼び出されます。また、@name.setterデコレーターを使用してname()メソッドを属性設定可能にしています。

person = Person("Alice")でPersonクラスのインスタンスを作成し、print(person.name)でname属性を取得します。これにより、name()メソッドが呼び出され、"Getting name..."というメッセージが表示されます。そして、person.name = "Bob"でname属性に値を設定し、再度print(person.name)を呼び出すと、"Setting name..."と"Getting name..."というメッセージが表示され、name属性が"Bob"に設定されていることが確認できます。

ファイルを操作する
--
標準のファイル操作関数を使用することができます。以下は、Pythonでファイルを開き、読み込み、書き込み、およびクローズする方法です。
ファイルの読み込み
~~~
# ファイルを読み込む
with open('ファイル名.txt', 'r') as f:
    contents = f.read()
print(contents)
~~~
ファイルの書き込み
~~~
# ファイルを書き込む
with open('ファイル名.txt', 'w') as f:
    f.write('書き込む内容')
~~~
ファイルの追記
~~~
# ファイルに追記する
with open('ファイル名.txt', 'a') as f:
    f.write('追記する内容')
~~~
Pythonの open() 関数を使用してファイルを開き、with ステートメントを使用してブロック内の処理が完了したら
ファイルを自動的にクローズしています。

open() 関数の第1引数には、ファイル名を文字列で指定し、第2引数にはファイルの開き方を指定します。 
'r' は読み込みモード、'w' は書き込みモード、'a' は追記モードです。

また、read() メソッドはファイルの内容を文字列で返し、write() メソッドは指定した文字列をファイルに書き込みます。

CSVファイルを操作する
--
csvモジュールを使用することができます。以下は、PythonでCSVファイルを読み込み、書き込み、およびクローズする方法です。

CSVファイルの読み込み
~~~
import csv

with open('ファイル名.csv', 'r') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)
~~~
上記の例では、csvモジュールの reader() 関数を使用してCSVファイルを読み込んでいます。
読み込んだデータはリストとして返され、各行の値はリストの要素として格納されます。
for ループを使用して各行の値を出力しています。

CSVファイルの書き込み
~~~
import csv

with open('ファイル名.csv', 'w') as f:
    writer = csv.writer(f)
    writer.writerow(['列1の値', '列2の値', '列3の値'])
~~~
上記の例では、csvモジュールの writer() 関数を使用してCSVファイルに書き込んでいます。
writerow() メソッドを使用して、CSVファイルに書き込む各行の値をリスト形式で指定します。

CSVファイルの追記
~~~
import csv

with open('ファイル名.csv', 'a') as f:
    writer = csv.writer(f)
    writer.writerow(['列1の値', '列2の値', '列3の値'])
~~~
上記の例では、csvモジュールを使用してCSVファイルに追記しています。
追記モードでファイルを開き、writerow() メソッドを使用して、CSVファイルに書き込む各行の値をリスト形式で指定します。

csv モジュールは、CSVファイルを扱うための便利な機能を多数提供しています
。例えば、区切り文字をカスタマイズしたい場合は、 csv.reader() や csv.writer() 関数に delimiter 引数を指定することができます。
また、ヘッダー行があるCSVファイルを読み込む場合は、 csv.DictReader() 関数を使用してディクショナリ形式で読み込むこともできます。

RESTful APIを作成する
--

1.FlaskなどのWebフレームワークをインストールする。
~~~
pip install flask
~~~
2.Flaskを使用してAPIのエンドポイントを定義する。
~~~
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/hello', methods=['GET'])
def hello():
    return jsonify({'message': 'Hello World!'})

if __name__ == '__main__':
    app.run(debug=True)
~~~
上記の例では、Flaskを使用して '/hello' というエンドポイントを定義しています。
'/hello' にGETリクエストが送信された場合、hello() 関数が呼び出され、JSON形式で {'message': 'Hello World!'} を返します。

3.Flaskアプリケーションを実行する。
~~~
if __name__ == '__main__':
    app.run(debug=True)
~~~
上記の例では、Flaskアプリケーションをデバッグモードで実行しています。
これにより、Flaskアプリケーションに問題が発生した場合にデバッグ情報を表示することができます。

4.APIのテスト
Webブラウザや、Pythonのrequestsモジュールなどを使用して、APIをテストすることができます。
~~~
import requests

url = 'http://localhost:5000/hello'
response = requests.get(url)
print(response.json())
~~~
上記の例では、Pythonのrequestsモジュールを使用して、APIエンドポイントにGETリクエストを送信し、JSON形式のレスポンスを出力しています。

以上がPythonでRESTful APIを作成する基本的な手順です。
より複雑なAPIを作成する場合は、Flaskを拡張するライブラリを使用するなどの方法があります。

__Pythonの標準ライブラリであるhttp.serverを使ってRESTful APIを作成する方法__

1.Pythonの標準ライブラリであるhttp.serverを使用する場合は、以下のようにサーバを作成します。
~~~
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        response = {
            'message': 'Hello, world!'
        }
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps(response).encode('utf-8'))

httpd = HTTPServer(('localhost', 8000), SimpleHTTPRequestHandler)
httpd.serve_forever()
~~~
2.上記のコードでは、HTTPServerとSimpleHTTPRequestHandlerをインポートし、SimpleHTTPRequestHandlerをサブクラス化して、
do_GETメソッドでGETリクエストを処理する方法を定義しています。この例では、レスポンスとしてJSONオブジェクトを返しています。

3.httpd.serve_forever()を呼び出すことで、サーバーが実行されます。

4.このAPIをテストするには、curlコマンドを使用することができます。
~~~
curl -X GET http://localhost:8000/
~~~


socketモジュールを使用する
--
ネットワーク通信を行うことができます。以下にPythonでsocketを使用する基本的な手順を示します。

__1.socketオブジェクトを作成する__
~~~
import socket
~~~

__IPv4を使用する場合__
~~~
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
~~~

__IPv6を使用する場合__
~~~
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
~~~
上記の例では、IPv4またはIPv6を使用するsocketオブジェクトを作成しています。
socket.AF_INETはIPv4を指定し、socket.AF_INET6はIPv6を指定します。

__2.サーバーに接続する__
~~~
s.connect(('サーバーのIPアドレス', ポート番号))
~~~
上記の例では、connect()メソッドを使用してサーバーに接続しています。
('サーバーのIPアドレス', ポート番号)には、接続先のサーバーのIPアドレスとポート番号を指定します。

__3.サーバーからデータを受信する__
~~~
data = s.recv(1024)
~~~
上記の例では、recv()メソッドを使用してサーバーからデータを受信しています。
1024は1回の受信で受け取るデータの最大サイズを指定しています。

__4.サーバーにデータを送信する__
~~~
s.sendall('Hello, World!')
~~~
上記の例では、sendall()メソッドを使用してサーバーにデータを送信しています。引数には、送信するデータを指定します。

__5.ソケットを閉じる__
~~~
s.close()
~~~
上記の例では、close()メソッドを使用してソケットを閉じています。
ソケットを使用しなくなったら、必ずソケットを閉じるようにしましょう。

MySQLを操作する
--
まずMySQLとの接続を確立する必要があります。
それには、PythonのMySQLドライバをインストールする必要があります。
よく使用されるMySQLドライバは「mysql-connector-python」と「pymysql」です。
ここでは、「mysql-connector-python」を使用した接続方法を説明します。

「mysql-connector-python」をインストールします。
~~~
pip install mysql-connector-python
~~~
次に、以下のようにPythonスクリプトを書いてMySQLとの接続を確立します。
~~~
import mysql.connector

mydb = mysql.connector.connect(
  host="localhost",
  user="yourusername",
  password="yourpassword",
  database="yourdatabase"
)

print(mydb)
~~~
上記のコードの「host」、「user」、「password」、および「database」を自分のMySQLデータベースの情報に置き換えてください。
このスクリプトを実行すると、MySQLデータベースに接続され、接続情報が表示されます。

接続が確立したら、以下のようにしてSQLクエリを実行できます。
~~~
import mysql.connector

mydb = mysql.connector.connect(
  host="localhost",
  user="yourusername",
  password="yourpassword",
  database="yourdatabase"
)

mycursor = mydb.cursor()

mycursor.execute("SELECT * FROM yourtable")

myresult = mycursor.fetchall()

for x in myresult:
  print(x)
~~~
上記のコードでは、「yourtable」というテーブルから全ての行を取得して表示しています。

AWS LambdaとCloudWatchを使用して、
Apacheのプロセス数が200を超えたときにSlack通知を送信するAWS CDKアプリケーションを作成する
--
1.必要なモジュールをインポートします。
~~~
from aws_cdk import (
    aws_lambda as _lambda,
    aws_logs as logs,
    aws_cloudwatch as cw,
    aws_sns as sns,
    aws_sns_subscriptions as subs,
    core
)
~~~
2.Lambda関数を定義します。この関数は、Apacheプロセス数が200を超えた場合にSNSトピックにメッセージを送信します。
~~~
class ApacheMonitor(core.Stack):

    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # Create an SNS topic
        topic = sns.Topic(self, "ApacheMonitorTopic")

        # Create a Lambda function
        lambda_fn = _lambda.Function(
            self, "ApacheMonitorFunction",
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.from_asset("lambda"),
            handler="lambda_function.lambda_handler",
            environment={
                'SNS_TOPIC': topic.topic_arn
            }
        )

        # Grant permissions to the Lambda function to publish to the SNS topic
        topic.grant_publish(lambda_fn)

        # Create a CloudWatch alarm to trigger the Lambda function when Apache process count exceeds 200
        alarm = cw.Alarm(
            self, "ApacheProcessCountAlarm",
            metric=cw.Metric(
                namespace="CWAgent",
                metric_name="Processes",
                dimensions={
                    "Name": "ProcessName",
                    "Value": "httpd"
                }
            ),
            comparison_operator=cw.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
            threshold=200,
            evaluation_periods=1,
            datapoints_to_alarm=1,
            period=60,
            treat_missing_data=cw.TreatMissingData.NOT_BREACHING,
        )

        # Add an action to the alarm to trigger the Lambda function
        alarm.add_alarm_action(cw_actions.SnsAction(topic))
~~~
3.Lambda関数が送信するSNSメッセージを処理する関数を定義します。
~~~
def lambda_handler(event, context):
    message = "Apache process count is now over 200!"
    sns_topic_arn = os.environ['SNS_TOPIC']
    sns_client = boto3.client('sns')
    sns_client.publish(TopicArn=sns_topic_arn, Message=message)
~~~
4.AWS CDKアプリケーションを作成し、ApacheMonitorスタックをデプロイします。
~~~
app = core.App()

ApacheMonitor(app, "ApacheMonitorStack")

app.synth()
~~~
これで、Apacheプロセス数が200を超えたときにSlack通知を送信するAWS CDKアプリケーションが完成しました。
デプロイ後、CloudWatchアラームのしきい値を変更することで、Apacheプロセス数が他の値を超えたときにも通知を送信できます。

CloudWatch LogsのSlack通知自動化(AWS-CDK python)
=
CloudWatch LogsのSlack通知を自動化するために、AWS-CDKとPythonを使用することができます。

__CloudWatch Logsからログイベントを取得し、Slackに通知するサンプルコード__
Lambda関数を作成し、SLACK_WEBHOOK_URLという環境変数にSlack WebhookのURLを設定します。次に、Log Groupを指定して、Lambda関数がロググループにアクセスできるようにします。CloudWatchイベントルールを作成して、ロググループの作成、ログストリームの作成、ログイベントの投稿が検出された場合にLambda関数を呼び出すようにします。最後に、Lambda関数に必要なIAMポリシーステートメントを追加します。
~~~
from aws_cdk import (
    core,
    aws_lambda as _lambda,
    aws_logs as logs,
    aws_iam as iam,
    aws_events_targets as targets,
    aws_events as events,
)

class SlackNotifierStack(core.Stack):

    def __init__(self, scope: core.Construct, id: str, slack_webhook_url: str, log_group_name: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # Lambda Function
        lambda_fn = _lambda.Function(
            self, "SlackNotifierFunction",
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.from_asset("lambda"),
            handler="slack_notifier.lambda_handler",
            environment={
                'SLACK_WEBHOOK_URL': slack_webhook_url
            }
        )

        # Log Group
        log_group = logs.LogGroup.from_log_group_name(
            self, "LogGroup", log_group_name)

        # Add permission for lambda to access log group
        log_group.grant_read(lambda_fn)

        # CloudWatch Event Rule
        rule = events.Rule(
            self, "SlackNotifierEventRule",
            event_pattern=events.EventPattern(
                source=["aws.logs"],
                detail_type=["AWS API Call via CloudTrail"],
                detail={
                    "eventSource": [
                        "logs.amazonaws.com"
                    ],
                    "eventName": [
                        "CreateLogGroup",
                        "CreateLogStream",
                        "PutLogEvents"
                    ],
                    "requestParameters": {
                        "logGroupName": [
                            log_group_name
                        ]
                    }
                }
            )
        )

        # Add target for the rule
        rule.add_target(targets.LambdaFunction(lambda_fn))

        # IAM Policy
        lambda_policy = iam.PolicyStatement(
            effect=iam.Effect.ALLOW,
            actions=[
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            resources=[
                log_group.log_group_arn,
                f"{log_group.log_group_arn}:*"
            ]
        )

        # Add policy to lambda function
        lambda_fn.add_to_role_policy(lambda_policy)
~~~
このように、AWS-CDKを使用することで、簡単にCloudWatch Logsからの通知をSlackに自動化することができます。