20210611のPythonに関する記事は28件です。

残プロ 第-10回 ~Line Notifyの通知を華やかに~

今回の実装内容 Line Notifyで絵文字を送る 簡単なスクレイピングを行いグラフ化,画像として送信 ?絵文字.py? pythonで絵文字を扱うには, 直接打ち込む(osに依存?) Unicodeを指定 の2つがあります.前者の方は例えばこちらの絵文字をコピーペーストで完了です.後者の方は同サイト一覧中のUnicodeを利用します. chr(Unicode) 一つ注意点としては,Unicodeは16進数なのでそれを明記する必要があります.以下の例のように先頭に'0x'をつけることで16進数とみなしてくれます. emoji.py message = "Hi! Good Morning!" + chr(0x1F600) Hi! Good Mornig!? 簡単にスクレイピングをしてみる ※過度なスクレイピングは相手側のサーバーに負荷をかけます.必ずtime.sleep等を利用して間隔を空けて使用するようにしてください. 新型コロナについてはスマートニュースさんのサイトが簡素で綺麗にまとめてくれているので,今回はこちらをスクレイピングします. LINEに通知したいのは上の表部分. 都合がいいことに,この部分はhtml内で最初の方に記載されているので,詳細なタグ・クラス属性の指定は必要ありません.ある程度で十分です. 今回は[感染者数, 回復者数, 志望者数]と,その内部に含まれる[全体, 本日, 昨日]をリストで指定しています. scraping.py import requests import time from bs4 import BeautifulSoup def getFromTag(soup, tag_name, class_name): content = soup.find(tag_name, class_=class_name) return content def getFromList(soup, get_list, isText=False, regular=""): contents = [] for tag_name, class_name in get_list: content = getFromTag(soup, tag_name, class_name) if isText and content: content = content.text if regular: content = re.findall(regular, content) content = ",".join(content) contents.append(content) return contents if __name__ == '__main__': prefecture = "osaka" base_url = "https://coronavirus.smartnews.com/jp/" japan_html = requests.get(base_url) japan = BeautifulSoup(japan_html.text, 'html.parser') time.sleep(1) local_html = requests.get(base_url + prefecture) local = BeautifulSoup(local_html.text, 'html.parser') get_list = [("td", "stat confirmed"), ("td", "stat recovered"), ("td", "stat death")] child_get_list = [("div", "today"), ("div", "compare today"), ("div", "compare")] text_japan = [] contents = getFromList(japan, get_list) for c in contents: text_japan.append(getFromList(c, child_get_list, isText=True)) text_local = [] contents = getFromList(local, get_list) for c in contents: text_local.append(getFromList(c, child_get_list, isText=True)) >>> print(text_japan) >>> [['771,590', None, '昨日 +1,935'], ['724,049', None, '昨日 +3,538'], ['13,989', None, '昨日 +64']] 画像とは少し異なりますが(執筆中にサイトが更新されたようです)うまく取れてますね!prefectureに都道府県を代入できるのでお住いの県で試してみてください! 表を描画し画像化 先ほど取得したテキストデータを用いてmatplotlibで表をつくり,画像データ化します. 綺麗な表を作ろうとするとどうしてもプログラムが冗長になってしまいますね... imageconvert.py from matplotlib import pyplot as plt plt.rcParams["figure.subplot.left"] = 0 plt.rcParams["figure.subplot.bottom"] = 0 plt.rcParams["figure.subplot.right"] = 1 plt.rcParams["figure.subplot.top"] = 1 plt.rcParams["font.family"] = "meiryo" plt.rcParams["font.size"] = 16 def text2table(text, header=None, index=None, color=None, imageTitle=""): if index: for t, i in zip(text, index): t.insert(0, i) if header: if index: header = [imageTitle] + header text.insert(0, header) fig, ax = plt.subplots() ax.axis('off') tb = ax.table(cellText=text, cellLoc='center', loc='center') tb.auto_set_font_size(False) cell_height = 1 / len(text) for pos, cell in tb.get_celld().items(): cell.set_height(cell_height) if color: for i in range(len(text)): for j in range(len(text[0])): if color[i][j]: tb[i, j].set_facecolor(color[i][j]) tb[i, j].set_text_props(weight='bold', color='white') if imageTitle: fig.savefig("images/"+ imageTitle + ".png") if __main__ == '__name__': header = ["全体", "本日", "昨日"] index = ["感染者数", "回復者数", "死亡者数"] color = [ ['', 'aqua', 'aqua', 'aqua'], ['red', '', '', ''], ['dodgerblue', '', '', ''], ['black', '', '', '']] _ = text2table(text_japan, header, index, color, "Japan") _ = text2table(text_local, header, index, color, "Kochi") Line Notifyで通知する まずはnotice.pyを用意しておきましょう notice.py import requests TOKEN = 'hoge' url = 'https://notify-api.line.me/api/notify' TOKEN_dict = {'Authorization': 'Bearer ' + TOKEN} def notice(message, img_path=None): message_dict = {'message': message} if img_path: files = {'imageFile': open(img_path, 'rb')} requests.post(url, headers=TOKEN_dict, data=message_dict, files=files) else: requests.post(url, headers=TOKEN_dict, data=message_dict) これでモジュールとして利用できるようになりました.あとは絵文字やスクレイピング,画像を利用して通知内容を華やかにしましょう!! 次回 第-11回は ラズパイで定期的なpythonファイルの実行をしてみようと思います.温度や動作時間等のラズパイ内部情報も通知するようにする予定です.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Matplotlib定量比較-箱ひげ図

箱ひげ図とは 箱ひげ図はデータの分布に関する追加情報を示すものです。 分布の中央値、四分位値の範囲、データの約99%の予想範囲がわかります。 範囲を超える異常値は強調されます。 簡単な箱ひげ図 import matplotlib.pyplot as plt fig, ax = plt.subplots() ax.boxplot([data01['colName'], data02['colName']]) ax.set_xticklabels(['Data01', 'Data02']) ax.set_ylabel('Col Name') plt.show()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RealSenseによるフィルター処理後の距離取得に関して

困ったこと 以下のソースコードでRealSenseで得た画像からピクセル座標指定で深度を取得しようとしました test.py frames = pipeline.wait_for_frames() # realsenseから画像取得 depth_frame = flames.get_depth_frame # 画像から深度画像取得 # フィルター処理 temporal = rs.temporal_filter() depth_frame = temporal.process(depth_frame) # 深度画像からピクセル座標(100, 100)の深度を取得 distance = depth_frame.get_distance(100, 100) すると以下のようなエラーが起きます。 distance = depth_frame.get_distance(100, 100) AttributeError: 'pyrealsense2.pyrealsense2.frame' object has no attribute 'get_distance' フィルター処理しなければこのようなエラーは起きません。 しかしフィルター処理した後のdepth_frameからはget_distance関数が呼べないそうです。 解決方法 フィルター処理した後に以下のコードを追加します。 depth_frame = depth_frame.as_depth_frame() すなわち、正しい最初のソースコードは以下のように修正されます test.py frames = pipeline.wait_for_frames() # realsenseから画像取得 depth_frame = flames.get_depth_frame # 画像から深度画像取得 # フィルター処理 temporal = rs.temporal_filter() depth_frame = temporal.process(depth_frame) depth_frame = depth_frame.as_depth_frame() # ←追加 # 深度画像からピクセル座標(100, 100)の深度を取得 distance = depth_frame.get_distance(100, 100)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python で MQTT (Paho)

pythonにおけるmqttのクライアントであるpaho-mqttの使い方、そしてクライアントとMQTTブローカー間の接続、publish/subscribeなどの機能をPythonで実装する方法を紹介します。 準備 今回はPython 3.6を使います。以下のコマンドでPythonのバージョンを確認できます。 ➜ ~ python3 --version Python 3.6.7 paho (mqttクライアント) について Paho Python Clientには、Python 2.7または3.x上でMQTT v3.1とv3.1.1をサポートするクライアントのクラスが用意されています。また、MQTTサーバーに単発でpublishするようなヘルパー関数も用意されています。 pahoのインストール pip3 install paho-mqtt pipは、Pythonパッケージの管理ツールです。pipを使って、Pythonパッケージの検索、ダウンロード、インストール、アンインストールを行えます。 PythonでMQTTを使う MQTTブローカーに接続 EMQ Xが提供しているテスト用のMQTTブローカーを使用します(このブローカーは、EMQ X Cloudをベースに運用されています)。ブローカーのアクセス情報は以下の通りです。接続先は以下の通りです。 Host: broker.emqx.io TCP Port: 1883 Websocket Port: 8083 pahoをimport from paho.mqtt import client as mqtt_client 接続のパラメータ設定 ブローカーに接続するためのアドレス、ポート、トピックを設定します。今回は、Pythonの関数random.randintを使って、Client IDをランダムに生成します。 broker = 'broker.emqx.io' port = 1883 topic = "/python/mqtt" client_id = f'python-mqtt-{random.randint(0, 1000)}' # username = 'emqx' # password = 'public' 接続する関数を書く on_connectはコールバック関数です。この関数は、クライアントが接続した後に呼び出されます。この関数にあるrc (return code) によって、クライアントが正常に接続したかどうかを判断することができます。このコールバック関数を使ってMQTTクライアントを作成し、broker.emqx.ioに接続するクライアントを作ります。 def connect_mqtt(): def on_connect(client, userdata, flags, rc): if rc == 0: print("Connected to MQTT Broker!") else: print("Failed to connect, return code %d\n", rc) # Set Connecting Client ID client = mqtt_client.Client(client_id) client.username_pw_set(username, password) client.on_connect = on_connect client.connect(broker, port) return client publish (送信) 上記のクライアントを引数に取るpublish関数を作ります。今回は、whileループを使って1秒ごとにトピック/python/mqttにメッセージを送信するようにします。 def publish(client): msg_count = 0 while True: time.sleep(1) msg = f"messages: {msg_count}" result = client.publish(topic, msg) # result: [0, 1] status = result[0] if status == 0: print(f"Send `{msg}` to topic `{topic}`") else: print(f"Failed to send message to topic {topic}") msg_count += 1 subscribe (受信) コールバック関数on_messageを書きます。この関数は、クライアントがブローカーからメッセージを受信したときに呼び出されます。今回は、subscribeしたトピックの名前と受信したメッセージを出力します。 def subscribe(client: mqtt_client): def on_message(client, userdata, msg): print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") client.subscribe(topic) client.on_message = on_message スクリプトを完成させる メッセージをpublishするスクリプト # python 3.6 import random import time from paho.mqtt import client as mqtt_client broker = 'broker.emqx.io' port = 1883 topic = "python/mqtt" # generate client ID with pub prefix randomly client_id = f'python-mqtt-{random.randint(0, 1000)}' # username = 'emqx' # password = 'public' def connect_mqtt(): def on_connect(client, userdata, flags, rc): if rc == 0: print("Connected to MQTT Broker!") else: print("Failed to connect, return code %d\n", rc) client = mqtt_client.Client(client_id) client.username_pw_set(username, password) client.on_connect = on_connect client.connect(broker, port) return client def publish(client): msg_count = 0 while True: time.sleep(1) msg = f"messages: {msg_count}" result = client.publish(topic, msg) # result: [0, 1] status = result[0] if status == 0: print(f"Send `{msg}` to topic `{topic}`") else: print(f"Failed to send message to topic {topic}") msg_count += 1 def run(): client = connect_mqtt() client.loop_start() publish(client) if __name__ == '__main__': run() メッセージをsubscribeするスクリプト # python3.6 import random from paho.mqtt import client as mqtt_client broker = 'broker.emqx.io' port = 1883 topic = "python/mqtt" # generate client ID with pub prefix randomly client_id = f'python-mqtt-{random.randint(0, 100)}' # username = 'emqx' # password = 'public' def connect_mqtt() -> mqtt_client: def on_connect(client, userdata, flags, rc): if rc == 0: print("Connected to MQTT Broker!") else: print("Failed to connect, return code %d\n", rc) client = mqtt_client.Client(client_id) client.username_pw_set(username, password) client.on_connect = on_connect client.connect(broker, port) return client def subscribe(client: mqtt_client): def on_message(client, userdata, msg): print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic") client.subscribe(topic) client.on_message = on_message def run(): client = connect_mqtt() subscribe(client) client.loop_forever() if __name__ == '__main__': run() 実行 publish python3 pub.py subscibe python3 sub.py おわりに この記事は How to use MQTT in Python (Paho)の翻訳です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

僕だってサービスつくって運用できるもん!2

そこでどういったサービスなのか?   それはずばりマッチングサービス! 自分の大学内の国際生と国内生の距離を縮めるためのマッチングアプリが作りたい! 今、あなたは、なんだありきたりじゃん、普通の大学生じゃん、つまんない、どうせ挫折するぜ、あほらしい、ポテチおいし、って思ったでしょ! そんな言わんでもいいやん! まあこれらに対して否定はできない。実際こんなアイデアを考える大学生は何万といるでしょうね。 しかし、実際につくって運用した学生は少ないと思います。 実際大学のいろんな教授にこのアプリについて聞いてみたら、需要は大いにあるみたいだ。 じゃあそれだけで作る意味はあると思う。 自分は自分の大学に何かを残したい。 大学に行く意味はあるのかと疑問が投げかけられる昨今、少しでも意味を見出せることをしようではないか! てことで勉強しまーす。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

僕だってサービスをつくって運用できるもん!

大学三年生になってしまったが、今猛烈にあるサイトを作りたい。そのためには自分だけの力ではできないことは明らかである。そこで、このサイトを通して、地道に作っていくとともに、自分の成長過程も記しておきたい。 どうかよろしく頼みます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoアプリ開発【友達リスト】〜その③〜

はじめに 今回はDjangoを使って友達リスト的なアプリを作成しようと思います。 プロジェクト名はTomodachi github登録 # GitHubに登録 $ git init $ git status $ git add -A $ git commit -m 'first commit' (リモートレポジトリを作成する) $ git remote add origin <RemoteRepositoryURL> $ git push origin main 認証機能(ログイン・ログアウト機能実装) config/urls.py # URLを設定する(後ほどログインビューで実装) path('login', views.login), friendslist/views.py # loginメソッドを実装する(後ほどログインビューで実装) def login(request): context = {} if request.method == 'POST': context['req'] = request.POST return render(request, 'friendslist/login.html', context) friendslist/base_auth.html # 下のリンクボタンがないbase.htmlを作成 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> <title>Tomodachi</title> </head> <body> {% include 'friendslist/snippets/header.html' %} {% block content %} {% endblock %} {% include 'friendslist/snippets/footer.html' %} <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js" integrity="sha384-q2kxQ16AaE6UbzuKqyBE9/u/KzioAlnx2maXQHiDX9d4/zp8Ok3f+M7DPm+Ib6IU" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.min.js" integrity="sha384-pQQkAEnwaBkjpqZ8RU1fF1AKtTcHJwFl3pblpTlHXybJjHpMYo79HY3hIi4NKxyj" crossorigin="anonymous"></script> </body> </html> friendslist/login.html # ログインページを実装(後ほど、ログインビューで実装) {% extends 'friendslist/base_auth.html' %} {% block content %} <div class="container "> <div class="row"> <div class="col-lg-3 m-auto "> <h1 class="display-4 font-italic text-center "><a href="/" class="text-decoration-none">Tomodachi</a></h1> </div> <div class="col-lg-3 m-auto "> <form class="form-signin mx-4" method="POST"> {% csrf_token %} <div class="text-center"> <h1 class="h5 my-4 ">ログイン</h1> </div> <div class="form-label-group my-3"> <input type="email" id="id_email" name="email" class="form-control" placeholder="Email address" required autofocus> </div> <div class="form-label-group my-3"> <input type="password" id="id_password" name="password" class="form-control" placeholder="Password" required> </div> <button class="btn btn-primary btn-block my-3" type="submit">サインイン</button> </form> </div> </div> </div> {% endblock %} ログインビューで実装 config/urls.py # ログインビュー用のURL設定 path('login/', views.Login.as_view()), friendslist/views.py # LoginViewクラスを継承したLoginクラスを作成する from django.contrib.auth.views import LoginView class Login(LoginView): template_name = 'friendslist/login.html' config/settings.py # ログイン用のURLとリダイレクト用のURLを設定 LOGIN_URL = 'login/' LOGIN_REDIRECT_URL = '/' ログアウトビューで実装 config/urls.py # ログアウトビュー用のURL設定 from django.contrib.auth.views import LogoutView path('logout/', LogoutView.as_view()), config/settings.py # ログアウト用のURLとリダイレクト用のURLを設定 LOGOUT_URL = 'logout/' LOGOUT_REDIRECT_URL = 'login/' ログイン状態の確認は以下の記述で確認できる login.html # 以下の記述でログイン状態を確認する {% if user.is_authenticated %} ログイン中です {% else %} ログインしていません {% endif %} LoginViewにしたところログイン出来なかったのでHTMLを変更 login.html # nameを変更 <input type="email" id="id_email" name="email" class="form-control" placeholder="Email address" required autofocus> ↓↓↓↓↓↓ <input type="email" id="id_email" name="username" class="form-control" placeholder="Email address" required autofocus> 認証機能(新規登録機能実装) config/urls.py # 新規登録用のURLを設定する path('signup/', views.signup), friendslist/forms.py # 認証設定をする from django.contrib.auth import get_user_model class UserCreationForm(forms.ModelForm): password = forms.CharField() class Meta: model = get_user_model() fields = ('email',) def clean_password(self): password = self.cleaned_data.get("password") return password def save(self, commit=True): user = super().save(commit=False) user.set_password(self.cleaned_data["password"]) if commit: user.save() return user friendslist/views.py # 新規登録メソッドを実装 from friendslist.forms import FriendForm, UserCreationForm def signup(request): context = {} if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save(commit=False) # user.is_active = False user.save() return redirect('/') return render(request, 'friendslist/signup.html', context) friendslist/signup.html # 新規登録ページを実装する {% extends 'friendslist/base_auth.html' %} {% block content %} <div class="container "> <div class="row"> <div class="col-lg-3 m-auto "> <h1 class="display-4 font-italic text-center "><a href="/" class="text-decoration-none">Tomodachi</a></h1> </div> <div class="col-lg-3 m-auto "> <form class="form-signin mx-4" method="POST"> {% csrf_token %} <div class="text-center"> <h1 class="h5 my-4 ">新規登録</h1> </div> <div class="form-label-group my-3"> <input type="email" id="id_email" name="email" class="form-control" placeholder="Email address" required autofocus> </div> <div class="form-label-group my-3"> <input type="password" id="id_password" name="password" class="form-control" placeholder="Password" required> </div> <button class="btn btn-primary btn-block my-3" type="submit">サインアップ</button> </form> </div> </div> </div> {% endblock %} 新規登録画面とログイン画面をひとまとめにする(リファクタリング) auth.html # login.htmlとsignup.htmlをauth.htmlにまとめる(条件分岐) {% extends 'friendslist/base_auth.html' %} {% block content %} <div class="container "> <div class="row"> <div class="col-lg-3 m-auto "> <h1 class="display-4 font-italic text-center "><a href="/" class="text-decoration-none">Tomodachi</a></h1> </div> <div class="col-lg-3 m-auto "> <form class="form-signin mx-4" method="POST"> {% csrf_token %} <div class="text-center"> <h1 class="h5 my-4 "> {% if 'login' in request.path %} ログイン {% elif 'signup' in request.path %} 新規登録 {% endif %} </h1> </div> <div class="form-label-group my-3"> <input type="email" id="id_email" name="{% if 'login' in request.path %}username{% elif 'signup' in request.path %}email{% endif %}" class="form-control" placeholder="Email address" required autofocus> </div> <div class="form-label-group my-3"> <input type="password" id="id_password" name="password" class="form-control" placeholder="Password" required> </div> <button class="btn btn-primary btn-block my-3" type="submit"> {% if 'login' in request.path %} サインイン {% elif 'signup' in request.path %} サインアップ {% endif %} </button> </form> </div> </div> </div> {% endblock %} friendslist/views.py # render先等をauth.htmlに変更する class Login(LoginView): template_name = 'friendslist/auth.html' def signup(request): context = {} if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save(commit=False) # user.is_active = False user.save() return redirect('/') return render(request, 'friendslist/auth.html', context) サクセス・エラーメッセージを追加する snippets/messages.html # メッセージテンプレートを作成 {% comment %} メッセージです {% endcomment %} {% for message in messages %} <nav class="py-2 {{ message.tags }}"> <div class="container"> {{ message }} </div> </nav> {% endfor %} base.html # messagesテンプレートをインクルード {% include 'friendslist/snippets/messages.html' %} config/settings.py # メッセージの設定をする friendslist/views.py # メッセージの追加 from django.contrib import messages class Login(LoginView): template_name = 'friendslist/auth.html' def form_valid(self, form): messages.success(self.request, 'ログイン完了!!!')  ←☆追加する return super().form_valid(form) def form_invalid(self, form): messages.error(self.request, 'エラーあり')  ←☆追加する return super().form_invalid(form) def signup(request): context = {} if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save(commit=False) # user.is_active = False user.save() messages.success(request, '登録完了!!!')  ←☆追加する return redirect('/') return render(request, 'friendslist/auth.html', context) リンク作成 auth.html # ログイン時新規登録へのリンク、新規登録時ログインでのリンク {% if 'login' in request.path %} <div class="text-center"> <a href="/signup/" class="link-secondary">新規登録はこちら</a> </div> {% elif 'signup' in request.path %} <div class="text-center"> <a href="/login/" class="link-secondary">ログインはこちら</a> </div> {% endif %} snippets/header.html # ログイン時ログアウトリンク、ログアウト時新規登録またはログインでのリンク <nav class="navbar navbar-expand-lg navbar-light bg-warning mb-5"> <div class="container-fluid"> <a class="navbar-brand" href="#">Tomodachi</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link active" aria-current="page" href="/">友達一覧</a> </li> <li class="nav-item"> <a class="nav-link" href="create/">友達登録</a> </li> <li class="nav-item"> <a class="nav-link" href="#">カテゴリ一覧</a> </li> <li class="nav-item"> <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">カテゴリ登録</a> </li> </ul> </div> {% if user.is_authenticated %} <a class="btn btn-sm btn-danger w-25" href="/logout/">ログアウト</a> {% else %} <div class="d-flex"> <a href="/login/" class="btn btn-sm btn-info me-2">ログイン</a> <a href="/signup/" class="btn btn-sm btn-outline-info">新規登録</a> </div> {% endif %} </div> </nav> 管理画面からも新規登録できるようにする friendslist/admin.py # 管理画面に追加 from .forms import UserCreationForm add_form = UserCreationForm さいごに 今回はユーザーの認証機能を実装しました。 新規登録機能 ログイン機能 ログアウト機能
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インタラクティブモードで依存関係をインストールする方法

このチュートリアルは、「How to Develop Function Compute」シリーズの一部です。このチュートリアルでは、対話形式で依存関係をインストールする方法を説明します。 はじめに まず、この記事で紹介されているいくつかの重要な概念について、簡単に説明します。 Function Compute:イベントドリブンなサービスで、ユーザーはサーバーなどのインフラを管理する必要がなく、コードの作成やアップロードに集中することができます。Function Computeはコンピュートリソースを提供し、ユーザーはコードを実行する際に消費されるリソースに対して料金を支払うだけで、より弾力的にコードを実行することができます。Function Computeの詳細については、こちらをご覧ください。 Fun: Funcraftとも呼ばれ、Function Compute、API Gateway、Log Serviceなどのリソースの管理を支援するサーバーレスアプリケーション向けの開発ツールです。Funを使って、template.ymlファイルで指定したリソースを定義することで、リソースの開発、ビルド、デプロイを行うことができます。Funの詳細については、こちらをご覧ください。 注)本記事の操作は、Fun 3.0.0-beta.1以降のバージョンに適用されます。 依存するツール 本プロジェクトの開発は、MacOSをベースにしています。しかし、関係するツールはプラットフォームに依存せず、LinuxやWindowsのOSにも適用できます。例題を進める前に、以下のツールが正しくインストールされ、最新バージョンにアップデートされ、適切に設定されていることを確認してください。 Docker Fun FunはDockerをベースに、ローカル環境をシミュレートします。 MacOSユーザーは、homebrewを使ってこれらのツールをインストールすることができます。 brew cask install docker brew tap vangie/formula brew install fun WindowsとLinuxのユーザーは、この記事を読んで、これらのツールをうまくインストールしてください。 インストールが完了したら、まずfun configコマンドを実行して設定を初期化します。 注:Funのバージョンは必ず3.0.0-beta.1またはそれ以降のバージョンを使用してください。 $ fun --version 3.0.0-beta.1 背景 Function Computeでは、合意された成果物としてZipファイルを使用しており、通常はコードと依存関係を含んでいます。これらの依存関係は、一般的に、apt-getによってインストール・管理されるシステム依存関係と、NPMやpipなどの言語関連パッケージ・マネージャーによってインストールされる言語ランタイム依存関係です。 システム依存性は一般的に移植性がない システムの依存性は、通常、環境に関連しています。例えば、brew install brotliコマンドを実行すると、MacOSを使用する開発マシンにbrotliがインストールされ、Function Computeにパッケージ化されます。その後、ファイルを解凍するbrotliコマンドを実行するために関数を呼び出すと、関数の呼び出しに失敗します。また、Linuxを使用している開発機でも、関数の呼び出しに失敗することがあります。これは、システムが提供するパッケージ・マネージャでインストールされる実行プログラムやダイナミックリンクライブラリが、システムの種類やバージョンに強く関係しているためです。Mac、Windows、Linuxなどの異なるシステムにインストールされたプログラムやファイルは、相互に移植できません。 言語依存は移植不可能 一般的に、言語依存関係はプラットフォームに依存しません。例えば、Node.jsの依存関係をインストールするためにnpm install jszipコマンドを実行したとします。その結果、その依存関係は異なるOSや異なるNode.jsのバージョンで動作する可能性があります。言語プラットフォームの依存関係は、通常、ポータブルです。ただし、例外がある場合もあります。例えば、npm install node-ptyは、ネイティブバインディングを用いた例です。node-ptyモジュールのインストールは、いくつかのC/C ++コードに依存しており、それらはインストールの過程でコンパイルされます。C/C++コードは異なるプラットフォーム間で移植性がありますが、コンパイル後は移植性のないコードになります。 DSLスクリプトの限界 Fun 2.0はDSLファイル(fun.yml)を使って依存関係を一括してインストールすることをサポートしています。fun install —package-type pip tensorflowのように、日常的な開発のためのコマンドラインモードも用意されています。Fun 3.0では、新しいDSLファイル、すなわちFunfileを提供します。FunfileはDockerfileのシンタックスサブセットと考えてください。そのため、Dockerに慣れている開発者はすぐに始められるでしょう。 しかし、fun.yml、Funfile、コマンドラインモードのどれを使っても、開発者にとっては痛いところがあります。現在の環境が不明で、以下のような点が考えられます。 1) どんなソフトウェアがインストールされているか? 2) 特定のディレクトリにどんなファイルがあるか? 3) それぞれのファイルの内容と属性は何か? 開発者には、インタラクションを伴うサンドボックス環境が必要です。しかし、この機能はFun 2.0にはありません。その代わり、ユーザーはfcli sboxコマンドを実行したり、fc-dockerで提供されるイメージを直接使用して、docker run --rm -it -v $(pwd):/code aliyunfc/runtime-python2.7:bashコマンドでコンテナを起動することが多いです。しかし、ユーザーはこれらの複雑なコマンドやパラメータを使用する前に、DockerとFunction Computeをよく知っている必要があります。 これらの問題を解決し、ユーザーの開発体験を向上させるために、Fun 3.0ではfun install sboxサブコマンドを提供しています。 コマンドラインパラメータ 以下のコマンドラインパラメーターを参照してください。 $ fun install sbox --help Usage: fun install sbox [-f|--function <[service/]function>] [-r|--runtime <runtime>] [-i|--interactive] [-e|--env key=val ...] [-e|--cmd <cmd>] Start a local sandbox for installation dependencies or configuration Options: -f, --function <[service/]function> Specify which function to execute installation task. -r, --runtime <runtime> function runtime, avaliable choice is: nodejs6, nodejs8, nodejs10, python2.7, python3, java8, php7.2, custom -i, --interactive run as interactive mode. Keep STDIN open and allocate a pseudo-TTY when in a interactive shell. (default: false) -e, --env <env> environment variable, ex. -e PATH=/code/bin (default: []) -c, --cmd <cmd> command with arguments to execute inside the installation sandbox. -h, --help output usage information クイックスタート Pyzbar_exampleプロジェクトを例に説明します。pyzbar_exampleプロジェクトには以下のファイルが含まれています。 $ tree . . ©À©¤©¤ fun.yml ©À©¤©¤ index.py ©À©¤©¤ qrcode.png ©¸©¤©¤ template.yml 0 directories, 4 files 次のスニペットは、template.ymlファイルの内容を表示しています。 ROSTemplateFormatVersion: '2015-09-01' Transform: 'Aliyun::Serverless-2018-04-03' Resources: pyzbar-srv: Type: 'Aliyun::Serverless::Service' pyzbar-fun: Type: 'Aliyun::Serverless::Function' Properties: Handler: index.handler Runtime: python3 Timeout: 60 MemorySize: 128 CodeUri: . インタラクティブモードでのサンドボックスの有効化 $ fun install sbox -f pyzbar-fun -i using template: template.yml root@fc-python3:/code# ls fun.yml index.py qrcode.png template.yml root@fc-python3:/code# exit exit $ template.ymlが存在するディレクトリで、fun install sboxコマンドを実行します。 -f/-- function:サンドボックスを起動するための関数を指定します。この例では,関数に設定されているランタイムはPython 3です。したがって,Python 3 環境のサンドボックスが有効になります。pyzbar-fun関数に対応するCodeUriディレクトリは,サンドボックス環境内の/codeディレクトリにマウントされており,サンドボックス環境でlsコマンドを実行した後に返されるファイルリストで確認できます。 -i/-- interactive:インタラクティブモードを有効にします。非対話型モードの使い方は後ほど紹介します。 template.ymlファイルが存在しない場合や,template.ymlファイル内の関数が設定されていない場合は,--runtimeパラメータを指定することで,インタラクティブ・モードが起動します。この場合,カレント・ディレクトリはサンドボックス環境の /code ディレクトリにマウントされます。 $ fun install sbox -r nodejs10 -i root@fc-nodejs10:/code# ls fun.yml index.py qrcode.png template.yml root@fc-nodejs10:/code# exit exit $ 上記の方法は、実験用のサンドボックスを一時的に起動する場合に適用されます。 fun-install を使って apt と pip の依存関係をインストールする 以下のコマンドを実行して、apt と pip をインストールします。 $ fun install sbox -f pyzbar-fun -i using template: template.yml root@fc-python3:/code# fun-install apt-get install libblas3 Task => [UNNAMED] => apt-get update (if need) => apt-get install -y -d -o=dir::cache=/code/.fun/tmp libblas3 => bash -c for f in $(ls /code/.fun/tmp/archives/*.deb); do dpkg -x $f /code/.fun/root; mkdir -p /code/.fun/tmp/deb-control/${f%.*}; dpkg -e $f /code/.fun/tmp/deb-control/${f%.*}; if [ -f "/code/.fun/tmp/deb-control/${f%.*}/postinst" ]; then FUN_INSTALL_LOCAL=true /code/.fun/tmp/deb-control/${f%.*}/postinst configure; fi; done; => bash -c 'rm -rf /code/.fun/tmp/archives' root@fc-python3:/code# fun-install --help Usage: fun local [options] [command] build function codes or install related depedencies for Function Compute Options: -h, --help output usage information Commands: apt-get install apt depencies pip install pip depencies build build function codes for Function Compute help [cmd] display help for [cmd] 非対話型モードでのサンドボックスの使用 pyzbar-fun関数のSandboxにプレインストールされたdebパッケージを表示するには、次のコマンドを実行します。 $ fun install sbox -f pyzbar-fun -c 'dpkg -l' using template: template.yml Desired=Unknown/Install/Remove/Purge/Hold | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) ||/ Name Version Architecture Description +++-==================================-================================-============-=============================================================================== ii acl 2.2.52-2 amd64 Access control list utilities ii adduser 3.113+nmu3 all add and remove users and groups ii apt 1.0.9.8.4 amd64 commandline package manager ii apt-utils 1.0.9.8.5 amd64 package management related utility programs ii autoconf 2.69-8 all automatic configure script builder ii automake 1:1.14.1-4+deb8u1 all Tool for generating GNU Standards-compliant Makefiles ...(´Ë´¦Ê¡ÂÔÁËÐí¶àÐÐ) また、以下のコマンドを実行すると、外部コマンドの出力をパイプライン経由で内部システムに送ることができます。 $ echo hello | fun install sbox -r nodejs10 -i -c 'cat -' hello 注:標準入力を受け付けることを示す-iパラメーターを指定します。 概要 fun install sbox コマンドは,fcli sbox コマンドの代替コマンドです。fun install sbox コマンドは、対話モードのサポートに加えて、指定されたランタイムの指定された関数を介してサンドボックスを起動します。次に、fun install sboxコマンドは、インラインコマンドやパイプラインなどの非対話型モードでも使用することができ、スクリプトの作成が容易になります。 本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。 アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。 アリババクラウドの詳細は、こちらからご覧ください。 アリババクラウドジャパン公式ページ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

#(初投稿)Python-streamlitの基礎

環境構築編 pythonとvisual studio codeをインストールした streamlitを'pip install streamlit'で導入した 大変だった点 今回参考にしていた動画ではpipという形を用いていたが、以前自分で入れていたanacondaとシステムがかぶってしまっていてエラーが起きたことが大変だった。 アナコンダをアンインストール後windows powershellでanacondaが適正でないという表示が消えなくて焦った。 過去にアナコンダにパスを通す際に記述したメモファイルを削除することでなおった。 画像編 pip=pythonでフレームワークは"pip install ~as ~"でフレームワークの導入する。 as以降の部分は省略系を入れることで後の記述を略称で記述することが出来る。 同じ階層内の画像は相対参照が出来るから名前のみでOK。 保存しないと反映しない(Ctrl+S) 表編 pandasのフレームワークを用いて表の出し方を学んだ。 st.write: 単純に表示する(動的) st.dataframe: サイズ変更ができる(writeよりも色を塗るなどカスタマイズできる。) st.table: 静的な表が出来る                ↑tableの例 マップ編 st.mapで簡単に表示された。 緯度と経度(latとlon)にrandomを足し合わせることで周辺を点で表せる。 最後に フロントエンドの作業がいらないstreamlitを触ったが、すぐに結果が反映される点がすごく楽しい。 今回は基礎をということで今後より動的に進んでいくことになる。 マークダウンの書き方にも慣れていきたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

import rayでSymbol not found: _Py_GetArgc_Argvが出たときの対処法メモ

こちらの記事を読み、なるほどこれはよさそうだとpip installしてimport rayしたところ表題のエラーが出たのでその対処法のメモです。 エラー発生時の環境 macOS Big Sur 11.3.1 Python 3.8.2 ray 1.4.0 事象 rayをインストールしました。 pip install ray Pythonコード内でインポートしました。 import ray ここでエラーが発生しました。(ファイル名や一部のパスは伏せてあります。) Traceback (most recent call last): File "~~~~.py", line 6, in <module> import ray File "~~~~/.venv/lib/python3.8/site-packages/ray/__init__.py", line 42, in <module> import ray._raylet # noqa: E402 File "python/ray/_raylet.pyx", line 87, in init ray._raylet File "~~~~/.venv/lib/python3.8/site-packages/ray/exceptions.py", line 6, in <module> import setproctitle ImportError: dlopen(~~~~/.venv/lib/python3.8/site-packages/ray/thirdparty_files/setproctitle.cpython-38-darwin.so, 2): Symbol not found: _Py_GetArgcArgv Referenced from: ~~~~/.venv/lib/python3.8/site-packages/ray/thirdparty_files/setproctitle.cpython-38-darwin.so Expected in: flat namespace in ~~~~/.venv/lib/python3.8/site-packages/ray/thirdparty_files/setproctitle.cpython-38-darwin.so 対処法 1. Pythonのバージョンを変更 同様のエラーが以下のIssueに挙がっていたので、それを参考にPythonのバージョンを変更(3.8.2 -> 3.8.5)することにしました。 自分はvenvで作成した仮想環境を使用していたので、以下の記事を参考にPythonのバージョンを変更しました。 pyenvによるPythonバージョンの変更は以下の記事もご参照ください。 2. pip install 'ray[default]' さて、改めてpip install rayでインストールし、import rayを含むPythonファイルを実行すると、エラーは出ませんでしたが以下のようなFutureWarningが出ました。 ~~~~/.venv/lib/python3.8/site-packages/ray/autoscaler/_private/cli_logger.py:57: FutureWarning: Not all Ray CLI dependencies were found. In Ray 1.4+, the Ray CLI, autoscaler, and dashboard will only be usable via `pip install 'ray[default]'`. Please update your install command. warnings.warn( 指示通りに以下のコマンドでインストールし直すと、FutureWarningも出なくなりました。 pip install 'ray[default]' 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python+Docker+Apache Spark】アプリ起動からPysparkでデータ取得までを実践してみる

はじめに とある用件で、Pysparkなるライブラリを知り、早速ながらその感覚を掴むために実践してみましたー! ウェブマーケ、広告運用をやっていた時はHiveやHadoopなどのHDFSに格納された広告配信データをSQLで取得し、ExcelまたはTableauでガリガリ加工して分析していた経験があったので、 今回は、取得する側ではなく、データを用意して入れる側の気持ちになって、挑戦してみました◎ アーキテクチャ(ざっくり) Dockerで、3つのコンテナを立ち上げる。 sparkのマスター 純粋にSparkアプリケーションのリソース・マネージャーとして機能 sparkのワーカー1 sparkのワーカー2 Sparkマスターに送信されたジョブの一部の実行を担当するエグゼキューターを起動 バージョン Python 3.7 Apache Spark 3.0.1 Apache Hadoop 3.2 Docker 20.10.6 docker-compose 1.29.1 ディレクトリ構成 spark-challenge . ├── build │ ├── master │ │ ├── Dockerfile │ │ └── files │ │ └── etc │ │ └── spark-defaults.conf │ └── worker │ └── Dockerfile ├── data │ └── bread_basket.csv ├── docker-compose.yaml ├── main.py └── scripts └── start.sh Dockerアプリ起動方法 spark-challengeディレクトリで、dockerコンテナ起動 $ cd spark-challenge $ docker-compose up -d ソースコード docker-compose.yaml docker version: '3.3' services: spark-master: build: context: ./build/master/ dockerfile: Dockerfile container_name: spark-master ports: - "8080:8080" - "7077:7077" - "4040:4040" - "18080:18080" volumes: - ./:/app environment: - INIT_DAEMON_STEP=setup_spark networks: - ichi_spark_network spark-worker-1: build: context: ./build/worker/ dockerfile: Dockerfile container_name: spark-worker-1 tty: true ports: - "8081:8081" volumes: - ./:/app environment: - SPARK_MASTER=spark://spark-master:7077 networks: - ichi_spark_network spark-worker-2: build: context: ./build/worker/ dockerfile: Dockerfile container_name: spark-worker-2 tty: true ports: - "8082:8081" volumes: - ./:/app environment: - SPARK_MASTER=spark://spark-master:7077 networks: - ichi_spark_network networks: ichi_spark_network: Dockerfile master用 FROM bde2020/spark-master:3.0.1-hadoop3.2 WORKDIR /app ENV LANG="ja_JP.utf8" \ LANGUAGE="ja_JP.utf8" \ LC_ALL="ja_JP.utf8" RUN apk --update add coreutils RUN apk add procps RUN rm -f /usr/bin/python \ && ln -s /usr/bin/python3.7 /usr/bin/python \ && pip3 install --upgrade pip RUN mkdir /tmp/spark-events COPY files/etc /etc RUN mv /etc/spark-defaults.conf /spark/conf worker用 FROM bde2020/spark-master:3.0.1-hadoop3.2 WORKDIR /app ENV LANG="ja_JP.utf8" \ LANGUAGE="ja_JP.utf8" \ LC_ALL="ja_JP.utf8" RUN apk --update add coreutils RUN apk add procps ※イメージは、こちらを使用しました =>bde2020/spark-master main.py main.py from pyspark import SparkConf, SparkContext from pyspark.sql import SparkSession from pyspark.sql.types import * from pyspark.sql.functions import * # SparkContextインスタンスを生成。SparkConfに書いた設定値を渡す。 conf = SparkConf().setAppName("simpleApp").setMaster("spark://spark-master:7077") sc = SparkContext(conf=conf) # SparkSessionのビルダーを使い、SparkSessionインスタンスを生成 spark = SparkSession.builder.config(conf=sc.getConf()).getOrCreate() # スキーマを指定 schema = StructType( [ # At the 3rd variable, set nullable or not StructField("Transaction", IntegerType(), True), StructField("Item", StringType(), True), StructField("date_time", StringType(), True), StructField("period_day", StringType(), True), StructField("weekday_weekend", StringType(), True) ] ) # Set spark.sql.legacy.timeParserPolicy to LEGACY to restore the behavior before Spark 3.0. # *Allow 'EEE MMM dd HH:mm:ss zzz yyyy' format pattern spark.sql("set spark.sql.legacy.timeParserPolicy=LEGACY") # データの読み込み csv_path = "./data/bread_basket.csv" csv_df = ( spark.read.option("header", True) .option("mode", "PERMISSIVE") .option("sep", r",") .option("enforceSchema", False) .schema(schema) .csv(csv_path) ) # データフレーム作成 # Convert date_time into timestamp type. add a column of format_date_time df = csv_df.withColumn("format_date_time", to_timestamp(col("date_time"), "dd-MM-yy HH:mm")) # テンプViewの作成 df.createOrReplaceTempView("BreadBasket") df.printSchema() # クエリの作成 query = f''' SELECT * FROM BreadBasket WHERE Item = 'Coffee' AND format_date_time BETWEEN '2016-11-01 00:00' AND '2016-12-01 00:00' LIMIT 10 ''' # データフレームにクエリ実行結果をセット df = spark.sql(query) # データフレームの表示 df.show() # spark.stop() spark.stop()の説明 SparkSessionインスタンスを生成すると、HTTPサーバーとしての機能が起動する。 終了するときはstopメソッドを呼び出す。 stopすることで、各executorも終了する。 このstopメソッドは、内部でSparkContextのstopを呼び出している。 補足情報 pyspark インストール $ pip install pyspark # ver 3.1.2 Kaggleからデータセットのダウンロード Group Byなんかもできる。 query = f''' SELECT Item, COUNT(*) AS item_count FROM BreadBasket GROUP BY Item ORDER BY item_count DESC ''' df.groupBy("Item").count().sort("count", ascending=False).show() マスターノードとワーカーノードを立ち上げる $ cd spark-challenge $ sh ./scripts/start.sh start.sh中身 start.sh #!/bin/bash docker exec -d spark-master /spark/sbin/start-history-server.sh docker exec -d spark-worker-1 /spark/sbin/start-slave.sh spark://spark-master:7077 docker exec -d spark-worker-2 /spark/sbin/start-slave.sh spark://spark-master:7077 Masterのコンテナに入りmain.pyの実行 $ docker exec -it spark-master bash $ /spark/bin/spark-submit /app/main.py http://localhost:8080/にアクセス!! おおお〜GUI見れましたね!! ワーカーさんが二人、 完了タスクが2つ みたいになってますな。 http://localhost:18080/にアクセス!! ポートが18080です!w 紛らわしいですね!w ヒストリーサーバーも立ち上がっているので、実行履歴の確認が可能です。 ※実行履歴画面があることに後で気づいたので、後付けのキャプチャになります。上記では2件のアプリ実行なのに、こっちでは1件となっていて齟齬があります汗 あまり気にしないでください〜?‍♀️ SparkがMesosあるいはYARN上で実行されている場合でも、アプリケーションのイベントログが存在するならSparkの履歴サーバを使って終了したアプリケーションのUIを構築することが可能です。以下を実行することで履歴サーバを開始することができます: ./sbin/start-history-server.sh これはデフォルトで http://:18080 にwebインタフェースを生成し、未完了あるいは完了済みのアプリケーションと試行をリスト化します。 参照元:監視および計測器 df.printSchema()の実行結果 root |-- Transaction: integer (nullable = true) |-- Item: string (nullable = true) |-- date_time: string (nullable = true) |-- period_day: string (nullable = true) |-- weekday_weekend: string (nullable = true) |-- format_date_time: timestamp (nullable = true) df.show()の実行結果 +-----------+------+----------------+----------+---------------+-------------------+ |Transaction| Item| date_time|period_day|weekday_weekend| format_date_time| +-----------+------+----------------+----------+---------------+-------------------+ | 178|Coffee|01-11-2016 07:51| morning| weekday|2016-11-01 07:51:00| | 179|Coffee|01-11-2016 08:20| morning| weekday|2016-11-01 08:20:00| | 188|Coffee|01-11-2016 10:04| morning| weekday|2016-11-01 10:04:00| | 189|Coffee|01-11-2016 10:29| morning| weekday|2016-11-01 10:29:00| | 190|Coffee|01-11-2016 10:34| morning| weekday|2016-11-01 10:34:00| | 193|Coffee|01-11-2016 11:00| morning| weekday|2016-11-01 11:00:00| | 197|Coffee|01-11-2016 11:10| morning| weekday|2016-11-01 11:10:00| | 197|Coffee|01-11-2016 11:10| morning| weekday|2016-11-01 11:10:00| | 198|Coffee|01-11-2016 11:11| morning| weekday|2016-11-01 11:11:00| | 199|Coffee|01-11-2016 11:13| morning| weekday|2016-11-01 11:13:00| +-----------+------+----------------+----------+---------------+-------------------+ やりましたね!ちゃんとcsvのデータセットが反映されて、無事クエリ結果が返ってきました! ハマったポイント kaggleから落としてきたcsvファイルの名前が、bread basket.csvとなっていて、アンダースコアがなくて、ファイル読み込みで死んだ。うそやんw Dockerfileのln -s /usr/bin/python3.7 /usr/bin/pythonのコマンドところ コンテナ入ってみたら、pythonのversionが3.7になっていて、3.9とかに勝手に変えてしまったら、そんなファイルないよって怒られた ちゃんとそのイメージの中(今回で言うとbde2020/spark-master:3.0.1-hadoop3.2こやつ。)に何が含まれているか確認しないとダメですね yamlの拡張子は、ymlでもいいらしい。違いは3文字か4文字のどちらで表記するかだけ まとめと感想 apache sparkを初めて触って、Pysparkライブラリを使って色々いじる感じとその感覚が養えました! もっともっとデータの処理であったり詳細の部分をこれから詰められればと思っています。 実務で経験積んでみたいな〜〜(´-`).。oO Python面白すぎてワクワクが止まりません〜?✨ 以上、ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python3で多次元リストを作成するときにハマったこと

はじめに 今回は、Pythonでプログラムを書いていた際にハマったことについて備忘録的にメモしておきます。 同じようなポイントにハマった方も少なからずいらっしゃるとおもうので、ご参考ください。 今回やりたかったこと 動的計画法を用いたアルゴリズムを実装する際に、多次元リストを作成する必要がありました。 以下のような感じです。 [[[], [], [],[]], [[],[],[],[]], [[],[],[],[]], [[],[],[],[]],] 要は、リストの要素をリストにしたい。 ハマったポイント 初めに、いつも使うような内包表記で記述しました。 w, h = 3, 4 list_l = [[[]] * w for i in range(h)] print(list_l) #check >>>[[[], [], []], [[], [], []], [[], [], []], [[], [], []]] ここからが問題です。 w, h = 3, 4 list_l = [[[]] * w for i in range(h)] #print(list_l) for i in range(w): list_l[0][i].append(i) print(list_l) >>> [[[0, 1, 2], [0, 1, 2], [0, 1, 2]], [[], [], []], [[], [], []], [[], [], []]] for文で1つ目のリストに対して、1つずつ.appendを実行すると、すべてのリストが一括で更新されてしまいます。 問題点 問題は以下の部分です。 [[]] * w これは、複数のリストを宣言するのではなく、複製していることになっています。なので、そのどれかに要素を追加しようとすると、ほかのリストも同様の操作をされてしまいます。 解決策 w, h = 3, 4 list_l =[[[] for i in range(w)] for j in range(h)] #print(list_l) for i in range(w): list_l[0][i].append(i) print(list_l) >>>[[[0], [1], [2]], [[], [], []], [[], [], []], [[], [], []]]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【tweepy】ツイート結果の取得

はじめに  tweepyのupdate_status()やupdate_with_media()でツイートしたときに,エラーならエラーと表示されるが成功したときに何も出ないので,成功したら成功したと表示させたい。 実装  エラーのステータスはtweepy.TweepError.reasonに入ってるらしい。 try: api.update_status(tweet) except tweepy.TweepError as e: print(e.reason) って感じにすると [{'code': 187, 'message': 'Status is a duplicate.'}] って返ってくるからキーを指定すればcodeだけ取り出せそうだけどどうやらこれ丸ごとstringらしいので,エラー文をそのまま条件に使用して下のような感じで実装(するのが手っ取り早そう)。  エラーになる要因はたいていAPIのリクエスト制限に引っかかるか重複したツイートしてるか(テスト投稿でやっちゃいがち)だと思うのでその二つは表示できるようにしてそれ以外のエラーはそのままって感じで。 try: api.update_status(self.tweet) except tweepy.TweepError as e: if e.reason == "[{'code': 88, 'message': 'Rate limit exceeded'}]": print("エラー;リクエスト上限に達しました。15分間待ってください") if e.reason == "[{'code': 187, 'message': 'Status is a duplicate.'}]": print("エラー:ツイートが重複しています") else: print(e) else: print("ツイート成功") tkinterでGUIにする場合はprint部分をtk.Labelのtextveriable=に使用してるtk.StringVar()に.setするよう置き換えればOK
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【tweepy】ツイート結果(TweepError)の取得

はじめに  tweepyのupdate_status()やupdate_with_media()でツイートしたときに,エラーならエラーと表示されるが成功したときに何も出ないので,成功したら成功したと表示させたい。 実装  エラーのステータスはtweepy.TweepError.reasonに入ってるらしい。 try: api.update_status(tweet) except tweepy.TweepError as e: print(e.reason) って感じにすると [{'code': 187, 'message': 'Status is a duplicate.'}] って返ってくるからキーを指定すればcodeだけ取り出せそうだけどどうやらこれ丸ごとstringらしいので,エラー文をそのまま条件に使用して下のような感じで実装(するのが手っ取り早そう)。  エラーになる要因はたいていAPIのリクエスト制限に引っかかるか重複したツイートしてるか(テスト投稿でやっちゃいがち)だと思うのでその二つは表示できるようにしてそれ以外のエラーはそのままって感じで。 try: api.update_status(self.tweet) except tweepy.TweepError as e: if e.reason == "[{'code': 88, 'message': 'Rate limit exceeded'}]": print("エラー;リクエスト上限に達しました。15分間待ってください") if e.reason == "[{'code': 187, 'message': 'Status is a duplicate.'}]": print("エラー:ツイートが重複しています") else: print(e) else: print("ツイート成功") tkinterでGUIにする場合はprint部分をtk.Labelのtextveriable=に使用してるtk.StringVar()に.setするよう置き換えればOK
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django公式チュートリアル(1~4)で分からない所、徹底的に調べた。

最初に 本格的なWebアプリケーションを作成したいのでPythonのフレームワークDjango(読み方:ジャンゴらしい。ディーどこ行った。)についてチュートリアルをこなしながら学んで行こうと思う。実際に作成するアプリは質問に対して回答して投票を表示するアプリになる。 この記事は公式チュートリアルの1〜4までに気になった事躓いた事をまとめていく。全部で1〜7まであるが4までにアプリは完成する。 5からはテストコード等を書いていくのでボリュームが多くなるため、前編として今回の記事を投稿する。後編も必ず書こうと思う。 今回のチュートリアルで作成したもの 質問一覧のページがあって、そこから質問に対して投票を行う。その後今まで投票された数を表示するページリダイレクトされる。 VSCodeのPython用の拡張機能をインストールする。 コードを書くにあたって構文エラーは事前に無くしたいので、拡張機能をインストールする。マイクロソフトがPython用に提供しているものがあるのでそちらをインストールする。 自分はanaconda環境での使用をしているので下記の通知が出てきた。 terminal.integrated.inheritEnv を false にした方が良いらしい。 We noticed you're using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we recommend that you let the Python extension change "terminal.integrated.inheritEnv" to false in your user settings. VSCodeの code > preference > settings に terminal.integrated.inheritEnv と入力して出てきたチェックを外す。 【Mac/Python】VSCodeターミナル動作が通常ターミナルと違う時 | ゆうきのせかい それだけだと Import "django.contrib" could not be resolved from source という警告がでたままなので、赤枠の箇所をクリックしてDjangoをインストールした環境を選択すると警告文が消えます。 インストール 好きなフォルダーを作成して、そこに開発していく。Pythonの環境構築は自分は下記のように行っている。 https://techblog-pink.vercel.app/posts/cc111706c3167 anacondaで仮想環境を作成して、Djangoをインストールしていく。 pip install Django 開発を始めたいフォルダに移動して下記を実行する。 django-admin startproject mysite mysiteというディレクトリが生成される。 mysite/ manage.py mysite/ __init__.py settings.py urls.py asgi.py wsgi.py フォルダはこのような構成になっている。 mysite/mysite となっているのが不思議だ。外側の mysite はDjangoのシステム的には何でも良いらしい。 urls.py はプロジェクトのURLを宣言する。目次のような機能を提供する。 サーバを起動する。 実行すると http://127.0.0.1:8000/ でサイトにアクセス出来るようになる。 最初はデータベース等の設定をしていないので、ターミナルに警告が表示されるが他に問題がなければアクセス出来る。 ctr + c でサーバを終了することが出来る。 python manage.py runserver GitHubで管理する これから開発していくので、Githubにコードをあげて進捗を管理したい。しかし、このままだと settings.py に書かれた SECRET_KEY も一緒にアップロードしてしまうので別ファイル local_settings.py に SECRET_KEY を書く。ちなみに私は気付かずに一度GitHubに、そのままアップしてしまった。そのため新たに SECRET_KEY を作成する手間が掛かる。 local_settings.py SECRET_KEY = 'settings.pyにあったシークレットキーまたは、新たに作成したもの' この変数を settings.py で読み込んでいく。下記のコードを追加する。 try: # 同じ階層のlocal_settingsファイルからSECRET_KEYをkeyとして読み込む。 # 参考記事等では .local_settingsとなっているが、local_settingsはファイルなので必要ない # 将来的に複雑にファイルを分ける必要が出てフォルダにする場合は .が必要となる。 # ダメだった .local_settingsが正しかった。Django環境だとパッケージとして見なされるのか... from .local_settings import SECRET_KEY as key except ImportError: pass # シークレットキーの部分を読み込んだ変数に置き換える。 SECRET_KEY = key 実際に test.py と test2.py を同じ階層に作成してどのように読み込まれるか調べてみた。 test.py hello = "hello" test2.py # .testとするとImportError: attempted relative import with no known parent package # と表示される。 # asを付けない場合は helloで読み込まれる。 from test import hello as A print(A) settings.py と同じ環境に出来ていると思ったが、全然違った。 local_settings.py は パッケージとして認識されるが、 test.py はパッケージとして認識されないので . を使用するとエラーになる。そもそも python test2.py と直接実行しているので settings.py とは違う実行状況になる。 詳しくはここで回答を頂いている。 SECRET_KEYの作成 自分は知らずにシークレットキーをGitHubにあげてしまったので新たに作り直す必要があるのでシークレットキーを生成してくれるプログラムを実行する。これを直接ターミナルで実行するとシークレットキーが生成されるのでそれを local_settings.py に貼り付ける。 get_random_secret_key.py from django.core.management.utils import get_random_secret_key secret_key = get_random_secret_key() text = 'SECRET_KEY = \'{0}\''.format(secret_key) print(text) これでようやくチュートリアルに専念してコードを書き進める事が出来る。 プロジェクトとアプリ Djangoではプロジェクトの中にアプリが含まれる。なので特定のDjangoで作成されたWebサイト全体をプロジェクトと呼び、その中に含まれる小規模な投票アプリ、ログシステムをアプリと呼ぶ。 アプリを作成する。 manage.py と同じ階層に移動して python manage.py startapp polls を実行すると polls というフォルダが生成される。 polls/ __init__.py admin.py apps.py migrations/ __init__.py models.py tests.py views.py これでプロジェクトにpollsというアプリが作成された事になる。 Viewを作成する。 URLからパスにアクセスがあって、その際に実行する関数がViewになるここでHTMLファイルを返したりと処理を決めることが出来る。 views.py に記述する。 def detail(request, question_id): return HttpResponse("You're looking at question %s." % question_id) def results(request, question_id): response = "You're looking at the results of question %s." return HttpResponse(response % question_id) def vote(request, question_id): return HttpResponse("You're voting on question %s." % question_id) viewメソッドの第一引数には必ずHttpRequestクラスを受け取る。 引数 request には今ユーザがアクセスしているURLやIPアドレスなどの情報が入ってくる。そして戻り値としては HttpResponseクラスを返す必要がある。 実際にHttpResponseとHttpRequestの中身がどんな感じになっているのか気になる人は下記のページで確認できます。 ざっくりですが、 views.py があって実行されるとクラスの中身はこんな感じに格納されているみたいです。 def sample(request): print(request) response = HttpResponse('') print(response) return response # 実行結果 # <WSGIRequest: GET '/hello/'> # <HttpResponse status_code=200, "text/html; charset=utf-8"> Djangoはリクエストを受け取ってレスポンスを返しているだけです【詳しく解説】 views.py だけではURLと紐づいていないので URLconfを作成する。 URLconf urls.py を作成する URLconfを作成するには urls.py というファイルを views.py と同じ階層に作成する。mysiteフォルダ内には urls.py がすでにあるので pollsフォルダ内に作成する。中身はこんな感じになる。 include()を使ってアプリのURLを結び付ける。 polls/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('<int:question_id>/', views.detail, name='detail'), path('<int:question_id>/results/', views.results, name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), ] <int:question_id> が views.py 関数の引数として渡される。この <> を使用すると、URLの一部がキャプチャされ渡される。文字列 :quesiton_id> は一致するパターンを定義し、 <int: の部分はURLパスに当てはまる値の型を指定している。なので <str:, <slug: などもある。 そして、これをmysiteフォルダ内の urls.py に結びつけてあげる必要がある。一応こっちが最初に読み込まれるのでここに後から追加したアプリのURLを include() を使って追加していくイメージになる。 mysite/urls.py from django.contrib import admin from django.urls import include, path urlpatterns = [ path('polls/', include('polls.urls')), path('admin/', admin.site.urls), ] これでpollsアプリのURLを結び付けることができた。サーバを起動して http://localhost:8000/polls/ にアクセスするとViewが返されるようになる。 path()の引数 4つの引数を受け取ることが出来る。そのうち route と view の2つは必須で残り kwargs と name は省略出来る。 route:URLパターンを含む文字列が入る。リクエストを処理する際に urlpatterns を順番にみて最初にマッチしたものを取り出す。このパターンはGET、POSTのパラメータに影響は受けないあくまでURLパスだけを見る。 view:URLがマッチしたら、そこに付随するView関数を返す仕組みになっている。 kwargs:任意のキーワード引数を辞書としてView関数に渡せる。 name:URLに名前を付ける事で reverse() を使って呼び出せるようになり、htmlのテンプレートでformに指定するURLを変更する際に動的にURLが変更されるようになる。 polls/urls.py がこんな感じだとして from django.urls import path from . import views import re urlpatterns = [ path('', views.index, name='index'), path('<int:number>', views.subView, name='suburl'), ] 試しに下記の views.py を実行してみる。 from django.http import HttpResponse from django.urls import reverse def index(request): urlName = reverse('index') print(urlName) return HttpResponse("Hello, world. You're at the polls index.{0}".format(urlName)) # 実行結果 # /polls/ こうすると例えば pollllllls/urls.py とURLを変更してもコードを変更する必要がないので、変更に強いコードになります。 ※pathのnameについて python、djangoのurls.pyで設定するnameってなんやねん?? - Qiita DjangoのURL ユーザがDjangoで作られたサイトにアクセスした際にどのような処理が走るのか。 ROOT_URLCONFに設定されているURLを確認する。(HttpRequestオブジェクトにurlconfという属性が設定されている場合はその値をROOT_URLCONFとする。) urlpatternsという名前の変数を探す。この変数値は django.urls.path() または django.urls.re_path() インスタンスのsequenceでなければならない。 urlpatternsから順番に要求されたURLパターンを探す。 マッチしたらViewを返す。 マッチしなかったらエラーハンドリングビューを返す。 URLconfのサンプル pathの左側がマッチするURL(route), 右側がマッチしたら呼び出される関数(view) from django.urls import path from . import views urlpatterns = [ # /articles/2003/にアクセスした場合 # Views.special_case_2003(request)を呼び出す。最後の/もしっかりないとマッチしない。 # 引数としてrequestが関数に渡る。 path('articles/2003/', views.special_case_2003), path('articles/<int:year>/', views.year_archive), path('articles/<int:year>/<int:month>/', views.month_archive), path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail), ] /articles/2005/03/ というアクセスがあった場合上記のパターンの中からviews.month_archive(request, year=2005, month=3) という views.py (上記で記したviews.pyとは別で例としてあげてるurls.pyに対応するviews.pyがあったらという話で見てもらいたい。)に書かれたviewメソッドに引数を渡して呼び出す事になる。引数 request にはHttpRequestクラスが入り 今ユーザがアクセスしているURLやIPアドレスなどの情報が入ってる。 /articles/2003/03/building-a-django-site/ なら最後のパターンにマッチして、このように views.article_detail(request, year=2003, month=3, slug="building-a-django-site") 関数を呼び出す。 タイムゾーンの設定 デフォルトではUTCと世界標準時間になっているので、ここで日本時間に変更しておきたいと思う。 settings.py の TIME_ZONE = 'UTC' を TIME_ZONE = 'Asia/Tokyo' に変更する。 データベースの作成 ここでデータベース用語についてまとめて置く。 カラム:縦列の事を指し、別名では列と呼ばれる。 カラム名:列全体に付けられた名前 フィールド:データが入っている場所。 フィールド名:そのデータが入っているカラム名を指す。 項目名:フィールド名を指す。 テーブル:Excelでいうシートのようなもの。 レコード:データそのものを指す言葉になる。もう一つは横列の事を指し、行と呼ばれる。そして行をロウと呼ぶこともある。 これからデータベースを設定していく、チュートリアルの段階なのでまずは複雑な設定の必要がないSQLiteを使用していく。 python manage.py migrate 実行すると settings.py に書かれた INSTALLED_APPS の設定を参照して mysite/settings.py ファイルのデータベース設定に従って必要な全てのデータベーステーブルを作成する。 migrate(マイグレート)するとは データベースを削除してから作り直すと、DBに保存されている情報が全て削除されてしまう。こういった事態を回避する方法として、データベースマイグレーションを行う方法が生まれた。マイグレーションとは、DBに保存されているデータを保持したまま、テーブルの作成やカラムの変更などを行うことが出来る。 モデルの作成 models.py models.py はデータを保存したり、取り出したりするときの設定を記録するファイルになる。データベースの取扱説明書と書かれることが多い。 models.py からデータベースが作成される流れ models.pyファイルでモデル(データベースの型)を作成する。 migrationファイルを作成する。 migrate(最終的にはおそらくSQL文に変換されて、データベースに命令を出してデータベースを作成する。)する。ここで上記のファイルを元にデータベースが作成される。 つまりモデルはデータベースのレイアウトとそれに付随するメタデータになる。 簡単な例を示す。(チュートリアルとは関係ないモデル) # modelsモジュールを読み込むこれはデータベースを作成するのに必要な機能が格納されている。 from django.db import models # データベース作成機能を継承してモデルを書き込んでいく。 class BookModel(models.Model): # booknameという文字を入力することが出来るフィールドを作成する命令をだす。 # bookname = models.CharField(max_length = 50)とすると50文字までと制限をかけれる。 # CharFieldには必須の引数がありmax_lengthを設定しないといけない。 bookname = models.CharField(max_length = 50) # CharFieldとほぼ同じで文字列を扱うがTextFieldの方がデータの読み出し等にコストがかかるらしい。 summary = models.TextField() # 整数値を入れるフィールドを作成する。 rating = models.IntegerField() ForeignKey(外部制約キー) これを使ってこの後2つのモデルを双方向に参照できるようにするのだが、その前にデータベースにおいて外部制約キーまたは外部キーとも呼ばれるがどのような役割を果たしているのかみていこうと思う。 外部キーとは関係データベースにおいてデータの整合性を保つための制約(参照整合性制約) 外部キーに設定されている列(子テーブルのカラム)には、参照先となるテーブルの列内(親テーブルのカラム内)に存在している値しか設定できない。 そのため、外部キーに設定されている子テーブルの列内に親テーブルの列内に存在しない値を追加しようとするとエラーになる。 なので新しく値を追加したい場合は一度、親テーブルで追加する必要がある。 このように制約を結ぶ事でデータの整合性を保つ事ができる。 Djangoでは foreign keyが設定されている方が子テーブルになる。 今度は少し複雑なモデルを見ていく。 図のようなQuestionとChoiceという2つのモデルを作成する。 ChoiceにQuestionが ForeignKey を使って紐ずけられている。 on_delete=models.CASCADE は紐づけられたモデルが削除される際にどのような動作をするかを決める事が出来る。削除された後そのモデルだった部分をNullで埋めたり、そもそも削除できないようにしたりと出来る。 CASCADE は紐づけられた側のモデルで関連するオブジェクトも削除するという動きになる。なので Questionが削除されたら、Questionと関連のあるChoice側のオブジェクトも削除するような動作を取る。 詳しくはここの記事が分かりやすい。 Django2.0から必須になったon_deleteの使い方 - DjangoBrothers from django.db import models class Question(models.Model): # Question textというフィードを作成してそこに入る文字列は200文字までと制限している。 question_text = models.CharField(max_length=200) # 基本的には変数名がフィールド名として使用されるが、引数で文字列を渡す事でフィールド名設定する事が出来る。 pub_date = models.DateTimeField('date published') class Choice(models.Model): # Question ← → Choiseと双方向のやりとりが可能となる。 question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) # Votesフィールドは整数値を受け付ける。最初は0が入る。 votes = models.IntegerField(default=0) モデル間の双方向やりとりについて 【Django】1対多の関係( related_name, _set.all() )について - Qiita モデルを作成したのでデータベースにマイグレート(モデルを元にデータベースのレイアウトを作成するデータを追加するわけではない。)していく migrationファイルを作成する前に、新たに作成したアプリpollsを伝える必要がある。 settings.py の INSTALLED_APPS の配列に 'polls.apps.PollsConfig', を追加する。 次にmigrationファイルを作成する。 新たにファイルを作成する必要はなくターミナルでコマンド実行する。 python manage.py makemigrations polls 実行すると models.py を元にmigrationファイルが生成される。 python manage.py migrate 実行するとmigrationファイルを元にデータベースが作成される。 試しにコマンドラインからDjango shellを通してデータベースを変更したりしてみる。 シェルに入るには下記のコマンドを実行する。 python manage.py shell するとPythonコード >>> を書ける状態になるのでここからデータベースにアクセスするコードを書く。 # 作成したモデルを読み込む from polls.models import Choice, Question # 格納されたデータを確認する。まだ追加していないので、空になっている。 Question.objects.all() from django.utils import timezone # モデルにデータを追加する。 q = Question(question_text="What's new?", pub_date=timezone.now()) # データベースに保存する。 q.save() q.id q.question_text q.pub_date # データの上書き q.question_text = "What's up?" q.save() # データが格納されているのが確認できる。 Question.objects.all() # 実行結果:<QuerySet [<Question: Question object (1)>]> このままではadminページでオブジェクト名が Question object (1) と表示され分かりにくいので 特殊メソッド __str__() をモデルに追加する。 あと追加で was_published_recently(self) メソッドを書きました。 データが最近追加されたかどうかを判定するメソッドで True or False で返します。 class Question(models.Model): # クラス変数を定義する。データベースフィールドを表現している。 # Charフィールドは文字のフィールド question_text = models.CharField(max_length=200) # 日時のフィールド pub_date = models.DateTimeField('date published') def __str__(self): # インスタンスを生成して、printした際にここが実行される。 # シェルで表示されるオブジェクトに質問名が使われるだけでなく # adminでオブジェクトを表現する際にも使用されるので追加する必要がある。 return self.question_text def was_published_recently(self): now = timezone.now() # now - datetime.timedelta(days=1)は今の時間から一日引いた日付を出す。 # 2021-05-19 23:29:56.216634こんな感じの値になる。 # pub_dateが現在時刻より過去で現在時刻から一日以内の場合はTrueを返すメソッド return now - datetime.timedelta(days=1) <= self.pub_date <= now class Choice(models.Model): # これはChoiceがQuestionに関連付けられている事を伝えている。 # データベースの多対一、多対多、一対一のようなデータベースリレーションシップに対応する。 # Question ← → Choiseと双方向のやりとりが可能となる。 question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0) def __str__(self): return self.choice_text すると下記のように表示されるので、 何のデータが入っているのか分かり易くなった。 Djangoのshell内でも下記のようにオブジェクトの中身が分かり易くなった。 from polls.models import Choice, Question Question.objects.all() # 実行結果:<QuerySet [<Question: What's up?>]> # 続けて色々な関数を試してデータベースから # データを取得してみる。 # filterをかけてデータを取得する。 # idはデータベースにデータを追加した際に1から順番に自動で割り振られる。 Question.objects.filter(id=1) # 実行結果:<QuerySet [<Question: What's up?>]> # Questionオブジェクトのquestion_textフィールドで"What"から # 始まるデータを取得する。 Question.objects.filter(question_text__startswith="What") # 実行結果:<QuerySet [<Question: What's up?>]> from django.utils import timezone # 今年作成されたデータを取得する。 current_year = timezone.now().year Question.objects.get(pub_date__year=current_year) # id 2のデータを取得する。 # filterで指定しなくてもgetでも取得できる。 Question.objects.get(id=2) # 実行結果はない場合はエラーになります。 # プライマリーキーと呼ばれるものでいまいちidとの違いが分からない。 # 取得するデータはidの場合と同じになる。 q = Question.objects.get(pk=1) q.was_published_recently() # 実行結果:True # ChoiseはQuestionと関連付けられてるのでQuestionからも # データにアクセスする事ができる。 # Choice側にはまだデータを入れてないので結果は何も表示されない。 # Choicecは質問に対する回答の選択肢をデータとして持つ。 # qには今What's upという質問が入っているので、それに対しての # 選択肢を作成した。 q.choice_set.all() q.choice_set.create(choice_text="Not much", votes=0) q.choice_set.create(choice_text="The sky", votes=0) c = q.choice_set.create(choice_text='Just hacking again', votes=0) # 選択肢が関連づけられている質問を返す。 c.question # 実行結果:<Question: What's up?> q.choice_set.all() # 選択肢が何個あるか数える。 q.choice_set.count() Choice.objects.filter(question__pub_date__year=current_year) # 実行結果:<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> # just hackingの選択肢だけ削除する。 c = q.choice_set.filter(choice_text__startswith="Just hacking") c.delete() pkとidの違い pkは primary key の略で、データベースでは 主キー と呼ばれている。主キーはテーブルで一意の値を取る。 どのレコードを主キーにするかはフィールド名を定義する時に primary_key=True を付ければ設定できる。 codeというフィールドに入る値を主キーとする。 code = models.CharField(max_length=10, primary_key=True) djangoの場合はModel(=テーブル)には必ず1つの主キー用のフィールドが必要になる。ユーザが定義しない場合はidという名前のAutoFiekd(int型の連番1~n)が作成される。 そのため、pkキーを定義しない場合はpkはidのショートカットになる。 pkキーを上記の code のように定義した場合はidは作成されない。 SQLに直接アクセスする。(おまけ) sqllite3を使用してデータベースを作成している場合はmysiteフォルダ内にデータベースのファイルが生成されていると思うので、下記のコマンドからSQLで操作するシェルに入る事ができる。 sqlite3 db.sqlite3 # シェル内での操作 # 作成されたテーブル一覧を確認できる。 >>> .table auth_group django_admin_log auth_group_permissions django_content_type auth_permission django_migrations auth_user django_session # 先ほどモデルから作成されたテーブル auth_user_groups polls_choice auth_user_user_permissions polls_question # 構造を確認できるみたいだけど、見てもよく分からなかった。 >>> .schema # シェルから抜ける。 >>> .quit モデルからデータベースを操作する事ができたので、次に先ほど登場したadminページにアクセスしたいと思う。 adminページアクセスする。 ログインが必要なのでユーザを下記のコマンドから作成する。 python manage.py createsuperuser # 実行すると下記の入力画面が登場する。 Username:名前を入力 Email address:@と.comがあれば架空で良い Password: Password(again): ユーザを作成したら開発サーバを起動してhttp://127.0.0.1:8000/admin/ にアクセスするとログイン画面となるのでログインする。 するとそこから作成したモデルを閲覧したりデータを追加したりできる。 ChoiceモデルからはQuestionを選択して使用することしか出来ないのが確認できる。 右側にある + ボタンを押すと Questionのページに飛びそこから新しい質問を追加することはできる。 Views.pyからデータベースの値を取得する 先ほどDjango shellで使用したPythonコードを使って、 views.py にデータを取得していく。 # 最後はHttpResponseを返す必要があるのでimportする。 from django.http import HttpResponse # データベースを操作するためにモデルを読み込んでおく。 from .models import Question def index(request): # データベースから最新5件を取得する。 # こんな感じのデータになる。<QuerySet [<Question: test3>, <Question: hello>, <Question: what's up?>]> latest_question_list = Question.objects.order_by('-pub_date')[:5] # "test3, hello, what's up?"区切った文字列にしてHttpResponseに渡す。 output = ', '.join([q.question_text for q in latest_question_list]) return HttpResponse(output) # Leave the rest of the views (detail, results, vote) unchanged Viewとページデザインを切り離す Viewではデータの取得や操作を専門的に行ってもらい、そのデータをテンプレート(htmlにPythonの変数を入れられる。)に渡してページをレンダリングしてもらうようにする。 pollsディレクトリの中に、templatesディレクトリを作成する。システムでそのディレクトリを認識する。作成した templatesディレクトリにpollsフォルダを作成する。なので polls/templates/polls みたいなディレクトリが完成する。その中にテンプレート index.html を作成する。なぜ templates/polls とするのかというとDjangoは名前がマッチした最初のテンプレートを使用するので、もし異なるアプリケーションの中に同じ名前のテンプレートがあるとそちらを読み込む。それを回避するために名前空間(所属する領域)を与えている。 views.py をテンプレートにデータを渡せるように書き換える。 from django.http import HttpResponse from django.template import loader from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] # テンプレートを読み込む template = loader.get_template('polls/index.html') # 辞書型に最新5件のデータを格納する。 context = { 'latest_question_list': latest_question_list, } # 辞書型のデータをテンプレートに渡してページを作成する。その結果をHttpResponseに返す。 return HttpResponse(template.render(context, request)) view.pyをさらに短くする。 from django.template import loader from django.http import HttpResponse を使わない書き方 より簡素にする事が出来る。 その場合、 render() 関数は第一引数に requestオブジェクト , 第二引数に テンプレート名 , 第三引数に 辞書型(テンプレートに渡したいデータ) を記述する。 from django.shortcuts import render from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] context = {'latest_question_list': latest_question_list} return render(request, 'polls/index.html', context) 404エラーを出力する。 from django.http import Http404 from django.shortcuts import render from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] context = {'latest_question_list': latest_question_list} return render(request, 'polls/index.html', context) # 質問の詳細ページのビュー def detail(request, question_id): try: # アクセスのあったURLでpkの値が変わる。/polls/1/なら1になる。 # データベースでエラーになるとHttp404を出力する。 question = Question.objects.get(pk=question_id) except Question.DoesNotExist: raise Http404("Question does not exist") return render(request, 'polls/detail.html', {'question': question}) 上記のdetail()を短くする。 django.shortcuts にはこうしたコードを省略する関数が多くあるので調べると面白いかもしれない。 from django.shortcuts import get_object_or_404, render from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] context = {'latest_question_list': latest_question_list} return render(request, 'polls/index.html', context)。 # モデルにアクセスするobjects.get()とHttp404が一緒になっている。 def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question}) index.html index() に対するテンプレートにはこのように記述する。 ビューで latest_question_list オブジェクトが辞書型に格納されて渡されているので、それを受け取ってテンプレート内でオブジェクトに格納された値を属性アクセス . して取得している。 <!-- 受け取った変数にデータがあるか確認する。 --> {% if latest_question_list %} <ul> {% for question in latest_question_list %} <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %} index.htmlのハードコード(直接記述している)を削除する このように直接URLを書き込むと変更に弱いコードになってしまうので、 polls.urlsモジュールのpath() 関数でname引数を定義したのでそれをURLに使用する。 {%url%} を使う。 <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li> 変更後 <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li> そしてテンプレートを入れるディレクトリを作成した際のように名前空間を追加する。システムが別々のアプリ内で同じname引数を含んでいても区別が付けられるように template/polls/detail.html にアクセスしたい場合は polls:detail と記述する。 <!-- 受け取った変数にデータがあるか確認する。 --> {% if latest_question_list %} <ul> {% for question in latest_question_list %} <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %} そして、URLconf(urls.py)に名前空間を追加 app_name = 'polls' from django.urls import path from . import views # ここを新たに追加した。 app_name = 'polls' urlpatterns = [ path('', views.index, name='index'), path('<int:question_id>/', views.detail, name='detail'), path('<int:question_id>/results/', views.results, name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), ] こうするとモジュールに指定されたURLの定義を検索出来る。例えば polls/specifics/12 のようにURLを変更した場合 urls.py に書かれたパスを変更する事でテンプレート側に変更を加える必要がない。 path('specifics/<int:question_id>/', views.detail, name='detail'), detail.html detail() に対するテンプレートはこのように記述する。 <h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul> フォームを使って質問に対して回答を送信する。 detail.html に <form> を追加してサーバにデータを送信して質問に対して、投票出来るようにする。 下記のように detail.html を変更する。 <!-- 質問の内容 --> <h1>{{ question.question_text }}</h1> <!-- もしデータベースから質問が取得出来ない場合エラーが表示される。 --> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} <form action="{% url 'polls:vote' question.id %}" method="post"> <!-- セキュリティのため --> {% csrf_token %} <!-- 質問に対する選択肢を並べる --> {% for choice in question.choice_set.all %} <!-- forloop.counterはforタグのループが何度実行されたかを表す値です。 --> <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}"> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br> {% endfor %} <input type="submit" value="Vote"> </form> views.pyにvote()関数を追加する コードの流れとしては ユーザがdetailページの質問に対する選択肢を選択する。 Voteボタンをクリックする。 データがサーバに送信される。 選択された選択肢の投票数をインクリメントする。 results.htmlにリダイレクトする。Postデータが成功した後は基本的に HttpResponse ではなく HttpResponseRedirect を返す必要がある。 # 追加した。HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render # 追加した。reverse from django.urls import reverse # Choiceを追加した。 from .models import Choice, Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] context = {'latest_question_list': latest_question_list} return render(request, 'polls/index.html', context)。 # モデルにアクセスするobjects.get()とHttp404が一緒になっている。 def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question}) # 質問に対して選択して投票する。 def vote(request, question_id): # まず質問があるかどうか確認する。 question = get_object_or_404(Question, pk=question_id) try: # ユーザが選択した値からpk値を取得して、それを元にモデルから選択肢のオブジェクトを取得する。 # なければYou didn't...choiceと表示される。 selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): # Redisplay the question voting form. return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice.", }) else: # 選択肢オブジェクトから何回投票されたか表示するvotesオブジェクトをインクリメントする。 selected_choice.votes += 1 # データベースに保存する。 selected_choice.save() # Always return an HttpResponseRedirect after successfully dealing # with POST data. This prevents data from being posted twice if a # user hits the Back button. # データの保存に成功したら、results.htmlにリダイレクトする。 return HttpResponseRedirect(reverse('polls:results', args=(question.id,))) reverse()関数とは この関数を使うと、vote関数中でのURLのハードコードを防ぐ事が出来る。 引数としては polls:results リダイレクト先のビュー名とそのビューに与えるURLパターン question.id を渡せる。 reverse('polls:results', args=(question.id,)) # 返り値 '/polls/3/results/' Post通信が成功した際のresults関数を作成する。 views.pyにresults関数を追加する。 # 先ほどまで書いてきたviews.pyにresults関数を追加する。 def results(request, question_id): # 指定したpkキーにデータがあれば返す、なければエラーを返す。 question = get_object_or_404(Question, pk=question_id) # 質問オブジェクトを引数で貰ってページを作成する。 return render(request, 'polls/results.html', {'question': question}) results.htmlを作成する。 <!-- 質問を表示する。 --> <h1>{{ question.question_text }}</h1> <!--質問の選択肢とそれに対する投票数を取得する。--> <ul> {% for choice in question.choice_set.all %} <!--choice.votes|pluralizeは投票数が2以上の場合vote s とsを追加してくれる。--> <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href="{% url 'polls:detail' question.id %}">Vote again?</a> Built-in template tags and filters | Django documentation | Django 汎用ビューを使って今まで書いたコードをさらに短くする。 views.py に書かれた index(), detail(), results() 関数は3つとも似たような機能でURLを介して渡されたパラメータに従ってデータベースからデータを取り出しページを作成する。これらの一連の動作はよくある事なのでDjangoでは汎用ビューというショートカットを用意してより簡素に機能を実装出来るようにしている。 汎用ビューを適用するにはいくつかこれまでに書いたコードを修正する必要がある。 URLconfを変換する。 古い不要なビューを削除する。 新しいビューにDjango汎用ビューを設定する。 URLconfの修正 変更前 from django.urls import path from . import views app_name = 'polls' urlpatterns = [ path('', views.index, name='index'), path('<int:question_id>/', views.detail, name='detail'), path('<int:question_id>/results/', views.results, name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), ] 変更後 from django.urls import path from . import views app_name = 'polls' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('<int:pk>/', views.DetailView.as_view(), name='detail'), path('<int:pk>/results/', views.ResultsView.as_view(), name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), ] views.IndexView.as_view(), views.DetailView.as_view(), views.ResultsView.as_view() と as_view() と書くようになった。 そして、 question_id が pk に変更された。ここは同じでもいいような気もする。結局同じ数値が返り値として入るから。 ※後述する DetailView には pk キーを渡す必要があるので同じではダメなようだ。 viewsの修正 index(), detail(), results() 関数を削除しクラスベースに書き換える。 indexでは ListView を継承している。 detail, resultsでは DetailView を継承している。 ListView 「オブジェクトのリストを表示する。」 メソッドのフローチャート継承したメソッドが下記の順番で自動で実行される。 1.setup() 2.dispatch() 3.http_method_not_allowed() 4.get_template_names() 5.get_queryset() 6.get_context_object_name() 7.get_context_data() 8.get() 9.render_to_response() このようにメソッドが実行されるので継承したクラスに get_quesryset() メソッドを追加して内容を上書きする事が出来る。 template_name ListView ではデフォルトの場合 <app name>/<model name>_list.html を自動で生成して使用する。 その場合、テンプレート名は polls/question_list.html になる。 しかし元々作成してある polls/index.html を使用したい場合は template_name に 'polls/detail.html' を代入する事でDjangoがそちらを使用するように認識してくれる。 DetailView 「あるタイプのオブジェクト詳細ページを表示する。」 なので ListViewの詳細ページをDetailViewで表示するみたいな使われ方をする。 template_name そしてデフォルトでは DetailView は <app name>/<model name>_detail.html という名前のテンプレートを自動生成して使用する。 その場合、テンプレート名は polls/question_detail.html になるが、今回は自動生成されたものではなく元々作成してある polls/detail.html を使いたいので template_name を指定して元々のテンプレートを使用する。方法はListViewの時と同じで template_name に polls/detail.html を代入する。 model このクラス変数はビューが使用するモデルを指定している。 model = Question の場合は裏側で Question.objects.all() を行ってくれる。なので queryset = Question.objects.all() としても良い。 from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.views import generic from .models import Choice, Question class IndexView(generic.ListView): # デフォルトのビューを使用せず、元々作成してあったものを使用する。 template_name = 'polls/index.html' # 自動で渡されるquestion_listというコンテキスト変数の変数名を独自のものに変更している。 context_object_name = 'latest_question_list' def get_queryset(self): """最新の5件を取得する。""" return Question.objects.order_by('-pub_date')[:5] class DetailView(generic.DetailView): # 自分がどのモデルに対して動作するかを伝えている。 # おそらくget_object_or_404(Question, pk=question_id)のQuestion部分を担っている。 # pkの部分はurls.pyで先に指定してある。 model = Question template_name = 'polls/detail.html' class ResultsView(generic.DetailView): model = Question template_name = 'polls/results.html' def vote(request, question_id): ... # 前回のまま変更しない。 これでサーバを起動して特にエラーもなく質問のリストページ(index.html)、詳細ページ(投票するページdetail.html)、投票後の今ままでの投票数を表示するページ(results.html)が表示されていれば汎用ビューでのアプリ構築ができたと思う。 最後に 過去にRuby on railsのフレームワークの中身がどう動作しているのかイメージ出来ないのが苦痛(フレームワークは面倒な中身を気にしなくてもアプリが作れるように設計してあるので仕方ないかもしれない。)で挫折しているので今回Djangoのチュートリアルまだ途中ですが挫折せずにアプリ作成まで出来てよかったです。普段Jsonしか触らなかったので少しですがデータベースを作成して操作する経験が出来たのでこれを気にSQL構文をもう少し勉強しようと思う。多対多、多対一の関係とかも自分で作成出来るまでになります。自分の作成したアプリのER図をかける書けるようになりたい。 参照 SECRET_KEYを誤ってGitHubにプッシュしたときの対処法(Django編) - Qiita Pythonの相対インポートで上位ディレクトリ・サブディレクトリを指定 | note.nkmk.me ワイルドカードインポート(import *)は推奨されない 「カラム名」と「フィールド名」の違い|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 3. データベースマイグレーション | densan-labs.net モデル(データベース)の作成 プログラミングでよく見かける"コンテキスト(context)って何? - Qiita Python Django チュートリアル(3) - Qiita DJangoのお勉強(1) - Qiita ドキュメント Djangoの汎用ビュー入門(ListView) FOREIGN KEY制約(外部キー制約を設定する) 外部キー制約とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 Django ForeignKeyで1対多のモデルを構築 記事に関するコメント等は ?:Twitter ?:Youtube ?:Instagram ??‍?:Github ?:Stackoverflow でも受け付けています。どこかにはいます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django公式チュートリアル(5~7)で分からない所、徹底的に調べた。

最初に 前回のチュートリアルで作成した投票アプリに対してテストコードを書く所から始める。内容はチュートリアル5〜7をカバーする予定になる。 Djangoでテストコードを書く チュートリアルが用意してくれたバグに対してテストコードを書いていく。 まずはバグを確認する。 Qustion.was_published_recently() のメソッドはQuestionが昨日以降に作成された場合に True を返すが 未来の日付になっている場合にもTrueを返す。これがバグになる。自分は前編の記事で登場した models.py に書かれた was_published_recently() はバグに対応済みなので下記のコードに入れ替えてバグを作り出す必要がある。 これだと pub_date が未来の場合も True を返す。 def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1) バグの確認 コードに故意にバグを生み出した所でバグを確認したいと思う。 python manage.py shell データベース APIを叩いていく。 import datetime from django.utils import timezone from polls.models import Question # 投稿日を今から30日後に設定した Questionオブジェクトを作成する。 # この状態ではQuestionクラスからインスタンスを生成しただけでデータベースに保存はされていない。 future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30)) # 結果 True テストを作成する。 pollsアプリのディレクトリに tests.py というファイルがあると思うのでそこにテストコードを書いていく。 import datetime from django.test import TestCase from django.utils import timezone from .models import Question class QuestionModelTests(TestCase): def test_was_published_recently_with_future_question(self): """ was_published_recently()はpubdateが現在時刻より未来に設定された 場合はFalseを返さないといけない。 """ time = timezone.now() + detetime.timedelta(days=30) future_question = Question(pub_date=time) # ここで返却値がFalse出ない場合はテストに通らない事を設定している。 self.assertIs(future_question.was_published_recently(), False) テストを実行する。 python manage.py test polls # 実行結果 Creating test database for alias 'default'... System check identified no issues (0 silenced). F ====================================================================== FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question self.assertIs(future_question.was_published_recently(), False) AssertionError: True is not False ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (failures=1) Destroying test database for alias 'default'... テストは失敗したと出力されると思う。 バグを修正する models.py の記述された関数 was_published_recently() を元に戻してバグがない状態にしたいと思う。 def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now もう一度実行してみる。 python manage.py test polls # 実行結果 Creating test database for alias 'default'... System check identified no issues (0 silenced). . ---------------------------------------------------------------------- Ran 1 test in 0.001s OK Destroying test database for alias 'default'... 今度はテストはOKと出力され、テストに通った。 複数のテストを実行する 先ほど作成した QuestionModelTests クラスに別のテストも追加してみましょう。 import datetime from django.test import TestCase from django.utils import timezone from .models import Question class QuestionModelTests(TestCase): def test_was_published_recently_with_future_question(self): """ was_published_recently()はpubdateが現在時刻より未来に設定された 場合はFalseを返さないといけない。 """ time = timezone.now() + detetime.timedelta(days=30) future_question = Question(pub_date=time) # ここで返却値がFalse出ない場合はテストに通らない事を設定している。 self.assertIs(future_question.was_published_recently(), False) # 新しくテストを追加していく。 def test_was_published_recently_with_old_question(self): """ was_published_recently()はpub_dateが1日より過去の場合 Falseを返す """ # 現在時刻より一日1秒前の質問のインスタンスを作成する。 time = timezone.now() - timedelta(days=1, seconds=1) old_question = Question(pub_date=time) # 返り値がFalseならテストに通る。 self.assertIs(old_question.was_published_recently(), False) def test_was_published_recently_with_recent_question(self): """ was_published_recently()はpub_dateが1日以内ならTrueを返す """ # 一日以内の質問インスタンスを作成する。 time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) recent_question = Question(pub_date=time) self.assertIs(recent_question.was_published_recently(), True) これで過去、現在、未来に対してのテストが揃った。これで期待通りに動作する事を保証できるようになった。 Djangoのviewをテストする。 ビューレベルでのユーザ動作をシュミレートする事ができるClientを用意しているので、 tests.py や shellで使用する事ができる。 最初はshellから使用してみる。 python manage.py shell 下記のコードを一行ずつshellで実行する。 from django.test.utils import setup_test_environment # テンプレートのレンダラーをインストールする # response.context等の属性を調査できるようになる。 setup_test_environment() from django.test import Client # クライアントインスタンスを作成してページアクセスしたように操作する。 client = Client() response = client.get('/') # 実行結果 Not Found: / response.status_code # 実行結果 404 from django.urls import reverse response = client.get(reverse('polls:index')) response.status_code # 実行結果 200 # ページのhtmlが返ってくる。 response.content # 実行結果 b'\n <ul>\n \n <li><a href="/polls/3/">test3</a></li>\n \n <li><a href="/polls/2/">hello</a></li>\n \n <li><a href="/polls/1/">what&#x27;s up?</a></li>\n \n </ul>\n\n' response.context['latest_question_list'] # 実行結果 <QuerySet [<Question: test3>, <Question: hello>, <Question: what's up?>]> 現在の投票一覧は最新5件を取得しているため、未来の投稿日の質問も表示している。これを views.py の get_queryset() に変更を加えていく。 from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.views import generic # 新しく追加した from django.utils import timezone from .models import Choice, Question class IndexView(generic.ListView): # デフォルトのビューを使用せず、元々作成してあったものを使用する。 template_name = 'polls/index.html' # 自動で渡されるquestion_listというコンテキスト変数の変数名を独自のものに変更している。 context_object_name = 'latest_question_list' # 変更する箇所 def get_queryset(self): """ 最新の5件を取得する。ただし投稿日が現在時刻より前にある投稿のみ表示。 filter(pub_date__lte=timezone.now()) = if Question.pub_date <= timezone.now(): return Question.pub_date """ return Question.objects.filter( pub_date__lte=timezone.now() ).order_by('-pub_date')[:5] class DetailView(generic.DetailView): # 自分がどのモデルに対して動作するかを伝えている。 # おそらくget_object_or_404(Question, pk=question_id)のQuestion部分を担っている。 # pkの部分はurls.pyで先に指定してある。 model = Question template_name = 'polls/detail.html' class ResultsView(generic.DetailView): model = Question template_name = 'polls/results.html' def vote(request, question_id): ... # 前回のまま変更しない。 Djangoでは querySetの filter() を使用する際に変数を比較したい場合下記のような記述を取る事が出来る。 下記は Product.weight の値が2以下の場合 Trueになり返却されるオブジェクトになる。 # weight <= 2 products = Product.objects.filter(weight__lte=2) 他にも比較したり出来る、下記のサイトで説明されている。 Django逆引きチートシート(QuerySet編) - Qiita viewのテストを追加する import datetime from django.test import TestCase from django.utils import timezone from .models import Question # 新しく追加した。 from django.urls import reverse class QuestionModelTests(TestCase): def test_was_published_recently_with_future_question(self): """ was_published_recently()はpubdateが現在時刻より未来に設定された 場合はFalseを返さないといけない。 """ time = timezone.now() + detetime.timedelta(days=30) future_question = Question(pub_date=time) # ここで返却値がFalse出ない場合はテストに通らない事を設定している。 self.assertIs(future_question.was_published_recently(), False) # 新しくテストを追加していく。 def test_was_published_recently_with_old_question(self): """ was_published_recently()はpub_dateが1日より過去の場合 Falseを返す """ # 現在時刻より一日1秒前の質問のインスタンスを作成する。 time = timezone.now() - timedelta(days=1, seconds=1) old_question = Question(pub_date=time) # 返り値がFalseならテストに通る。 self.assertIs(old_question.was_published_recently(), False) def test_was_published_recently_with_recent_question(self): """ was_published_recently()はpub_dateが1日以内ならTrueを返す """ # 一日以内の質問インスタンスを作成する。 time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) recent_question = Question(pub_date=time) self.assertIs(recent_question.was_published_recently(), True)    # 新しく追加した def create_question(question_text, days): """ 引数から質問を作成する。過去に投稿された質問を作りたいなら-1~nの値を第二引数に取る、 まだ公開されてない質問を作成したいなら+1~nの値を第二引数に取る。 現在から10日後の投稿日の質問を作成したいなら 例: create_question('今日は何食べる?' , 10)     15日前の質問を作成したいなら create_question('今日は何食べる?' , -15) """ time = timezone.now() + datetime.timedelta(days=days) return Question.objects.create(question_text=question_text, pub_date=time) class QestionIndexViewTests(TestCase): def test_no_question(self): """ 質問がデータベースにない際に適切なメッセージを 表示出来てるか確認する。 """ response = self.client.get(reverse('polls:index')) # テスト合格条件 # ステータスコードが200である事 self.assertEqual(response.status_code, 200) # コンテンツに No polls are availableが含まれる事 self.assertContains(response, "No polls are available.") # データベースが空である事 self.assertQuerysetEqual(response.context['latest_question_list'], []) def test_past_question(self): """ 過去の投稿日の質問一覧が表示されるか確認する。 """ # 投稿日が30日前の質問を作成する。ダミーデータなので実際のデータベースにデータ作成されることはない。 # そしてメソッドが終了すればダミーデータは破棄される。 # なので新しくテストする際は質問は空の状態から始まる。 question = create_question(question_text="過去の質問", days=-30) response = self.client.get(reverse('polls:index')) # テスト合格条件 # 先ほど作成した質問が表示されているか表示する。 self.assertQuerysetEqual(response.context['latest_quesiton_list'], [question],) def test_future_question(self): """ 投稿日が未来の質問が表示されていないか確認する。 """ create_question(question_text="未来の質問", days=30) response = self.client.get(reverse('polls:index')) # テスト合格条件 # コンテンツに No polls are awailableが含まれる事 self.assertContains(response, "No polls are available.") # 最新の質問5件が質問が空な事 self.assertQuerysetEqual(response.context['latest_question_list'], []) def test_future_question_and_past_question(self): """ 過去・未来の質問の両方ある時に過去の質問だけ表示される。 """ # 片方だけ変数に入れるのはテストの合格条件を判別する際に過去質問が表示されているのを確認するため question = create_question(question_text="Past question.", days=-30) create_question(question_text="Future question.", days=30) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [question], ) def test_two_past_questions(self): """ 過去の質問2つが表示されているか確認する。 """ question1 = create_question(question_text="Past question 1.", days=-30) question2 = create_question(question_text="Past question 2.", days=-5) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [question2, question1], ) システムに問題がなければテストに全て合格する。作成された質問はデータベースに保存される事なく各テストが実行されて終わるたびに破棄される。 DetailViewのテスト 上記のテストは上手く動作して未来の質問はindexに表示されないが、 detail.html への正しいURLを知っていたり推測したユーザは、まだページに到達する事が出来る。そのため同じように未来の投稿日の場合はページを表示しないように polls/views.py コードを書き換える必要がある。 class DetailView(generic.DetailView): # テンプレートで変数にアクセスする際はquestionになる。 model = Question template_name = 'polls/detail.html' # 新しく追加した def get_queryset(self): """ まだ公開されていない質問は除外する。 """ return Question.objects.filter(pub_date__lte=timezone.now()) そして新たに追加した機能が動作するか確認するテストを書く。 tests.py に下記のコードを追加する。 class QuestionDataViewTests(TestCase): def test_future_question(self): """ detail.htmlの未来の日付のページにアクセスする場合は404を表示する、 """ # 現在から5日後の質問を作成する future_question = create_question(question_text = '未来の質問', days=5) url = reverse('polls:detail', args=(future_question.id,)) response = self.client.get(url) # 合格条件 # ページにアクセスした際のステータスコードが404 self.assertEqual(response.status_code, 404) def test_past_question(self): """ 過去の質問の場合はページを表示する。 """ past_question = create_question(question_text='過去の質問', days=-5) url = reverse('polls:detail', args=(past_question.id,)) response = self.client.get(url) # ページに過去の質問が含まれている。 self.assertContains(response, past_question.question_text) detailビューのテストも書いてきましたが、同様にresultsビューが必要になるが似たようなコードになるのでチュートリアルでは紹介されていない。 別の問題として現在の状態ではChoice(質問に対する選択肢)を持たない質問が公開されている。それを views.py で処理する事が出来るので機能を追加して、ChoicesがないQuestionを作成し、それが公開されないことをテスト、同じようにChoiceがあるQuestionを作成し、それが公開されることをテストをする。 get_queryset()に選択肢がない質問を表示しないようにfilterを追加する。 class IndexView(generic.ListView): template_name = 'polls/index.html' # テンプレート側でQuestion.objects.order_by('-pub_date')[:5]を呼び出す際の名前を設定している。 context_object_name = 'latest_question_list' def get_queryset(self):      # filter内の条件は現在より過去の質問かつ選択肢がある場合に質問オブジェクトを返すようになっている。 return Question.objects.filter(pub_date__lte=timezone.now(), choice__isnull=False).distinct().order_by('-pub_date')[:5] class DetailView(generic.DetailView): # テンプレートで変数にアクセスする際はquestionになる。 model = Question template_name = 'polls/detail.html' def get_queryset(self): print(Question.objects) return Question.objects.filter(pub_date__lte=timezone.now(), choice__isnull=False).distinct() choice__isnull=False で逆参照を行い 各質問にぶら下がる選択肢を確認する。 選択肢がある場合はおそらく内部でこんな感じに取得できると考えている。 Djangoのシェルに移動して直接データベースAPIを操作して選択肢があり参照関係になっている質問を確認する事ができる。 # 登録された選択肢を全て取り出して、それぞれがどこの質問に結びつけらているか表示している。 # 図でいう1, 1, 2を取り出しているのでそれに結びついたQuestionオブジェクトが表示されている。 [obj.question for obj in Choice.objects.all()] # 実行結果 [<Question: what's up?>, <Question: what's up?>, <Question: hello>] そして参照関係にない質問は obj.questtion しても空なので false になりその質問には選択肢がないと判断する事ができる。 distinct()で重複する結果を表示しないようにする。 親テーブルと子テーブルをJoinして作成された新しいテーブルになる。 そして同じフィールドに別の値を入れる事が出来ないので選択肢に対してどの質問が参照されているのかという表示方法になる。 そのため、1つの質問で複数の選択肢を参照している質問は参照する選択肢の数だけ表示されることになる。 http://127.0.0.1:8000/polls/ アクセスすると質問が重複して表示される。 この重複項目をなくすために distict() を使用する。 そうすると重複項目がなくなり、選択肢がない質問だけを表示する事ができる。 新しく追加した機能のテストコードを書いていく。 まず tests.py の create_question() で選択肢を含む質問を作成できるようにする。 choice_texts に値がある場合は、それを元に選択肢を作成する。複数作成することもできる。 def create_question(question_text, days, choice_texts=[]): """ 質問を `question_text` と投稿された日から作成する。現在より過去の時間で投稿したい場合は days= -days、 未来の時間で投稿したい場合は対してはdays= +daysとする。 """ time = timezone.now() + datetime.timedelta(days=days) q = Question.objects.create(question_text=question_text, pub_date=time) # 選択肢がある場合とない場合で変数に格納した際返ってくるモデルが変わるから注意が必要 # 選択肢があるとChoiceオブジェクトが変える。ないとQuestionオブジェクトになる。 if choice_texts: for choice_text in choice_texts: return q.choice_set.create(choice_text=choice_text, votes=0) else: return q これを使って、先ほど追加した indexページ、detailページで選択肢がない質問が表示されていないか確認するテストコードを書いていく、そして前回作成したテストも選択肢がない質問の場合ページが表示されなくなっているので、作成する質問に選択肢を付けてあげないとテストが通らなくなっている。 その修正も行う。このように一部変更を加えたために今まで通ってたテストを含めて、全体を修正しなくてはならないコードはとても修正が大変なので良いコードとは言えないかもしれない。もしもっと良いテストコードの書き方があったら教えて下さい。 tests.py これがテストの全体コードになる。 import datetime from django.http import response from django.test import TestCase from django.urls import reverse from django.utils import timezone from .models import Choice, Question # テストコードの書き方はTestCaseを継承する事 # メソッド名をtestから始める事でDjango側で実行してくれるようになる。 class QuestionModelTests(TestCase): def test_was_published_recently_with_future_question(self): """ was_published_recently()はpub_dateが未来の場合Falseを返す。 """ time = timezone.now() + datetime.timedelta(days=30) future_question = Question(pub_date=time) self.assertIs(future_question.was_published_recently(), False) def test_was_published_recently_with_recent_question(self): """ was_published_recently()はpub_dateが昨日までに投稿されたものなら Trueを返す。 """ time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) recent_question = Question(pub_date=time) self.assertIs(recent_question.was_published_recently(), True) def create_question(question_text, days, choice_texts=[]): """ 質問を `question_text` と投稿された日から作成する。現在より過去の時間で投稿したい場合は days= -days、 未来の時間で投稿したい場合は対してはdays= +daysとする。 """ time = timezone.now() + datetime.timedelta(days=days) q = Question.objects.create(question_text=question_text, pub_date=time) # 選択肢がある場合とない場合で変数に格納した際返ってくるモデルが変わるから注意が必要 # 選択肢があるとChoiceオブジェクトが変える。ないとQuestionオブジェクトになる。 if choice_texts: for choice_text in choice_texts: return q.choice_set.create(choice_text=choice_text, votes=0) else: return q class QuestionIndexViewTests(TestCase): def test_no_questions(self): # reverse('polls:index')でpollsのindexページURLを返している。それを利用してアクセスしている。 response = self.client.get(reverse('polls:index')) self.assertEqual(response.status_code, 200) self.assertContains(response, "No polls are available") self.assertQuerysetEqual(response.context['latest_question_list'], []) def test_past_question(self): question = create_question(question_text="Past question.", days=-30, choice_texts=['game set']) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [question.question], ) def test_future_question(self): create_question(question_text="Future question.", days=30, choice_texts=['game set']) response = self.client.get(reverse('polls:index')) self.assertContains(response, "No polls are available") self.assertQuerysetEqual(response.context['latest_question_list'], []) def test_future_question_and_past_question(self): # 片方だけ変数に入れるのはテストの合格条件を判別する際に過去質問が表示されているのを確認するため question = create_question(question_text="Past question.", days=-30, choice_texts=['game set']) create_question(question_text="Future question.", days=30, choice_texts=['game set']) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [question.question], ) def test_two_past_question(self): question1 = create_question(question_text="Past question 1.", days=-30, choice_texts=['game set']) question2 = create_question(question_text="Past qustion 2.", days=-5, choice_texts=['game set']) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [question2.question, question1.question], ) def test_choice_question(self): """ Indexページで 選択肢のある質問を表示する。 """ choice_question = create_question(question_text='Indexページでの選択肢のある質問', days=-1, choice_texts=['game set']) url = reverse('polls:index') response = self.client.get(url) self.assertContains(response, choice_question.question) def test_no_choice_question(self): """ Indexページで 選択肢がない質問は表示しない。 """ no_choice_question = create_question(question_text='Indexページでの選択肢のない質問', days=-1) url = reverse('polls:index') response = self.client.get(url) self.assertNotContains(response, no_choice_question) class QuestionDataViewTests(TestCase): def test_future_question(self): """ detail.htmlの未来の日付のページにアクセスする場合は404を表示する、 """ # 現在から5日後の質問を作成する future_question = create_question(question_text = '未来の質問', days=5) url = reverse('polls:detail', args=(future_question.id,)) response = self.client.get(url) # 合格条件 # ページにアクセスした際のステータスコードが404 self.assertEqual(response.status_code, 404) def test_past_question(self): """ Detailページ 過去の質問の場合はページを表示する。 """ past_question = create_question(question_text='過去の質問', days=-5, choice_texts=['geme set']) url = reverse('polls:detail', args=(past_question.id,)) response = self.client.get(url) # ページに過去の質問が含まれている。 self.assertContains(response, past_question.question) def test_choice_question(self): """ Detailページ 選択肢のある質問を表示する。 """ choice_question = create_question(question_text='detailページでの選択肢がある質問', days=-1, choice_texts=['game set']) url = reverse('polls:detail', args=(choice_question.id,)) response = self.client.get(url) self.assertContains(response, choice_question.choice_text) def test_no_choice_question(self): """ 選択肢がない質問は表示しない。 """ no_choice_question = create_question(question_text='detailページでの選択肢のない質問', days=-1) url = reverse('polls:detail', args=(no_choice_question.id,)) response = self.client.get(url) self.assertEqual(response.status_code, 404) まずは今まで動作していたテストが選択肢がない質問だったので、選択肢 ['game set'] を追加して再び動作するように変更する。 その際に choice_set.create で選択肢を追加した場合、返り値が Questionオブジェクトではなく Choiceオブジェクトになるので質問を取り出す際は 返り値.question とする必要がある。 問題なければ、テストが13個実行され OK と表示される。 スタイルシート・静的ファイルを追加する。 スタイルシートを追加 pollsディレクトリにstaticディレクトリを作成する。そうするとDjangoはそこから静的ファイルを探してくれる。 polls/static/polls と templateディレクトリを作成した時みたいになる。 先ほど追加したディレクトリに style.css を追加する。 polls/static/polls/style.css のようになる。 style.css li a { color: green; } polls/templates/polls/index.html の上部に下記のコードを追加する。 {% load static %} <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}"> 画像を追加する polls/static/polls/images/ とディレクトリを作成する。その中に 好きな画像をおく。 スタイルシートで背景画像として読み込む body { background: white url("images/background.gif") no-repeat; } li a { color: green; } adminのフォームをカスタマイズする 編集フォームでのフィールドの並び順を替える 質問の詳細ページでのフィールドの並び順を変更する。 polls/admin.py from django.contrib import admin from .models import Question class QuestionAdmin(admin.ModelAdmin): # この順番で表示されるようになる。 fields = ['pub_date', 'question_text'] # 第二引数で作成したclassを渡す admin.site.register(Question, QuestionAdmin) admin.site.register(Choice) 変更前 変更後 pub_dateとquestion_textの位置が入れ替わってる。 フィールドを分割する。 polls/admin.py from django.contrib import admin from .models import Question class QuestionAdmin(admin.ModelAdmin): fieldsets = [ (None, {'fields': ['question_text']}), ('Date information', {'fields': ['pub_date']}), ] admin.site.register(Question, QuestionAdmin) ChoiceオブジェクトをQuestionフォームから追加・編集する。 現在Choiceフォームから質問に選択肢を追加・編集可能ですが、これだとページを移動したりと効率が悪いので Questionフォームから追加・編集できるようにする。 polls/admin.py from django.contrib import admin from .models import Choice, Question class ChoiceInline(admin.StackedInline): model = Choice extra = 3 class QuestionAdmin(admin.ModelAdmin): fieldsets = [ (None, {'fields': ['question_text']}), ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), ] inlines = [ChoiceInline] admin.site.register(Question, QuestionAdmin) コードを追加するとQuestionフォームに3つ(extraで数の調整ができる)の choice_text, votes を設定できる項目が追加される。 今のままだと多くの画面スペースを必要とするのでこれを小さくする。 class ChoiceInline の引数を TabularInline に変更する。 class ChoiceInline(admin.TabularInline): #... これでコンパクトになったと思う。 pollsの質問一覧ページをカスタマイズする。 チェンジリストページと呼ばれるページで(http://127.0.0.1:8000/admin/polls/question/)質問の一覧が表示されている。 現在は オブジェクトの名前(どんな質問が格納されているのがわかる)だけが表示されていますが、各フィールドの値を表示してより多くの情報をここで確認できるようにする。 polls/admin.py class QuestionAdmin(admin.ModelAdmin): # ... list_display = ('question_text', 'pub_date', 'was_published_recently') 各カラムのヘッダーをクリックすると並び替えを行えるが、 was_published_recently だけは並び替えをサポート出来ていないので、 @ デコレータを使用して並び替えの対応させていく。 デコレータなのでクラスメソッドの直前に追加する。 polls/models.py from django.contrib import admin class Question(models.Model): # ... # ここを新しく追加した。 @admin.display( boolean=True, ordering='pub_date', description='Published recently?', ) def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now 質問を日付でフィルター掛けれるようにする。 pub_date の日付を元に質問を絞れるようにする。フィルタは対象のフィールドの種類によって変化する。 pub_date は DateTimeField なので、Django はこのフィールドにふさわしいフィルタオプションが、「すべての期間 ("Any date")」「今日 ("Today")」「今週 ("Past 7 days")」「今月 ("This month")」 を用意してくれる。 polls/admin.py from django.contrib import admin from .models import Choice, Question class ChoiceInline(admin.StackedInline): model = Choice extra = 3 class QuestionAdmin(admin.ModelAdmin): fieldsets = [ (None, {'fields': ['question_text']}), ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), ] inlines = [ChoiceInline] # 新しく追加した。 list_filter = ['pub_date'] admin.site.register(Question, QuestionAdmin) 質問の検索機能を追加する。 先ほどのコードにさらに変数を追加する。 question_text フィールドをユーザが入力した文字列を元に Likeクエリで検索するのでデータベースに割と負荷がかかるみたいで常識の範囲で使用しましょうとチュートリアルに記述されている。 # ... list_filter = ['pub_date'] # 新しく追加した search_fields = ['question_text'] 管理サイトの見た目をカスタマイズする。 管理サイトの上部に Django administration と書かれているのでこれを Polls administration と変更してみたいと思う。 manage.py が置かれているディレクトリに templates ディレクトリを作成する。その中に adminフォルダを作成する。 templates/admin みたいな構成になる。 その中にデフォルトのDjango adminのテンプレートをコピーして貼り付ける。 場所は 下記のコマンドから確認できる。anacondaの環境の場合は仮想環境内で実行する必要がある。 python -c "import django; print(django.__path__)" そして開いたファイルを下記のように編集する。 変更前 {% extends "admin/base.html" %} {% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} {% block branding %} <h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></h1> {% endblock %} {% block nav-global %}{% endblock %} 変更後 {% extends "admin/base.html" %} {% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} {% block branding %} <h1 id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></h1> {% endblock %} {% block nav-global %}{% endblock %} 次に mysite/settings.py を開いて TEMPLATES 設定オプションの中にある DIRS オプションを下記のように変更する。 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', # ここを新しく追加した。 'DIRS': [BASE_DIR / 'templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] これでデフォルトのテンプレートをオーバライドすることが出来た。 これでチュートリアル5~7の内容は終了した。 最後に チュートリアル5でテストコードを初めて書く経験が出来てよかったです。途中チュートリアルから外れた事をしようとした際に逆参照でモデルからデータを取得する方法がわからなくてかなり時間が掛かりました。SQLデータベースの理解がまだ乏しいのでもう少しデータベースに慣れてからDjangoでアウトプットとして、Webアプリを作成したいと思います。 参照 DjangoのModelからデータを取り出す方法をまとめとく - やる気がストロングZERO LEFT JOIN / INNER JOIN を実行すると同じ内容のレコードが複数含まれる - SQLの構文 ドキュメント 記事に関するコメント等は ?:Twitter ?:Youtube ?:Instagram ??‍?:Github ?:Stackoverflow でも受け付けています。どこかにはいます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonプログラムを稼働させるのにおすすめのVPSサーバーランキング

この記事では Python で作成したアプリケーションを動かすのにオススメのVPSサーバーをランキング形式で紹介します。 ・DjangoやFlaskで作成したWebアプリを公開したい ・仕事や研究で作成したPythonプログラムを24時間稼働させたい こういったニーズを持つ方はぜひ最後まで読んでみてください。 1位 ConoHa VPS メリット 初期費用無料かつ月額料金が安い 利用者数の多いのでネット上に情報が豊富で、何かでハマっても対応しやすい クレジットカードを持っていなくても支払いが行える デメリット 最安プランだと制限が多い ConoHa VPSは東証一部上場企業のGMOグループが運営するサービスで、国内のVPSサーバーの定番といった位置づけです。 まず第一に料金的にオススメなサービスです。 月額料金は最も安いものだと600円代ですし、初期費用も発生しません。 (ただし、最安プランだと使用できるリソースには制限がかかっています。) メモリ CPU SSD 512MB 1コア 30GB 本当に簡素な身内だけが使うサービスを公開する時は、別にこれでもいいでしょう。 しかし、本格稼働を目指すならメモリ4GBプラン(月額3608円)以上を選択しておくと良いでしょう。 仮にリソースが余った場合は、後で説明するようにクラウドのファイル置き場として活用したり、出先から操作できる仕事or研究用パソコンとして使うこともできます。 またネット上に情報が多いのもポイントです。 例えばFlask(Pythonでwebアプリを作る時によく使うフレームワーク)のアプリのデプロイ手順なんかも調べればすぐに見つけることができます。 参考記事)ConoHa VPS(CentOS7)+Flask+Nginx+uWSGIでWEBアプリを作ってみる(1) この「調べたら何かしらの情報が出てくる」という状態は初心者にとっては本当にありがたいです。 >>Cono HaVPSの公式サイトを見てみる ちなみに多くの vps サーバーがそうであるように、ConoHaVPSもOSはLinux です。 windowsが望ましいという方は、ConoHa vpsの Windows バージョンもあります。 >>Conoha for Windows の公式サイトを見てみる 2位 さくらのVPS 二位は、さくらの VPS としました。 メリット Djangoのデプロイに関する情報が多い 月額料金が安い(月643円〜) 歴史のあるサービスだが障害も少なく安定性が高い 2週間の無料お試し期間がある デメリット 最低利用期間が3ヶ月間設定されている 最大でもメモリ32 GB のプランしかない 安定性を重視する方にはさくらのVPSもおすすめです。 ConoHaと同様にリーズナブルですし、ネット上にも情報は多い方です。 ただし最高リソースのプランでもメモリ32 GBなので、Python で機械学習の大規模モデルを動かしたい人には不安が残るかもしれません。 一方でDjangoで作った Web アプリを公開したい人にはあってると思います。 別にさくらのVPSが機能的にDjangoアプリに特化しているということはないはずですが、ググるとなぜかデプロイ手順の解説記事が豊富にヒットします。 一例)https://qiita.com/shibafu/items/b00818f1f102419e6e1c >>さくらのVPS公式サイトを見てみる 3位 WebARENA Indigo 第3位はwebARENA Indigoです。 あの NTT の関連企業であるNTTPCが展開するサービスです。 WebARENA Indigoのメリット 安いプランがめちゃくちゃ安い(月額349円〜) インスタンス全体の状態をバックアップできる(オプション機能) WebARENA Indigoのデメリット OSの選択肢が狭い Web 上の情報が少なめ 今回紹介しているサービスはコスパの良いものに限定していますが、その中でもWebARENA Indigoはリーズナブルです。 最安プランだと月額349円から利用できるという破格の安さ。 (しかし2021年6月時点で僕が確認したタイミングでは最安プランは在庫切れとなっていたのでご注意) またバックアップ機能にも優れており、インスタンス全体の状態を保存しておき後で過去の状態に戻すこともできます。 (ただしこの機能はオプションなので注意してください) 安定運用を重視する方にはオススメです。 デメリットとしては OS の選択肢の少なさですね。 他のVPSでは、Linuxと言っても実に色々な Linux が使用できることが多いのですが、こちらはメジャーなubuntuとcentOSに限定されているようです。 また、先に挙げた二つに比べて体感としてはネット上に出回っている情報が少ないので何かよくわからないエラーが出た時が怖いかもしれません。 >>WebARENA (Indigo)公式サイトを見てみる VPSサーバーを使って出来ること Vps サーバーでは個々のユーザーに Root 権限が与えられているので、好きなライブラリをインストールすることが可能です。 普段のように pip install hogehoge ができるということですね。 ※通常のレンタルサーバーではユーザー全員が同じOSを共有して使う仕組みになっているので、好き勝手にソフトウェアをインストールすることができません。 商業用や研究用にプログラムを公開したい時に、真っ先に候補に上がるのがVPSサーバーです。 特にVPSサーバーが役に立つのは、これから列挙するような場合です。 Pythonでウェブサービスを公開する ご存知の通り Python ではDjangoやflaskといった Web サービスのサーバーサイド用のフレームワークが存在しています。 これらで書いたコードをVPSサーバーにアップロードして公開することが可能です。 アダルトサイト一部のジャンルは VPSサーバー側の規約に引っかかってしまいますが、それ以外のジャンルであれば基本的に OK です。 もちろん収益化も可能です。 ディープラーニングの学習を回す Pythonを使って機械学習のコードを回すことがよくあると思います。 ディープラーニングのそこそこでかいモデルを回す場合は、数日から数ヶ月の時間が必要になることもあります。 自宅でそれだけの長期間コードを回すことが難しい場合は、VPSサーバー上でコードを実行するのもありです。 (作成された重みファイルなどの成果物を別途ダウンロードすることも出来ます) ディープラーニング用の学習を vps サーバー上で回すメリットは以下です。 電気代を自分で負担しなくても良い 学習にメモリなどの自分のパソコンのリソースを割く必要がない 高額の GPU を購入する前にVPSの使用も検討しておいて欲しいところです。 ただし、基本的に現状のVPSサーバーだと、機械学習に特化したGPUが使えないのがほとんどなのでそれなりに時間がかかることは覚悟しておきましょう。 クラウドのファイル置き場として利用する こちらはおまけ的な使い方なのですが、契約したVPSサーバーは Dropbox や Onedriveなどのクラウドのストレージとしても利用することができます。 当然ネット環境さえあればどこからでも契約したサーバーにアクセスすることができます。 ここに頻繁に使うファイルを置いておくなり、大事なデータのバックアップを置いておくなりすると色々捗ります。 仮想通貨のマイニングを行う 最後に vps 上で仮想通貨のマイニングを行うこともできます。 こちらもVPSサーバーのリソースを持て余した時のおまけ的な使い方になるかなと思います。 CPUでもマイニングできる仮想通貨としてはビットゼニーなどが有名です。 自宅パソコンでこういった仮想通貨のマイニングすると、電気代負担で なってしまうことが赤字になってしまうことがよくあります。 なので、マイニングにvps サーバーの余ったリソースを活用して電気代コストを踏み倒しつつ収益を得るのも一つの手です。 ただし、VPSサーバーの利用規約でマイニングが禁止されていないかどうかは各自しっかりとチェックするようにしてください。 >>ConoHaVPS公式サイトを見てみる 参考) ・FXや仮想通貨の自動売買bot稼働にオススメのVPSサービスまとめ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python勉強記録 #8 相関関係 相関係数

入力変数同士の相関関係が強いと多重共線性という問題が発生します。 1 相関関係 入力変数同士の相関が高いかどうかを調べる時には、.corr()を使用します。 # 相関係数の算出 df_corr = df.corr() すると、相関係数が表示されます。 相関係数は1が最大で、その2つの変数が完全に正の相関があることを示しています。 変数aと変数bの相関係数が1の場合は、aが1増えるとbも1増えるような関係です。 また、相関係数が-1の時は、その逆で、aが1増えるとbが1減るような、負の相関を示します。 1.1 相関関係の可視化 seabornを用いて、ヒートマップで相関関係を可視化をすることができます。 plt.figure(figsize=(12, 8)) # figsizeは一つ一つのマス(プロット)の大きさを調整できる sns.heatmap(df_corr.iloc[:20, :20], annot=True); # .iloc[:20,:20]とすると、最初の20行、20列だけが表示される # annot=Trueでヒートマップ中に数値を表示することができる (復習) df_corr[df_corr['A'] > 0.5]のように書くことでA列の値が0.5を超える行だけを表示することができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

天文データ解析入門 その10 (astropy.coordinates.SkyCoordのあれこれ)

本記事では、astropy.coordinates.SkyCoord の簡単な使い方について紹介します。 まず必要なものを import します。 from astropy.coordinates import SkyCoord from astropy.coordinates import ICRS, Galactic, FK4, FK5 import astropy.units as u import numpy as np 座標オブジェクトの定義 FK5 coords = "12h34m56.7s +12d34m56.7s" # スペースの位置に注意 c = SkyCoord(coords, frame=FK5, unit="deg") # unit=u.deg でもok print(c) #<SkyCoord (FK5: equinox=J2000.000): (ra, dec) in deg # (188.73625, 12.58241667)> coords = "12:34:56.7 +12:34:56.7" # : でもいける c = SkyCoord(coords, frame=FK5, unit=u.deg) coords = "12 34 56.7 +12 34 56.7" # スペースでもいける c = SkyCoord(coords, frame=FK5, unit=u.deg) coords = "188.73625 12.58241667" # degree でもいける c = SkyCoord(coords, frame=FK5, unit=u.deg) coords = ["1h12m45.6s +1d23m45.6s", "12h34m56.7s +12d34m56.7s"] # list や numpy array も食べられる c = SkyCoord(coords, frame=FK5, unit=u.deg) print(c) #<SkyCoord (FK5: equinox=J2000.000): (ra, dec) in deg # [( 18.19 , 1.396 ), (188.73625, 12.58241667)]> Galactic coords = "12.34 0.12" c = SkyCoord(coords, frame=Galactic, unit="deg") #<SkyCoord (Galactic): (l, b) in deg # (12.34, 0.12)> c = SkyCoord(12.34, 0.12, frame=Galactic, unit="deg") c = SkyCoord("12h34m56.7s +0d12m34s", frame=Galactic) print(c) #<SkyCoord (Galactic): (l, b) in deg # (188.73625, 0.20944444)> オブジェクト内に obstime や radial_velocity などを格納することも可能。 座標の取り出し FK5 coords = "12h34m56.7s +12d34m56.7s" c = SkyCoord(coords, frame=FK5, unit="deg") print(c.ra) print(c.dec) print(c.ra.deg) print(c.dec.deg) #188d44m10.5s #12d34m56.7s #188.73624999999998 #12.582416666666667 Galactic coords = "12.34 0.12" c = SkyCoord(coords, frame=Galactic, unit="deg") print(c.l) print(c.b) print(c.l.deg) print(c.b.deg) #12d20m24s #0d07m12s #12.34 #0.12 座標の変換 coords = "12h34m56.7s +12d34m56.7s" c = SkyCoord(coords, frame=FK5, unit="deg") print(c.galactic.l.deg) print(c.galactic.b.deg) #287.2526296864695 #74.94982709188524 coords = "12.34 0.12" c = SkyCoord(coords, frame=Galactic, unit="deg") print(c.fk5.ra.deg) print(c.fk5.dec.deg) #273.0271677731397 #-18.183520254020582 天体名から座標を取得 W51 = SkyCoord.from_name('W51') print(W51) #<SkyCoord (ICRS): (ra, dec) in deg # (290.95833333, 14.1)> Az–El (方位角–仰角) を取得 from astropy.coordinates import EarthLocation, AltAz from astropy.time import Time nobeyama = EarthLocation(lon=138.472555556*u.deg, lat=35.9446944444*u.deg, height=1350*u.m) utcoffset = 9*u.hour obstime = Time('2021-6-11 00:00:00') - utcoffset W51 = SkyCoord.from_name('W51') W51altaz = W51.transform_to(AltAz(obstime=obstime, location=nobeyama)) print(W51altaz) print(W51altaz.az.deg) #Az print(W51altaz.alt.deg) # El #<SkyCoord (AltAz: obstime=2021-06-10 15:00:00.000, location=(-3870981.01680331, 3428061.63171939, #3724017.76068491) m, pressure=0.0 hPa, temperature=0.0 deg_C, relative_humidity=0.0, obswl=1.0 micron): (az, #alt) in deg # (123.1085355, 56.40766096)> #123.10853550436268 #56.4076609561065 以上です。 リンク 目次
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C++のTupleがあればPythonみたいな便利な書き方ができる

概要 C++11以降に追加された機能にTupleがあります。 これはPythonなどのプログラミング言語でおなじみなのですが、TupleによってC++でもPythonのような書き方ができるようになりました。 関数の複数戻り値 #include <iostream> #include <tuple> std::tuple<int, int> add_and_sub(int a, int b) { return {a + b, a - b}; } int main() { auto [aa, bb] = add_and_sub(20, 10); std::cout << aa << " " << bb << std::endl; return 0; } 表示は 30 10 となります。Tupleを使用することで複数の戻り値を返すことができます。そしてそれを構造化束縛を用いて複数の変数で受け取るという流れです。Pythonで書けば以下のようになります。 def add_and_sub(a, b): return (a + b, a - b) aa, bb = add_and_sub(20, 10) 範囲for #include <iostream> #include <tuple> #include <vector> int main() { std::vector<std::tuple<int, int>> v{ {10, 20}, {2, 2}, {4, 1} }; for (auto [aa, bb] : v){ std::cout << aa << " " << bb << std::endl; } return 0; } 構造化束縛は範囲forとも相性が抜群です。TupleのVectorに対するループは上記のように書けます。Pythonで書くと以下のようになります。 v = [(10, 20), (2, 2), (4, 1)] for (aa, bb) in v: print(aa, bb) 構造体のソート C++のTupleはPythonのと同様にprimitive型のみを含んでいればそのままソートすることができます。これを利用することで構造体のソートロジックを簡単に実装することができます。またラムダ関数とも相性が抜群です。 #include <algorithm> #include <iostream> #include <string> #include <tuple> #include <vector> struct Data { int a; int b; }; int main() { std::vector<Data> v{ {5, 6}, {1, 2}, {3, 4} }; std::sort(v.begin(), v.end(), [](const auto &lhs, const auto &rhs){ auto lt = std::make_tuple(lhs.a, lhs.b); auto rt = std::make_tuple(rhs.a, rhs.b); return lt < rt; }); for (auto [aa, bb] : v){ std::cout << "Data[" << aa << "," << bb << "]" << std::endl; } return 0; } そう、実は構造化束縛はTuple以外の構造体にも使えるんです。Pythonで書くとこう。 import dataclasses @dataclasses.dataclass class Data: a: int b: int v = [ Data(5, 6), Data(1, 2), Data(3, 4) ] v.sort(key=lambda d: (d.a, d.b)) for d in v: print(d) auto, Tuple, 構造体束縛, 範囲for, lambda関数などをうまく使えばC++もPythonと同様に簡単に書けますね!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonはなぜグルー言語と呼ばれるのか?

はじめに Pythonはグルー言語だと称されますが、その心は、他言語(C等)で書かれたライブラリを簡単に利用できる特徴を指しています。 他言語で実装されたステキライブラリ(データ分析周りの例ばかりになって恐縮ですが、NumPy/Pandas/Tensorflow...)を、平易に記述できるPythonコードから利用できるのは開発者観点からもステキですね。 PythonからCをどう呼び出すのかと言いますと、SciPy公式ドキュメントにそのままズバリ「Using Python as glue」との章があり、こちらに2つの方法が書いてあります。 拡張モジュールを作成し、importコマンドを使用してPythonにインポートする方法 Pythonから直接共有ライブラリを呼び出す方法 前者については、CでPython処理系の拡張モジュールを作成する方法です。 Python処理系(CPython)はCで書かれていますから、Cでコードを書き、処理系に紐付けてあげることで、Pythonモジュールとして利用できるわけです。 Python/C APIというPython処理系のAPIが公開されているので、こちらを利用して実装します。 後者についてはctypes等のPythonモジュールを使ってCの共有ライブラリをロードし、ロードした共有ライブラリから直接関数を呼び出す方法です。 以降では、PythonとCを連携する2つの方法それぞれについてもう少し詳しく説明します。 方法①:拡張モジュールを作成しCと連携 拡張モジュールはPython/C APIを利用して作成するのは前述の通りです。 改めてPython/C APIの説明ですが、 Pythonインタプリタに対する様々なレベルでのアクセス手段をCやC++のプログラマに提供 するものです1。 愚直にPython/C APIを使って拡張モジュールを作成する手順としては、 python.hをincludeしたCコードを書く(python.h内にPython処理系のAPIに関する関数や型の定義) できたCコードをビルドし、処理系が読み込めるように紐付ける といった流れです。 基本的には、Cのステキなライブラリを包むラッパーコードをpython.h使って作成して、Pythonから呼び出せるようにする、みたいなユースケースが多いかなと思います。 とはいえ、Python/C APIを直書きしてラッパーコードを書くことにはツラミもありまして、 手作業での開発だと作成や保守作業にかかるコストが大きいという点2 C/C++を書く必要がある点(Pythonプログラマには辛い) があげられるかと思います。 前者については、ラッパーを自動生成するツール、例えばSWIG等の解決方法があります。 SWIGでは、簡易な定義ファイルをもとにラッパーコードが自動生成されます。 後者についてはPythonライクなCythonでコードを書くことでツラミを軽減する方法等があるかなと思われます。 方法②:直接共有ライブラリを呼び出す(FFI) FFI(Foreign function interface)とは Wikipedia引用。 Foreign function interface (FFI)とは、あるプログラミング言語から他のプログラミング言語で定義された関数などを利用するための機構 まさにPythonからCのライブラリを呼ぶことはFFIですね。 前述の「拡張モジュールを作成しCと連携」することもFFIと呼ぶとは思いますが、本稿では便宜上、直接共有ライブラリを呼び出すような言語間連携を特にFFIと表現します。 FFIの流れについては、こちらの記事が圧倒的に分かりやすかったです。 流れを簡単にまとめると、 Cの関数が入ったライブラリをロードする Cの関数(関数ポインタ)を呼び出す 関数の引数として渡すために、Pythonの型をCの型に変換する 関数の戻り値を受け取るために、Cの型をPythonの型に変換する といった感じです。 ライブラリのロードも関数の呼び出しも、全てPythonコード上で行われます。 ライブラリのロードや言語間の型変換3といった操作はFFIでは頻出ですが、そうした機能をまとめたライブラリとして、例えばlibffiが挙げられます。 libffiをもとにしたライブラリは多くのスクリプト言語に存在し、Pythonならctypes、Rubyだとruby-ffi等のライブラリ(モジュール)として利用できます。 ctypesはPythonの標準モジュールです。 libffiのラッパーであり、Cと互換性のあるデータ型を提供し、共有ライブラリ内の関数呼び出しを可能にします。 NumPyの内部でも使われているようです。 ctypes、なんでCの関数を呼べるの? 実際にPythonからCを呼び出すFFIはどのように動作しているのでしょうか。 C等をコンパイルした機械語(バイナリ。ctypesが呼び出す共有ライブラリももちろんバイナリです)の仕様のことをABIと呼びます。 ABIでは、 関数呼び出しで変更されるレジスタとされないレジスタ intやlongなどの型のサイズ 構造体のレイアウトのルール ビットフィールドのレイアウトのルール 等が規定されますが、今回の例で特に重要なのが、関数の呼び出し方(呼び出し規約)が定まっている点です。 ABIの通りに、引数を格納する/戻り値がセットされる場所(レジスタ等)の把握等ができていれば、関数を呼び出すことができるわけです。 C/C++ともに標準化されたABIはない(異なるコンパイラでは互換性のないバイナリを生成する可能性がある)のですが、Cは古く安定した言語なので、異なるコンパイラ間でも(基本的に)同じABIを満たしたバイナリが得られます。 ctypesではCのABIを満たした機械語を受け取ることを想定しています。 CのABI通りに共有ライブラリを解釈することで、関数呼び出しが可能になるというスンポーです。 例えばGoでは-buildmode=c-sharedをつけたビルドでCのABIを持ったバイナリを作れるので、こいつをctypesに食わせればPythonから呼び出せます。 このように、CのABIを通せばさまざまな言語間を連携することができます。 まとめ Pythonは拡張モジュールやFFIを利用する仕組みが整っているので、他言語で作られた資産を利用しやすいグルー言語であることが分かりました。 普段はほとんどPython書かないので誤り等あればご指摘お願いいたします?‍♂️ 用語整理など ラッパー、バインディング、ポーティング FFI等について調べているとよく出てくる用語ですが、上記の記事を参考にまとめました。 ラッパー ライブラリの機能を同じ言語でラッピング バインディング ライブラリの機能を別の言語でラッピング ポーティング ライブラリを別の言語で書き換え とはいえ原理原則ということではなく、原義的にはこんな感じって認識で良いと思います。 Pyhonでは他にどんな言語間連携がある? こちらの記事にまとまっています。 余談ですが、C以外で書かれた処理系、PyPyとかではC APIをエミュレートしたりしてるようです(詳しくは読んでない) ↩ 「Web技術文書からのFFI自動生成に関する実践」参照。それこそCライブラリの変更を追従する必要があったりは辛そう ↩ FFIでは型の変換が頻出ですが「そもそも型のメモリ上の表現が違うから変換する必要がある」みたいな理解で良いんですかね ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

manimの作法 その60

概要 manimの作法、調べてみた。 add_updater、使ってみた。 サンプルコード from manimlib.imports import * def temperature_to_color(temp, min_temp = -1, max_temp = 1): colors = [BLUE, TEAL, GREEN, YELLOW, "#ff0000"] alpha = inverse_interpolate(min_temp, max_temp, temp) index, sub_alpha = integer_interpolate(0, len(colors) - 1, alpha) return interpolate_color(colors[index], colors[index + 1], sub_alpha) def two_d_temp_func(x, y, t): return np.sum([c * np.sin(f * var) * np.exp(-(f ** 2) * t) for c, f, var in [(0.2, 1, x), (0.3, 3, x), (0.02, 5, x), (0.01, 7, x), (0.5, 2, y), (0.1, 10, y), (0.01, 20, y), ]]) class test(ThreeDScene): CONFIG = { "cells_per_side": 20, "body_height": 6, } def construct(self): self.introduce_body() self.show_temperature_at_all_points() def introduce_body(self): height = self.body_height buff = 0.025 rows = VGroup(*[VGroup(*[Dot(stroke_width = 0, fill_opacity = 1, ) for x in range(self.cells_per_side)]).arrange(RIGHT, buff = buff) for y in range(self.cells_per_side)]).arrange(DOWN, buff=buff) for row in rows[1::2]: row.submobjects.reverse() body = self.body = VGroup(*it.chain(*rows)) body.set_height(height) body.center() body.to_edge(LEFT) axes = self.axes = Axes(x_min = -5, x_max = 5, y_min = -5, y_max = 5, ) axes.match_height(body) axes.move_to(body) for cell in body: self.color_cell(cell) plate = Square(stroke_width = 0, fill_color = DARK_GREY, sheen_direction = UL, sheen_factor = 1, fill_opacity = 1, ) plate.replace(body) plate_words = TextMobject("Piece of \\\\ metal") plate_words.scale(2) plate_words.set_stroke(BLACK, 2, background = True) plate_words.set_color(BLACK) plate_words.move_to(plate) self.play(DrawBorderThenFill(plate), Write(plate_words, run_time = 2, rate_func = squish_rate_func(smooth, 0.5, 1))) self.wait() self.remove(plate_words) def show_temperature_at_all_points(self): body = self.body start_corner = body[0].get_center() dot = Dot(radius = 0.01, color = WHITE) dot.move_to(start_corner) get_point = dot.get_center lhs = TexMobject("T = ") lhs.next_to(body, RIGHT, LARGE_BUFF) decimal = DecimalNumber(num_decimal_places = 1, unit = "^\\circ") decimal.next_to(lhs, RIGHT, MED_SMALL_BUFF, DOWN) decimal.add_updater(lambda d: d.set_value(40 + 50 * self.point_to_temp(get_point()))) arrow = Arrow(color = YELLOW) arrow.set_stroke(BLACK, 8, background = True) arrow.tip.set_stroke(BLACK, 2, background = True) arrow.add_updater(lambda a: a.put_start_and_end_on(lhs.get_left() + MED_SMALL_BUFF * LEFT, get_point(), )) dot.add_updater(lambda p: p.move_to(body[-1] if (1 < len(body)) else start_corner)) self.add(body, dot, lhs, decimal, arrow) self.play(ShowIncreasingSubsets(body, run_time = 10, rate_func = linear, )) self.wait() self.remove(dot) self.play(FadeOut(arrow), FadeOut(lhs), FadeOut(decimal), ) def point_to_temp(self, point, time = 0): x, y = self.axes.point_to_coords(point) return two_d_temp_func(0.3 * x, 0.3 * y, t = time) def color_cell(self, cell, vect = RIGHT): p0 = cell.get_corner(-vect) p1 = cell.get_corner(vect) colors = [] for point in p0, p1: temp = self.point_to_temp(point) color = temperature_to_color(temp) colors.append(color) cell.set_color(color = colors) cell.set_sheen_direction(vect) return cell 生成した動画 以上。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

manimの作法 その59

概要 manimの作法、調べてみた。 TransformFromCopy、使ってみた。 サンプルコード from manimlib.imports import * class test(Scene): CONFIG = { "k": 0.2, "initial_water_temp": 80, "room_temp": 20, "delta_T_color": YELLOW, } def construct(self): self.setup_axes() self.show_temperatures() self.show_graph() self.show_equation() self.talk_through_examples() def setup_axes(self): axes = Axes(x_min = 0, x_max = 10, y_min = 0, y_max = 100, y_axis_config = { "unit_size": 0.06, "tick_frequency": 10, }, center_point = 5 * LEFT + 2.5 * DOWN) x_axis = axes.x_axis y_axis = axes.y_axis y_axis.add_numbers(*range(20, 100, 20)) x_axis.add_numbers(*range(1, 11)) x_axis.label = TextMobject("Time") x_axis.label.next_to(x_axis, DOWN, MED_SMALL_BUFF) y_axis.label = TexMobject("\\text{Temperature}") y_axis.label.next_to(y_axis, RIGHT, buff = SMALL_BUFF) y_axis.label.align_to(axes, UP) for axis in [x_axis, y_axis]: axis.add(axis.label) self.add(axes) self.axes = axes def show_temperatures(self): axes = self.axes water_dot = Dot() #water_dot.color_using_background_image("VerticalTempGradient") water_dot.move_to(axes.c2p(0, self.initial_water_temp)) room_line = DashedLine(axes.c2p(0, self.room_temp), axes.c2p(10, self.room_temp), ) room_line.set_color(BLUE) #room_line.color_using_background_image("VerticalTempGradient") water_arrow = Vector(LEFT, color = WHITE) water_arrow.next_to(water_dot, RIGHT, SMALL_BUFF) water_words = TextMobject("Initial water\\\\temperature") water_words.scale(0.7) water_words.next_to(water_arrow, RIGHT) room_words = TextMobject("Room temperature") room_words.scale(0.7) room_words.next_to(room_line, DOWN, SMALL_BUFF) self.play(FadeInFrom(water_dot, RIGHT), GrowArrow(water_arrow), Write(water_words), run_time = 1, ) self.play(ShowCreation(room_line)) self.play(FadeInFromDown(room_words)) self.wait() self.set_variables_as_attrs(water_dot, water_arrow, water_words, room_line, room_words, ) def show_graph(self): axes = self.axes water_dot = self.water_dot k = self.k rt = self.room_temp t0 = self.initial_water_temp graph = axes.get_graph(lambda t: rt + (t0 - rt) * np.exp(-k * t)) #graph.color_using_background_image("VerticalTempGradient") def get_x(): return axes.x_axis.p2n(water_dot.get_center()) brace_line = always_redraw(lambda: Line(axes.c2p(get_x(), rt), water_dot.get_center(), stroke_width = 0, )) brace = always_redraw(lambda: Brace(brace_line, RIGHT, buff = SMALL_BUFF)) delta_T = TexMobject("\\Delta T") delta_T.set_color(self.delta_T_color) delta_T.add_updater(lambda m: m.next_to(brace, RIGHT, SMALL_BUFF)) self.add(brace_line) self.play(GrowFromCenter(brace), Write(delta_T), ) self.play(ShowCreation(graph), UpdateFromFunc(water_dot, lambda m: m.move_to(graph.get_end())), run_time = 10, rate_func = linear, ) self.wait() self.graph = graph self.brace = brace self.delta_T = delta_T def show_equation(self): delta_T = self.delta_T equation = TexMobject("{d ({\\Delta T}) \\over dt} = -k \\cdot {\\Delta T}", tex_to_color_map = { "{\\Delta T}": self.delta_T_color, "-k": WHITE, "=": WHITE, }) equation.to_corner(UR) equation.shift(LEFT) delta_T_parts = equation.get_parts_by_tex("\\Delta T") eq_i = equation.index_of_part_by_tex("=") deriv = equation[:eq_i] prop_to = equation.get_part_by_tex("-k") parts = VGroup(deriv, prop_to, delta_T_parts[1]) words = TextMobject("Rate of change", "is proportional to", "itself", ) words.scale(0.7) words.next_to(equation, DOWN) colors = [BLUE, WHITE, YELLOW] for part, word, color in zip(parts, words, colors): part.word = word word.set_color(color) word.save_state() words[0].next_to(parts[0], DOWN) self.play(TransformFromCopy(VGroup(delta_T), delta_T_parts, ), Write(VGroup(*filter(lambda p: p not in delta_T_parts, equation)))) rects = VGroup() for part in parts: rect = SurroundingRectangle(part, color = part.word.get_color(), buff = SMALL_BUFF, stroke_width = 2, ) anims = [ShowCreation(rect), FadeIn(part.word), ] if part is parts[1]: anims.append(Restore(words[0])) self.play(*anims) rects.add(rect) self.play(FadeOut(rects, lag_ratio = 0.2)) self.equation = equation self.equation_words = words def talk_through_examples(self): dot = self.water_dot graph = self.graph self.play(MoveAlongPath(dot, graph, rate_func = lambda t: smooth(1 - t), run_time = 2, )) def get_slope_line(self, graph, x): pass 生成した動画 以上。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでdatetimeをimportするスタイルについて調査した

はじめに Pythonでdatetimeモジュールをインポートする際に、Webで調べてみると様々なスタイルでインポートされていることが分かります。 様々なスタイルでのdatetimeモジュールのインポート例 import datetime import datetime as dt from datetime import datetime, timedelta, ... from datetime import datetime as dt これは、 import numpy as npやimport pandas as pdのように、datetimeをdtと省略したい datetime.datetime を直接インポートしたい といった考えによって生まれたスタイルだと思われます。 厳密には datetime と datetime.datetime は別物ですが、datetime.datetime しか使わない場合には下2行のスタイルが使われることも多いかと思います。 しかし、自身でスタイルが統一されていないと、datetime という文字を見たときに、datetime と datetime.datetime のどちらなのかややこしくなるでしょう。 そこで、自身のプロダクト開発においてdatetimeのインポートスタイルを統一したいと考え、githubの有名OSSを調べることで、一般的にどのスタイルが多く使われているのか調べてみることにしました。 各OSSの調査方法 今回は、githubでスター数の多いPythonレポジトリを参考にして、それぞれどのようなスタイルでdatetimeをインポートしているのか確認していきます。 https://github.com/search?l=Python&q=stars%3A%3E1&s=stars&type=Repositories こちらのページの上からそれぞれのレポジトリで import datetime で検索して確認していきます。 調査結果 datetimeをインポートしているOSSについて、そのスタイルをまとめました。 ついでに、Python公式Docsについても調べてみました。 import datetime import datetime as dt from datetime import datetime from datetime import datetime as dt docs.python.org ◯ TheAlgorithms/Python ◯ jackfrued/Python-100-Days ◯ ◯ ytdl-org/youtube-dl ◯ tensorflow/models ◯ ◯ nvbn/thefuck ◯ django/django ◯ ◯ pallets/flask ◯ ◯ httpie/httpie ◯ ansible/ansible ◯ ◯ huggingface/transformers ◯ ◯ scikit-learn/scikit-learn ◯ 学んだこと 実際にOSSを調べてみたことで、以下の点について学ぶことができました。 ほとんどのOSSが from datetime import datetime を採用している - Python公式Docs、scikit-learnなどは from datetime import datetime の形式のみを採用している import datetime と from datetime import datetime が混在しているOSSも多い as dt は使われていない そこで、私自身の結論として、 基本的には from datetime import datetime のスタイルを採用する どうしても必要になったときには import datetime を使用することとする というスタイルを採用しようと思います。 おわりに githubの有名OSSを調査することで、datetimeが一般的にどのようなスタイルでインポートされているか確認できました。 この調査が皆さんのプロダクト開発のお役に立てれば幸いです。 以上です。ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Matplotlib定量比較-ヒストグラム

ヒストグラムとは ヒストグラムは変数の全体的分布を示すものです。 ヒストグラムを見て、どんな数値が多いか、少ないか、偏りがあるかないかを読み解きます。 ヒストグラムのX軸は階級(データを区切った空間)、Y軸は度数(データの数量)を表します。 簡単なヒストグラム import matplotlib.pyplot as plt fig, ax = plt.subplots() ax.hist(data01['colName']) ax.hist(data02['colName']) ax.set_xlabel('Data') ax.set_ylabel('Frequency') plt.show() 常用オプション label : 凡例のラベル(凡例を表示するためplt.legend()との併用が必要) bins : ビンの数またはビンの境界 例:bin=5 or bin=[100, 150, 200, 250, 300] histtype : ヒストグラムの種類 stepは透明 import matplotlib.pyplot as plt fig, ax = plt.subplots() ax.hist(data01['colName'], label='Data01', bins=5, histtype='step') ax.hist(data02['colName'], label='Data02', bins=5, histtype='step') ax.set_xlabel("Data") ax.set_ylabel("Frequency") ax.legend() plt.show()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Natural Language API利用時のクレデンシャルを文字列で指定したい

目的と経緯 AWS Lambdaで Cloud Natural Language をPython用SDK で操作することが目的です Getting started with authentication によると環境変数 GOOGLE_APPLICATION_CREDENTIALS へサービスアカウントJSONファイルパスを指定すれば良いと書いてあり、当然その通りにすれば動作しました この方法はファイルを読み込む前提であり、AWS LambdaやGoogle以外のコンテナ系環境で実行する場合は実行環境内にファイルを設置する必要があります デプロイファイルセットにAPIアクセス用のクレデンシャルファイルを含めるのは少し躊躇するところです 今回はAWSなので パラメータストア(AWS Systems Manager)へクレデンシャル内容を保持し、実行時に取得することにしました パラメータストアから取得した時点で文字列は変数に保持できているので、それをそのまま使ってくれよ、というのが自然かなと思うのですが現時点のSDK(version2.0.0)にはそのインターフェイスは用意されていないようです パラメータストアから取得した内容(JSON文字列)をテンポラリファイルへ書き出し、そのパスを環境変数 GOOGLE_APPLICATION_CREDENTIALS へセットしてSDK利用する、という方法でも動作すると思いましたが、それもいかにも無駄かなと感じます ※用意されたインターフェイスに従うという意味では間違いとは思っていません そこで、ファイルを介さずに直接クレデンシャルJSONを利用する方法としたので、メモと何かの参考になればと記録します コード クライアントオブジェクトを取得する google.oauth2.service_account.Credentialsのクラスメソッド from_service_account_info() を引っ張り出し、クレデンシャルをロードしています 要は LanguageServiceClient() のcredentialsパラメータを指定すれば良いのです。指定内容が本記事で言いたいことすべてです from google.cloud.language_v1 import LanguageServiceClient from google.oauth2.service_account import Credentials def get_language_client(credential: dict) -> LanguageServiceClient: return LanguageServiceClient( credentials=Credentials.from_service_account_info(credential)) 利用例 実行コード import json from google.cloud import language_v1 # 省略しますがサービスアカウントJSONファイルの内容そのままをセット(実際はパラメータストアなどから取得する想定) credential_json = '{\n "type": "service_account",\n "project_id": "...",\n ....' # 作成したメソッドでクライアントオブジェクトを取得 client = get_language_client(json.loads(credential_json)) # エンティティ分析を呼び出して動作を確認 text = '東京の夏は暑い' response = client.analyze_entities( document=language_v1.Document( content=text, type_=language_v1.Document.Type.PLAIN_TEXT), encoding_type=language_v1.EncodingType.UTF8) for entity in response.entities: print('{}\n{}\n{}\n{}\n'.format( '='*20, entity.name, entity.type_.name, entity.metadata)) 実行結果 ==================== 東京 LOCATION {'wikipedia_url': 'https://de.wikipedia.org/wiki/Tokio', 'mid': '/g/12lnhn10f'} ==================== 夏 OTHER {} 最後に 現時点でv2.0.0がリリースされていますが、未リリースのgithubコード上ではメソッド追加されているようです このバージョンがリリースされれば LanguageServiceClient.from_service_account_info(key_dict) を使えるようになると思われます ※見返したところ、冒頭の「目的と経緯」が長いポエム状態でした。夜中に書いたのですみません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでLinux に wxPythonをインストールする

これして sudo apt install libsdl2-2.0-0 libsdl2-dev これする pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-20.04 wxPython ※ubuntuのバージョンは要確認 だけでは動かなかった これをトライしてみる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pandasのdataframeで和暦を西暦に変換する

はじめに 巷には未だに和暦のcsvデータが溢れています。 和暦のcsvデータをreadして、いい感じに西暦なdataframeにしたいと思っている人への備忘です。 結論 整形前 input.csv "年月日","メモ" R03.05.15,"メモ1" R03.06.04,"メモ2" 整形後 date memo 0 2021-05-15 メモ1 1 2021-06-04 メモ2 コード import pandas as pd from datetime import datetime from dateutil.relativedelta import relativedelta def read_wareki_csv(csv_filename): df = pd.read_csv(csv_filename, header=None, names=['date', 'memo'], skiprows=1, parse_dates=[0], date_parser=wareki_parser) return df def wareki_parser(date): if date[0]=='R': dt = datetime.strptime(date, 'R%y.%m.%d') dt = dt + relativedelta(years=18) elif date[0]=='H': dt = datetime.strptime(date, 'H%y.%m.%d') dt = dt - relativedelta(years=12) return dt if __name__ == '__main__': df = read_wareki_csv("./input.csv") print(df) 解説 read_csv def read_wareki_csv(csv_filename): df = pd.read_csv(csv_filename, header=None, names=['date', 'memo'], skiprows=1, parse_dates=[0], date_parser=wareki_parser) return df pandasのread_csvには、便利なオプションとして、指定のcolumnをdatetime型で読み込むparse_datesと、parse_datesで指定したcolumnを処理するparserを指定するdate_parserがあります。 parse_datesにはlistとかdictとかを指定しましょう。ここをparse_dates=0とかにすると、TypeError: Only booleans, lists, and dictionaries are accepted for the 'parse_dates' parameterと怒られます。 date_parserには自作関数を指定します。この自作関数の中で和暦→西暦変換を書いてあげましょう。 今回は、令和と平成の両方に対応する感じでやります。 parser def wareki_parser(date): if date[0]=='R': dt = datetime.strptime(date, 'R%y.%m.%d') dt = dt + relativedelta(years=18) elif date[0]=='H': dt = datetime.strptime(date, 'H%y.%m.%d') dt = dt - relativedelta(years=12) return dt ちょっとトリッキーなやり方ですが、先にRとかHとかを排除して、2桁の数字を下2桁西暦としてそのまま読み込んでしまいます。そして、読み込んだ後に和暦→西暦変換で必要な年数を加減算します。 datetime.strptimeではinputデータのフォーマットを書いてあげます。ちなみに%yが下2桁の西暦(20XX)で、%Yが4桁の西暦です。 年数の加減算には、dateutil.relativedeltaを使用しました。 最初、timedeltaで年計算をしようとしましたが、日単位の計算しかできない?みたいで、年数×365とかやってると閏年の処理を考えるのがめんどくさかったのでこちらにしました。 なお、今回は令和と平成のみにしましたが、同様に昭和等も対応可能です。 皆様、素敵な和暦データ処理をお楽しみください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む