YouTube Data API (v3)のサンプルコードを解釈する。

#Pythonコードの意味を１つ１つ理解する練習

YouTube Data API (v3)を試してみようと、以下の記事を試してみることにしたものの、
pythonに対する理解不足のため、一個実行するのにかなり苦労しました。

[youtubeAPIを使って特定のチャンネルの動画タイトルを取得](https://qiita.com/y-i-p/items/fdd9aaaccc0ba377261b#google-api-python-client%E3%82%92%E5%85%A5%E3%82%8C%E3%82%8B)

pythonの基礎をおさらいするつもりで、コードの意味を解釈、まとめました。


```python

#!/usr/bin/python

import apiclient.discovery
import apiclient.errors
from googleapiclient.discovery import build
from oauth2client.tools import argparser

# Set DEVELOPER_KEY to the API key value from the APIs & auth > Registered apps
# tab of
#   https://cloud.google.com/console
# Please ensure that you have enabled the YouTube Data API for your project.
DEVELOPER_KEY = "{APIキー}" 
YOUTUBE_API_SERVICE_NAME = "youtube" 
YOUTUBE_API_VERSION = "v3" 

def youtube_search(options):
  youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION,
    developerKey=DEVELOPER_KEY)

  # Call the search.list method to retrieve results matching the specified
  # query term.
  search_response = youtube.search().list(
    q=options.q,
    part="id,snippet",
    maxResults=options.max_results
  ).execute()

  videos = []
  channels = []
  playlists = []

  # Add each result to the appropriate list, and then display the lists of
  # matching videos, channels, and playlists.
  for search_result in search_response.get("items", []):
    if search_result["id"]["kind"] == "youtube#video":
      videos.append("%s (%s)" % (search_result["snippet"]["title"],
                                 search_result["id"]["videoId"]))
    elif search_result["id"]["kind"] == "youtube#channel":
      channels.append("%s (%s)" % (search_result["snippet"]["title"],
                                   search_result["id"]["channelId"]))
    elif search_result["id"]["kind"] == "youtube#playlist":
      playlists.append("%s (%s)" % (search_result["snippet"]["title"],
                                    search_result["id"]["playlistId"]))

  print("Videos:\n", "\n".join(videos), "\n")
  print("Channels:\n", "\n".join(channels), "\n")
  print("Playlists:\n", "\n".join(playlists), "\n")

if __name__ == "__main__":
  argparser.add_argument("--q", help="Search term", default="ヒカキン")
  argparser.add_argument("--max-results", help="Max results", default=25)
  args = argparser.parse_args()

  assert isinstance(args, object)
  youtube_search(args)
```
##まずはYouTube Data API (v3)を使うためにAPIキーの取得
APIキーの取得は、以下のサイトを参考にしました。
[IT連合 - 入門から応用的な使い方まで【技術総合wiki】](https://jpn.itlibra.com/mkhp/module/article/page/displayArticle.php?article_id=11013)

* [APIキー取得方法 ](https://jpn.itlibra.com/mkhp/module/article/page/displayArticle.php?article_id=11013)
* [APIの有効化 ](https://jpn.itlibra.com/mkhp/module/article/page/displayArticle.php?article_id=11015)
ちゃんと有効化まで行わないとAPIキーが使えないので、忘れないよう注意

##コードの内容を1ブロックずつ確認する
```python
#!/usr/bin/python
```
[Pythonのファイルによく書かれている /usr/bin/env python について調べた](http://orememo-v2.tumblr.com/post/90344015142/python-shebang)

```python
import apiclient.discovery
import apiclient.errors
from googleapiclient.discovery import build
from oauth2client.tools import argparser
```
* [[Python] ImportError：apiclient.discoveryという名前のモジュールがありません](https://code.i-harness.com/ja/q/116be65)
* [【Python入門】import・fromでモジュールを読み込む方法](http://programming-study.com/technology/python-import/)  

`apiclient`というモジュールは古いらしく今は`googleapiclient `になっているらしい。そういえば`discovery`というクラスが２回呼ばれているな。。。  


* `oauth2client `とは

>[原文](https://oauth2client.readthedocs.io/en/latest/)
oauth2client makes it easy to interact with OAuth2-protected resources, especially those related to Google APIs. You can also start with general information about using OAuth2 with Google APIs.
>[google翻訳]
oauth2clientを使用すると、OAuth2で保護されたリソース、特にGoogle APIに関連するリソースとのやりとりが容易になります。また、Google APIでOAuth2を使用する際の一般的な情報から始めることもできます。

どうやらgoogleAPIでoauth2認証を行うときに使うモジュールらしいです。ちなみに読み方はOAuth（オーオース）。
そして、Oauth2認証とは、、、

* [一番分かりやすい OAuth の説明](https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be)  
なるほど、前提となるOAuth認証があって、トークンの要求と応答を標準化したものがOAuth２．０ということですね。
* [Python Module Index](https://oauth2client.readthedocs.io/en/latest/py-modindex.html)  
そして、`oauth2client`にも沢山のモジュールがあって`tools`というのは、

>[原文](https://oauth2client.readthedocs.io/en/latest/source/oauth2client.tools.html#module-oauth2client.tools)  
Command-line tools for authenticating via OAuth 2.0
Do the OAuth 2.0 Web Server dance for a command line application. Stores the generated credentials in a common file that is used by other example apps in the same directory.
>[google翻訳]
OAuth 2.0経由で認証するためのコマンドラインツール
OAuth 2.0 Web Serverはコマンドラインアプリケーション用にダンスを行いますか？生成された資格情報を、同じディレクトリ内の他のサンプルアプリケーションが使用する共通ファイルに格納します。  

ダンスはのせいでよくわからない文になってますけど、oauth2clientのコマンドラインツールらしいです。引数を格納してくれるのかな。

* [argparse モジュール](https://docs.python.jp/3/library/argparse.html)  

>argparse モジュールはユーザーフレンドリなコマンドラインインターフェースの作成を簡単にします。プログラムがどんな引数を必要としているのかを定義すると、argparse が sys.argv からそのオプションを解析する方法を見つけ出します。argparse モジュールは自動的にヘルプと使用方法メッセージを生成し、ユーザーが不正な引数をプログラムに指定したときにエラーを発生させます。

コードの後ろの方に出てくる`.add_argument`で引数を追加、格納してくれる。

```python
# Set DEVELOPER_KEY to the API key value from the APIs & auth > Registered apps
# tab of
#   https://cloud.google.com/console
# Please ensure that you have enabled the YouTube Data API for your project.
DEVELOPER_KEY = "{APIキー}" 
YOUTUBE_API_SERVICE_NAME = "youtube" 
YOUTUBE_API_VERSION = "v3" 
```
各種定数を変数に格納。

* `DEVELOPER_KEY `:APIキー
* `YOUTUBE_API_SERVICE_NAME`:使いたいAPIサービス名を入れる。google APIがサポートしているAPIの一覧は　[*Supported Google APIs*](https://developers.google.com/api-client-library/python/apis/)　にまとめられています。
* `YOUTUBE_API_VERSION `:使いたいAPIのバージョン。サービス名一覧と同じページにバージョンも記載されています。

```python
def youtube_search(options):
  youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION,
    developerKey=DEVELOPER_KEY)

  # Call the search.list method to retrieve results matching the specified
  # query term.
  search_response = youtube.search().list(
    q=options.q,
    part="id,snippet",
    maxResults=options.max_results
  ).execute()
```
* `def youtube_search(options):`で`youtube_search`という関数を定義。`options`という引数をとります。
* `youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION,
    developerKey=DEVELOPER_KEY)`では、上で格納した変数を使って、 `youtube`というインスタンスをビルドしています。


>build the service object
Whether you are using simple or authorized API access, you use the **build()** function to create a service object. It takes an API name and API version as arguments. You can see the list of all API versions on the Supported APIs page. The service object is constructed with methods specific to the given API. To create it, do the following:
```from apiclient.discovery import build
service = build('api_name', 'api_version', ...)
```

* [build()の詳細](http://google.github.io/google-api-python-client/docs/epy/googleapiclient.discovery-module.html#build)

```
build(serviceName, version, http=None, discoveryServiceUrl=DISCOVERY_URI, developerKey=None, model=None, requestBuilder=HttpRequest, credentials=None, cache_discovery=True, cache=None) 
```
* ```search_response = youtube.search().list(```では以下の検索結果を変数`search_response`へと格納しています。
 * [.search( )](https://developers.google.com/youtube/v3/docs/search?hl=ja) : 以下のListメソッドで指定した検索を行います。
 * [.list( )](https://developers.google.com/youtube/v3/docs/search/list?hl=ja) :   
     * `q=options.q,`　：q パラメータは検索クエリを指定します。`options`は関数の引数で、今回のコードでは`argparse `によりコマンドライン引数が格納されています。`.q`で格納された引数にアクセスできます。（`"--q"`で指定）
     * `part="id,snippet",`　：「part パラメータには、API レスポンスに含める 1 つまたは複数の search リソースのプロパティをカンマ区切りリストの形式で指定します。このパラメータに指定できる part 名は id と snippet です。」と書いてあるがよくわからんです・・・。
     * `maxResults=options.max_results`　：「maxResults パラメータには、結果セットとして返されるアイテムの最大数を指定します。0 以上 50 以下の値を指定できます。デフォルト値は 5 です。」とあります。`options`は関数の引数で、今回のコードでは`argparse `によりコマンドライン引数が格納されています。`.max_results`で格納された引数にアクセスできます。（`"--max-results"`で指定）

```python
  videos = []
  channels = []
  playlists = []
```
これは、結果を格納するための空リストを準備します。

```python
  # Add each result to the appropriate list, and then display the lists of
  # matching videos, channels, and playlists.
  for search_result in search_response.get("items", []):
    if search_result["id"]["kind"] == "youtube#video":
      videos.append("%s (%s)" % (search_result["snippet"]["title"],
                                 search_result["id"]["videoId"]))
    elif search_result["id"]["kind"] == "youtube#channel":
      channels.append("%s (%s)" % (search_result["snippet"]["title"],
                                   search_result["id"]["channelId"]))
    elif search_result["id"]["kind"] == "youtube#playlist":
      playlists.append("%s (%s)" % (search_result["snippet"]["title"],
                                    search_result["id"]["playlistId"]))
```
* `for search_result in search_response.get("items", []):`  
for文で変数`search_response`から、.getメソッドで第一引数"items"というキーの値を取得します。もし、第一引数で指定したキーが存在しない場合は、第二引数に設定したからのリスト"[ ]"を返します。  
[Pythonの辞書のgetメソッドでキーから値を取得（存在しないキーでもOK）]()

* `if search_result["id"]["kind"] == "youtube#video":`  
検索結果`search_result`の中のリストidの中のkindが”youtube#video”だったら

* `videos.append("%s (%s)" % (search_result["snippet"]["title"],
                                 search_result["id"]["videoId"]))`  
リストvideosの中に文字列"%s(%s)"を追加（append）する。なお、それぞれの%sには、検索結果`search_result`の中のリストsnippetの中のtitleと、リストidの中のchannelIdをいれます。
[Python文字列操作マスター](https://qiita.com/tomotaka_ito/items/594ee1396cf982ba9887)

* `elif search_result["id"]["kind"] == "youtube#channel":`
上と同じ解釈
* `elif search_result["id"]["kind"] == "youtube#playlist":`
* 上と同じ解釈

```python
  print("Videos:\n", "\n".join(videos), "\n")
  print("Channels:\n", "\n".join(channels), "\n")
  print("Playlists:\n", "\n".join(playlists), "\n")
```
* `print("Videos:\n", "\n".join(videos), "\n")`  
 1. ` videos:(改行)`と出力  
 2. リスト`videos`の中身を改行でjoin(連結)して出力　[便利！joinの使い方まとめ](https://www.sejuku.net/blog/41752) 
 3. 最後に改行を出力

* `print("Channels:\n", "\n".join(channels), "\n")`
上と同じ解釈
* `print("Playlists:\n", "\n".join(playlists), "\n")`
上と同じ解釈

```python
if __name__ == "__main__":
  argparser.add_argument("--q", help="Search term", default="ヒカル")
  argparser.add_argument("--max-results", help="Max results", default=25)
  args = argparser.parse_args()
```
* `if __name__ == "__main__":`は、要するに

>「if __name__ == ‘__main__’:」の意味は、「直接実行された場合のみ実行し、それ以外の場合は実行しない」という意味だったのです。 (*[【Python入門】モジュールを扱う際に重要な「if __name__ == ‘__main__’:」とは？](http://programming-study.com/technology/python-if-main/)*)

ということらしいです。コードから、このプログラムは単体実行されることを想定しているようです。

* `argparser.add_argument("--q", help="Search term", default="ヒカル")`  
この記事の上の方で軽く触れたargparseモジュールに関連する記述です。`"--q"`はオプショナル引数といいます。

>オプショナル引数（Optional Arguments）を指定することで、任意のパラメータを取得することができます
*[[Python] argparseモジュールを使って、コマンドライン引数を本格的に扱う](https://www.yoheim.net/blog.php?q=20160509)*

　`help="Search term"`はヘルプ用の文言。`default="ヒカル"`は、引数がなかった時のデフォルト値を指定します。

* `argparser.add_argument("--max-results", help="Max results", default=25)`
上と同じ解釈

* `args = argparser.parse_args()`
引数が入ったオブジェクトをargsとしてインスタンス化します。


```python
  assert isinstance(args, object)
  youtube_search(args)
```
`assert文`は条件式がTrueではない時に例外を投げる文です。[[Python] assert文で超お手軽テスト](https://qiita.com/nannoki/items/15004992b6bb5637a9cd)
`isinstance関数`は変数などのオブジェクトの型を取得して確認したり、特定の型であるかを判定したりする関数です。今回のコードでは変数argsがobjectかどうかを判定しています。[Pythonで型を取得・判定するtype関数, isinstance関数](https://note.nkmk.me/python-type-isinstance/)
` youtube_search(args)`は、上で定義した`youtube_search関数`に引数`args`を渡す処理です。

以上、文脈も体裁もハチャメチャですが１つの簡単なプログラムを真面目に見ていくといろいろな要素が詰まってて面白いなーと思いました。