- 投稿日:2022-01-25T23:09:21+09:00
【合格】Pythonエンジニア認定データ分析試験の勉強方法
Python基礎試験に引き続いて受験し、合格しました。準備期間が短かったこともあり、点数に余裕のない合格でした。Pythonの基本文法が聞かれるというよりは、データ分析にまつわる知識や理解が問われる試験です。少しだけですが数学も絡むので、初見で公式テキストに取り掛かった段階では少々理解が辛く感じてしまいました。Youtubeで動画を拾い見してからテキストに戻るなど、工夫が必要でした。 せっかくPython勉強したから何か形にして残しときたい&Python基礎試験を受けたのでその勢いでついでに申し込んだ程度の動機で受験しました。受験料が税込11000円と高額な民間の試験です。実際、後の転職活動で一応記載はしましたが、面接官だったDSの方々はこの試験をご存じなかったです。 受験状況 勉強期間:3週間 受験日:2021年3月 使用教材 公式テキスト あたらしいPythonによるデータ分析の教科書 動画(Youtube) Jupyter Notebook 超入門 numpy 超入門 pandas 超入門 matplotlib 超入門 中学数学からはじめる微分積分 分散・標準偏差【超わかる!高校数学ⅠA】データの分析 #16 サイト(無料) DIVE INTO EXAM 模擬試験1回分 サイト(無料) PRIME STUDY 模擬試験3回分 費用合計:13800円 公式テキスト:2800円 受験料:11000円 教材と勉強方法 「あたらしいPythonによるデータ分析の教科書」Pythonデータ分析試験の公式テキスト アマゾンで公式テキストを購入しました。よくわからない部分も多かったので、流し読み。合格者の勉強方法の記事を検索して読んでみると、動画で勉強してからテキストに戻っている人が見受けられたので、真似して2倍速で一気になんとなく全体を頭に入れました。 見た動画一覧 動画を見終わってから再度公式テキストに戻ってさっと1周だけ目を通しました。そのあとは模擬試験に取り掛かりました。模擬試験の無料サイトは、下記の2つがあります。いずれも無料です。 DIVER | DIVE INTO CODE(DIC) PRIME STUDY(プライム・スタディ)Python試験とPHP試験の無料模擬試験サイト DIVE INTO EXAMを先に解いて、次にPRIME STUDYをやりました。この順番は失敗したと思っていて、PRIME STUDYの方が本番試験との関連が強かったように感じました。DIVE INTO EXAMも良いのですが、少々教科書からずれてることがあったり、教科書に載ってなくて(これは試験範囲外なのでは?)と思うなど違和感がありました。 DIVEを先にやったために時間がなくなってしまい、PRIME STUDYの模擬は3種類中、1種類しか解けませんでした。PRIME STUDYを先に全部消化していたら、もう少し点数伸びていたかも? 試験結果 合格 775 / 1000点(合格基準は700点) Pythonデータ分析試験の合格証 Pythonデータ分析試験の試験結果レポート 3週間前にPythonエンジニア基礎試験に合格していたので、基礎はOKだろうと思い臨んだところ、Python文法基礎のカテゴリを全部落としました。配点が1問25点換算になるので、あと3問ミスしていたら不合格でした。ギリギリの合格…危なかった。
- 投稿日:2022-01-25T22:48:46+09:00
【合格】Pythonエンジニア認定基礎試験の勉強方法
Pythonを業務で触った経験があり、体系立てて一度勉強したかったので、そのモチベーション到達点として試験を利用しました。民間会社が行っているものなので知名度は微妙ですが、学習レベルがまだ初歩段階の人にはよく受けられている試験です。 「基礎試験」と「データ分析試験」があり、今回受験したのは基礎試験です。Pythonの基礎文法が問われる試験です。 受験状況 勉強期間:20日 受験日:2021年2月 使用教材 Udemy動画 現役シリコンバレーエンジニアが教えるPython3入門+応用+アメリカのシリコンバレー流コードスタイル | Udemy 公式テキスト Pythonチュートリアル第3版(※最新は第4版になってますのでご注意ください) サイト(無料)DIVE INTO EXAM 模擬試験 サイト(無料)PRIME STUDY 模擬試験 費用合計:13400円 Udemy動画:1400円 公式テキスト中古:1000円 受験料:11000円 使用教材と勉強方法 最初に動画から入りました。基礎文法の講座ならなんでもよいかと思い、Udemyセールのタイミングで目についた動画を購入しました。 現在は、少しUdemyの雰囲気が変わってしまってあまり好きではない(主観)ので、人に薦めるならjmoocのgaccoやcourseraを推したいかな〜とおもってます 動画を倍速で見て概観を掴んだら、DIVE INTO CODEで模擬試験を受けてみました。375 / 1000点でひどい点数でしたが、とりあえずどんな問題が出るのかを把握。 次に、公式テキストのPythonチュートリアルを読み始めます。 公式テキストに目を通し終わったら、模擬試験に再びトライします。試験の中で出てくるコードを実行しながらゆっくり進めました。解きながら知識を吸収します。模擬に出てきた内容は公式テキストにマークしたり書き込みしたりして、必要知識を一元化しておきます。 試験結果 合格 975 / 1000点(合格基準点は700点) ▼追記:Pythonデータ分析試験も合格しました。
- 投稿日:2022-01-25T20:35:02+09:00
スプレッドシート対応抽選デスクトップアプリの作成
はじめに ■抽選デスクトップアプリvar0.1 報告 抽選デスクトップアプリを制作してみた。 例えば、ポケモンのパーティをランダムに構築してみたいと思った時や、 トレーディングカードゲームで、デッキから最初に引く5枚をシミュレーションする 等に利用できる。 きっかけ 最近、転職や副業、動画配信での利用を意識するようになり、 苦手意識のあるGUIのデスクトップアプリに挑戦してみることにした。 実は抽選プログラムそのものはすでにできていたのだが、 開発環境から実行するにとどまっていたため、 今回のGUIのデスクトップアプリの制作対象とした。 tkinterの理由 GUIアプリ作成にあたり、様々なライブラリがあったが、 標準で使え、参考資料も多いtKinterを試してみることにした。 開発環境 ・Eclipse 4.8 photon ・Python3.10.0 イメージ図 配信でadob XDを利用を試みたが、 配信が上手くいかなかったため、 泣く泣くペイントツールを使用した。 イラストの出来は許してほしい。 簡単にまとめると以下のような動きをしたい。 ・上のテキストボックスに入力したい数を記入、 ・中段のリストボックスで抽選したいスプレットシート名を決定 ・下段のボタンを入力すると、下部のテキストボックスにて抽出結果が表示される 他にも追加したい機能があったが、最低限にまとめると上記の内容となった。 プログラム部分を作る前に・・・ Pythonでスプレットシート使うためには準備が必要 Google Drive API というのを有効にする等あるが、 詳細は参考サイトを御覧ください。 ソースコードについては、手探りの部分が多く 整理されていないところもある。 ご了承ください。 今回は一部のコードを載せている。 プログラムについて ライブラリインポート #Random_Form.py from tkinter import * from tkinter import ttk import Qdeeplearn as ql tkinter関係のライブラリに加え、 自作したライブラリをimportしている。 自作ライブラリは、元々作成している 「スプレットシートを読み込み、抽選する」プログラムである。 名残として機械学習関連の名前となっている。 メイン関数 #メイン関数 if __name__ == '__main__': #自作ライブラリ スプレットシート読み込みクラスを起動 a=ql.get_sheet_titles() #Tkinterライブラリにて画面を表示 win = Tk() app=Main_Application(master=win) app.mainloop() 処理としては、 スプレットシート関係のクラスを読み込み、 画面を表示しているだけである。 Main_Applicationクラス class Main_Application(ttk.Frame): def __init__(self,master): super().__init__(master) self.pack() master.minsize(width=480,height=360) master.title("抽選アプリ") self.get_texts="" self.button_list_flag=False self.widget() 画面を表示するクラスである。 self.widget() 以降にテキストボックス等のオブジェクトを配置する。 動作部分 def buttonClick(self,combox_data): len_value=self.list_entry_num.get() self.result_txt.delete("1.0", "end") if len_value == "": self.button_list_flag=False elif (len_value.isdecimal()==True): if combox_data=="": self.button_list_flag=False self.result_txt.insert("end","リストを選んでください") elif combox_data!="": self.button_list_flag=True sl=self.get_name_data(combox_data,int(len_value)) self.button_list_flag=True for i in sl: self.result_txt.insert("end",i + "\n" ) else: self.button_list_flag=False self.result_txt.insert("end","半角数字を入力してください") ボタンをクリックした後の処理を表す。 def get_name_data(self,sheets_list,cardunits): wkst = ql.load_Spreadsheets(sheets_list) cardlist=[] reslist=[] nlist=[] rangse=wkst.get_all_values() nlist=ql.organize(rangse,"名前") cardlist=ql.data_processing(nlist) reslist=ql.roulette_getlist_few(cardlist,cardunits) return reslist こちらの関数で、スプレットシートの結果をテキストボックスに伝える。 以上です。 見てくださってありがとうございました。 参考サイト Python入門 (6) - TkinterによるGUIの作成 Google Spread Sheets に Pythonを用いてアクセスしてみた
- 投稿日:2022-01-25T19:50:27+09:00
自分のためだけの新着お笑いライブ通知Botを作りました
つくったもの 推し芸人info(仮名) お気に入りの芸人を登録すると、その芸人さんの新着ライブを毎日LINEで教えてくれます。 前々からこういうのあったらいいなと思っていたものの、各サイトがなかなか機能追加してくれないので自分で作りました。 プロダクト名は推し芸人info(仮名)です。 infoというのは芸人のファンが有志で運営しているTwitterアカウントの通称で、その芸人のライブ・メディア出演情報をまとめてくれるものです。 たとえばマヂカルラブリーinfoさんは非常に熱量が高く、infoさんご自身にファンがついているほどです。 今は完全に自分用なので、需要があればWebサービス化しようかなと思ってます。Web上でお気に入り芸人を登録するとLINEで通知される、みたいな。 なぜ作ったか モチベーションはとても単純で、好きな芸人さんのライブ情報を低コストで漏らさずチェックしたいからです。 もともとお笑いが好きなのと、在宅ワークで時間に余裕ができたので、1,2週間に1回ほどのペースで配信ライブを観るようになりました。吉本興業など各社も昨年ごろから配信ライブに力を入れており、ライブ数もかなり増えています。 一方で、自分の好きな芸人の出演ライブを漏らさず見つける手段は「定期的にサイトで検索する」「Twitterを見張る」くらいしかなく、よく見るサイトに通知機能もありません。人力でのサイト巡回はコストが高いので、機械にやってもらおうと思って作りました。 システム構成 ざっくり以下の流れで動かしています。 Scrapyで各チケット販売サイトをクロールしてS3に保存 別のバッチでライブ情報を読み込んで新着ライブを検知 LINEのMessaging APIで新着ライブ情報を毎朝個人アカウントに送信 Scrapyと通知バッチはAmazon LightsailというAWSのVPSで動かしています。 現在対応しているチケット販売サイトは、配信ライブ大手のFANY Online TicketとK-PROの2つだけですが、まだまだ網羅できていないので今後追加していくつもりです。ワラリー!というサイトもありますが、お笑いライブ版Wikipediaのようなもので公式の情報ではないので、今回はスルーしました。 クローリング・通知部分の処理概要 ここからは各機能の細々としたことを書きます。 Scrapyで各サイトをクローリング Scrapyでプロジェクトを作り、その中でサイトごとにSpiderを実装 Spiderを毎朝5時ごろに動かして、その時点で掲載されているライブ情報全件をS3に保存 件数はFANY・K-PROともに毎日数百件ほど Scrapyの基本的な使い方はいろんな書籍や記事があるので割愛します。この本を読めば大体わかると思います。 運用する中で「クロール結果を通知してほしい(ただし各Spider内に同じ処理を書きたくない)」という場面があり、それに関しては別で書いています。 新着ライブを検知してLINE送信 毎朝9時に以下の処理を動かしています。 PandasでS3から昨日と今日のライブ情報を読み込む 昨日と今日を比較して、お気に入りの芸人が出演している新着ライブを抽出 お気に入りの芸人はtxtファイルで管理 新着ライブのDataFrameからMessaging APIで送る用のJSONを作成 個人のLINEアカウントに新着ライブ情報を送信 API用のLINEアカウント作成やAPIの使い方については、公式のリファレンスが充実しているのでそちらを読むとよいと思います。「こういうJSONを書くとこういうメッセージになる」というシミュレーターも用意されていて、テンプレートも充実しているので非常に助かりました。 メッセージの見た目は悩みましたが、10件ライブがあったときに10回メッセージを送りたくはなかったので、下のように横に情報をスクロールできるFlex Messageのcarouselというタイプにしました。 ちなみに、Pythonでは下のコードを書くと上のメッセージが送られます(LINEビジネスアカウントのトークン情報が必要です)。 from linebot import LineBotApi from linebot.exceptions import LineBotApiError from linebot.models import FlexSendMessage, TextSendMessage line_bot_api = LineBotApi("CHANNEL_ACCESS_TOKEN") message_json = { "type": "carousel", "contents": [ { "type": "bubble", "hero": { "type": "image", "url": "https://cdn.shopify.com/s/files/1/0396/3151/9898/products/0101_aa0b6f5c-6e78-44ed-a21c-c8de8702a874_1024x1024@2x.jpg?v=1639629433", "size": "full", "aspectMode": "cover", "action": { "type": "uri", "label": "action", "uri": "https://online-ticket.yoshimoto.co.jp/collections/selling-well/products/madicalnoyose220101" }, "aspectRatio": "16:9" }, "body": { "type": "box", "layout": "vertical", "spacing": "xs", "contents": [ { "type": "text", "text": "マヂカルラブリーno寄席", "wrap": true, "weight": "bold", "size": "lg" }, { "type": "text", "text": "■出演者", "size": "sm" }, { "type": "text", "text": "マヂカルラブリー、ゴー☆ジャス、ザ・ギース、モダンタイムス、ランジャタイ、永野、脳みそ夫", "size": "xs", "wrap": true }, { "type": "text", "text": "■日時", "size": "sm" }, { "type": "text", "text": "1/1(土)配信開始21:30 配信終了22:30 \n※見逃し視聴は1/8(土)21:30まで \n※チケットの販売は見逃し視聴終了日の昼12:00まで", "wrap": true, "size": "xs" } ], "alignItems": "center", "justifyContent": "space-between" }, "footer": { "type": "box", "layout": "vertical", "spacing": "sm", "contents": [ { "type": "button", "style": "link", "action": { "type": "uri", "label": "詳細を見る", "uri": "https://online-ticket.yoshimoto.co.jp/collections/selling-well/products/madicalnoyose220101" }, "height": "sm" } ], "offsetTop": "-10px", "paddingAll": "none" } }, { "type": "bubble", "hero": { "type": "image", "url": "https://cdn.shopify.com/s/files/1/0396/3151/9898/products/22.1.18__10___3_1024x1024@2x.jpg?v=1642140188", "size": "full", "aspectMode": "cover", "action": { "type": "uri", "label": "action", "uri": "https://online-ticket.yoshimoto.co.jp/products/%E7%B2%97%E5%93%81%E3%81%AB%E5%8B%9D%E3%81%A3%E3%81%9F%E3%82%89%EF%BC%91%EF%BC%90%E4%B8%87%E5%86%86-1-18-19-00?mls_content=nana" }, "aspectRatio": "16:9" }, "body": { "type": "box", "layout": "vertical", "spacing": "xs", "contents": [ { "type": "text", "text": "粗品に勝ったら10万円", "wrap": true, "weight": "bold", "size": "lg" }, { "type": "text", "text": "■出演者", "size": "sm" }, { "type": "text", "text": "霜降り明星 粗品、オダウエダ、軟水、10億円、真空ジェシカ(人力舎)...", "size": "xs", "wrap": true }, { "type": "text", "text": "■日時", "size": "sm" }, { "type": "text", "text": "1/18(火)配信開始19:00 配信終了21:00\n※見逃し視聴は1/25(火)19:00まで\n※チケットの販売は見逃し視聴終了日の昼12:00まで", "wrap": true, "size": "xs" } ], "alignItems": "center", "justifyContent": "space-between" }, "footer": { "type": "box", "layout": "vertical", "spacing": "sm", "contents": [ { "type": "button", "style": "link", "action": { "type": "uri", "label": "詳細を見る", "uri": "https://online-ticket.yoshimoto.co.jp/products/%E7%B2%97%E5%93%81%E3%81%AB%E5%8B%9D%E3%81%A3%E3%81%9F%E3%82%89%EF%BC%91%EF%BC%90%E4%B8%87%E5%86%86-1-18-19-00?mls_content=nana" }, "height": "sm" } ], "offsetTop": "-10px", "paddingAll": "none" } } ] } line_bot_api.push_message(to="USER_ID", messages=FlexSendMessage(alt_text="新着ライブがあります!", contents=message_json)) 感想 自分が欲しかったものをほぼイメージ通りに作ることができたので満足です。 冒頭にも書いたように、今は1人用の構成なのでBotとして公開するのは難しいですが、需要があればWebサービス化しようかなと思ってます。
- 投稿日:2022-01-25T19:14:38+09:00
浮動小数点にマッチする正規表現
修正履歴 - 間違いを修正。@StrawBerryMoonさんありがとうございます。 - Non-capturing groupについて追加。 やり方 Pythonを使った例です。 整数、浮動小数点('10e5'などのexponentを使った表現含む)にマッチ。 import re regex = r'[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?' text = '22 10.08 .52667 2.96996e-05 .57E+05' m = re.findall(rf'({regex})', text) print([t[0] for t in m]) #=> ['22', '10.08', '.52667', '2.96996e-05', '.57E+05'] 正規表現を表す文字列regex内部で()を使ったグルーピングがされているので、 regex全体をさらに()で囲ってグルーピングし取り出します。 例の中の.57E+05という表記が実際に使われるのかは分かりませんが。。 Non-capturing group Pythonにはキャプチャーせずにグルーピングする方法があるようでした。 やり方は(?:foo)のように(の後に?:を付けます。 指数部分のキャプチャーは必要なかったので、上記のコードを書き直すと以下になります。 import re regex = r'[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?' text = '22 10.08 .52667 2.96996e-05 .57E+05' m = re.findall(regex, text) print(m) #=> ['22', '10.08', '.52667', '2.96996e-05', '.57E+05'] 参考 Regular-Expressions.info
- 投稿日:2022-01-25T18:54:56+09:00
plotlyを実行したらAttributeErrorが発生した話
plotlyで作成したグラフをPDF出力とhtml出力させようとしたらハマった話の第二弾です。 第一弾のfig.write_imageを実行するとValueErrorが発生してハマった話はこちら 環境 使用した環境 ・Google Colaboratory 使用言語 ・Python 実現したいこと Plotlyで動的なグラフを作成し、作成したグラフを出力する。 AttributeError発生サンプルコード !pip install japanize-matplotlib %matplotlib inline import pandas as pd import matplotlib.pyplot as plt from matplotlib import cm import japanize_matplotlib import plotly import plotly.graph_objs as go # plotlyを使用するための準備 import plotly.io as pio from plotly.subplots import make_subplots pio.renderers.default = "colab" plt.rcParams['font.family'] plt.rcParams['font.family'] = 'IPAexGothic' !pip install plotly # kaleidoを使用するための記述 !pip install -U kaleido !pip install --upgrade plotly !pip install fpdf # 「IPA」フォントをインストール !apt-get -y install fonts-ipafont-gothic # matplotlibのキャッシュをクリア !rm /root/.cache/matplotlib/fontlist-v300.json # plotlyの簡単なグラフを作成 trace = go.Scatter( x = [1,2,3,4,5], y = [10,20,30,20,10], mode = 'markers + lines', text=["A","B","C","D","E"], marker= dict(size= 14, line= dict(width=1), color= "red", opacity= 0.3 ), ) data = [trace] go.Figure(data).show() trace1 = go.Scatter( x = [1,2,3,4,5], y = [10,20,30,20,10], mode = 'markers + lines', ) trace2 = go.Scatter( x = [1,2,3,4,5], y = [20,20,20,20,20], mode = 'markers + lines', ) data = [trace1, trace2] layout = go.Layout( title ="2つのトレース", ) fig = go.Figure(data, layout) fig.show() fig.write_image("該当ファイルを置きたいパス/test.pdf") 上記のコードを実行するとAttributeErrorが発生しました。 エラー内容 AttributeError Traceback (most recent call last) in () 7 line= dict(width=1), 8 color= "red", ----> 9 opacity= 0.3 10 ), 11 ) 1 frames /usr/local/lib/python3.7/dist-packages/plotly/graph_objs/init.py in init(self, arg, cliponaxis, connectgaps, customdata, customdatasrc, dx, dy, error_x, error_y, fill, fillcolor, groupnorm, hoverinfo, hoverinfosrc, hoverlabel, hoveron, hovertemplate, hovertemplatesrc, hovertext, hovertextsrc, ids, idssrc, legendgroup, line, marker, meta, metasrc, mode, name, opacity, orientation, r, rsrc, selected, selectedpoints, showlegend, stackgaps, stackgroup, stream, t, text, textfont, textposition, textpositionsrc, textsrc, texttemplate, texttemplatesrc, tsrc, uid, uirevision, unselected, visible, x, x0, xaxis, xcalendar, xsrc, y, y0, yaxis, ycalendar, ysrc, **kwargs) /usr/local/lib/python3.7/dist-packages/plotly_utils/importers.py in __getattr(import_name) 39 raise AttributeError( 40 "module {name!r} has no attribute {name!r}".format( ---> 41 name=import_name, __name_=parent_name 42 ) 43 ) AttributeError: module 'plotly.validators.scatter' has no attribute 'ErrorXValidator' やってみたこと ・こちらのサイトをもとに以下コマンドをグラフ作成前に追記 pip install chart-studio import chart_studio.plotly as py →「再起動してすべてのセルを実行」を実行すると通るが、「ランタイムを出荷設定時にリセット」してから「すべてのセルを実行」すると同様のエラーが発生してしまう。 ・重複したインストール、アップグレードを実行するとうまく動かない場合がある旨の記載をGutHubにて発見 →重複したインストール、アップグレードの記述を削除してから動作確認するもうまく動かない。。。(Kaleidoを使用するよう指定している記述の箇所でエラー発生) →そこでplotlyのソースがあるGitHub内にヒントが無いか探したところ、Kaleidoを標準機能として提供し、自動で使用されるのはplotlyの新しいバージョンからという記載を発見したので追記したところエラー解消! 解決方法 以下コマンドを記述してplotlyをKaleidoを標準機能として提供しているバージョンを指定してインストールする。 !pip install plotly==5.1.0 !pip install -U kaleido AttributeErrorを解消した後のコードの全文はこちらになります。 分かったこと !pip install --upgrade plotly を実行してもKaleidoを標準機能として提供してfig.write実行時に自動的にKaleidoが使用されるバージョンにならず、その場合は再起動しないといけなくなるので出荷設定時にリセットしてすべてのセルの実行だけだとうまく動かないということが分かりました。 参考サイト Using Plotly latest version and getting error : module 'plotly.validators.layout' has no attribute 'ExtendtreemapcolorsValidator' https://github.com/plotly/plotly.py/issues/1789 https://github.com/plotly/plotly.py/issues/3298
- 投稿日:2022-01-25T18:54:56+09:00
plotlyを実行したらAttributeErrorが発生してハマった話
plotlyで作成したグラフをPDF出力とhtml出力させようとしたらハマった話の第二弾です。 第一弾のfig.write_imageを実行するとValueErrorが発生してハマった話はこちら 環境 使用した環境 ・Google Colaboratory 使用言語 ・Python 実現したいこと Plotlyで動的なグラフを作成し、作成したグラフを出力する。 AttributeError発生サンプルコード !pip install japanize-matplotlib %matplotlib inline import pandas as pd import matplotlib.pyplot as plt from matplotlib import cm import japanize_matplotlib import plotly import plotly.graph_objs as go # plotlyを使用するための準備 import plotly.io as pio from plotly.subplots import make_subplots pio.renderers.default = "colab" plt.rcParams['font.family'] plt.rcParams['font.family'] = 'IPAexGothic' !pip install plotly # kaleidoを使用するための記述 !pip install -U kaleido !pip install --upgrade plotly !pip install fpdf # 「IPA」フォントをインストール !apt-get -y install fonts-ipafont-gothic # matplotlibのキャッシュをクリア !rm /root/.cache/matplotlib/fontlist-v300.json # plotlyの簡単なグラフを作成 trace = go.Scatter( x = [1,2,3,4,5], y = [10,20,30,20,10], mode = 'markers + lines', text=["A","B","C","D","E"], marker= dict(size= 14, line= dict(width=1), color= "red", opacity= 0.3 ), ) data = [trace] go.Figure(data).show() trace1 = go.Scatter( x = [1,2,3,4,5], y = [10,20,30,20,10], mode = 'markers + lines', ) trace2 = go.Scatter( x = [1,2,3,4,5], y = [20,20,20,20,20], mode = 'markers + lines', ) data = [trace1, trace2] layout = go.Layout( title ="2つのトレース", ) fig = go.Figure(data, layout) fig.show() fig.write_image("該当ファイルを置きたいパス/test.pdf") 上記のコードを実行するとAttributeErrorが発生しました。 エラー内容 AttributeError Traceback (most recent call last) in () 7 line= dict(width=1), 8 color= "red", ----> 9 opacity= 0.3 10 ), 11 ) 1 frames /usr/local/lib/python3.7/dist-packages/plotly/graph_objs/init.py in init(self, arg, cliponaxis, connectgaps, customdata, customdatasrc, dx, dy, error_x, error_y, fill, fillcolor, groupnorm, hoverinfo, hoverinfosrc, hoverlabel, hoveron, hovertemplate, hovertemplatesrc, hovertext, hovertextsrc, ids, idssrc, legendgroup, line, marker, meta, metasrc, mode, name, opacity, orientation, r, rsrc, selected, selectedpoints, showlegend, stackgaps, stackgroup, stream, t, text, textfont, textposition, textpositionsrc, textsrc, texttemplate, texttemplatesrc, tsrc, uid, uirevision, unselected, visible, x, x0, xaxis, xcalendar, xsrc, y, y0, yaxis, ycalendar, ysrc, **kwargs) /usr/local/lib/python3.7/dist-packages/plotly_utils/importers.py in __getattr(import_name) 39 raise AttributeError( 40 "module {name!r} has no attribute {name!r}".format( ---> 41 name=import_name, __name_=parent_name 42 ) 43 ) AttributeError: module 'plotly.validators.scatter' has no attribute 'ErrorXValidator' やってみたこと ・こちらのサイトをもとに以下コマンドをグラフ作成前に追記 pip install chart-studio import chart_studio.plotly as py →「再起動してすべてのセルを実行」を実行すると通るが、「ランタイムを出荷設定時にリセット」してから「すべてのセルを実行」すると同様のエラーが発生してしまう。 ・重複したインストール、アップグレードを実行するとうまく動かない場合がある旨の記載をGutHubにて発見 →重複したインストール、アップグレードの記述を削除してから動作確認するもうまく動かない。。。(Kaleidoを使用するよう指定している記述の箇所でエラー発生) →そこでplotlyのソースがあるGitHub内にヒントが無いか探したところ、Kaleidoを標準機能として提供し、自動で使用されるのはplotlyの新しいバージョンからという記載を発見したので追記したところエラー解消! 解決方法 以下コマンドを記述してplotlyをKaleidoを標準機能として提供しているバージョンを指定してインストールする。 !pip install plotly==5.1.0 !pip install -U kaleido AttributeErrorを解消した後のコードの全文はこちらになります。 分かったこと !pip install --upgrade plotly を実行してもKaleidoを標準機能として提供してfig.write実行時に自動的にKaleidoが使用されるバージョンにならず、その場合は再起動しないといけなくなるので出荷設定時にリセットしてすべてのセルの実行だけだとうまく動かないということが分かりました。 参考サイト Using Plotly latest version and getting error : module 'plotly.validators.layout' has no attribute 'ExtendtreemapcolorsValidator' https://github.com/plotly/plotly.py/issues/1789 https://github.com/plotly/plotly.py/issues/3298
- 投稿日:2022-01-25T18:52:50+09:00
fig.write_imageを実行するとValueErrorが発生してハマった話
plotlyで作成したグラフをPDF出力とhtml出力させようとしたらハマった話の第一弾です。 環境 使用した環境 ・Google Colaboratory 使用言語 ・Python 実現したいこと Plotlyで動的なグラフを作成し、作成したグラフを出力する。 サンプルコード !pip install japanize-matplotlib %matplotlib inline import pandas as pd import matplotlib.pyplot as plt from matplotlib import cm import japanize_matplotlib import plotly import plotly.graph_objs as go # plotlyを使用するための準備 import plotly.io as pio from plotly.subplots import make_subplots pio.renderers.default = "colab" # orcaのインストール !pip install plotly>=4.7.1 !wget https://github.com/plotly/orca/releases/download/v1.2.1/orca-1.2.1-x86_64.AppImage -O /usr/local/bin/orca !chmod +x /usr/local/bin/orca !apt-get install xvfb libgtk2.0-0 libgconf-2-4 plt.rcParams['font.family'] plt.rcParams['font.family'] = 'IPAexGothic' !pip install fpdf # 「IPA」フォントをインストール !apt-get -y install fonts-ipafont-gothic # matplotlibのキャッシュをクリア !rm /root/.cache/matplotlib/fontlist-v300.json # plotlyの簡単なグラフを作成 trace = go.Scatter( x = [1,2,3,4,5], y = [10,20,30,20,10], mode = 'markers + lines', text=["A","B","C","D","E"], marker= dict(size= 14, line= dict(width=1), color= "red", opacity= 0.3 ), ) data = [trace] go.Figure(data).show() trace1 = go.Scatter( x = [1,2,3,4,5], y = [10,20,30,20,10], mode = 'markers + lines', ) trace2 = go.Scatter( x = [1,2,3,4,5], y = [20,20,20,20,20], mode = 'markers + lines', ) data = [trace1, trace2] layout = go.Layout( title ="2つのトレース", ) fig = go.Figure(data, layout) fig.show() fig.write_image("該当ファイルを置きたいパス/test.pdf") 上記のコードを実行するとValueErrorが発生しました。 エラー内容 ValueError Traceback (most recent call last) in () ----> 1 fig.write_image("該当ファイルを置きたいパス/test.pdf") 4 frames /usr/local/lib/python3.7/dist-packages/plotly/io/_orca.py in validate_executable() 1182 for more info on Xvfb 1183 """ -> 1184 raise ValueError(err_msg) 1185 1186 if not help_result: ValueError: The orca executable is required in order to export figures as static images, but the executable that was found at '/usr/local/bin/orca' does not seem to be a valid plotly orca executable. Please refer to the end of this message for details on what went wrong. If you haven't installed orca yet, you can do so using conda as follows: $ conda install -c plotly plotly-orca ...以下割愛 エラー内容を見てみるとorcaをインストールしていなければするように記載されていますがインストールする記述はしており、実際にコマンドでバージョンの確認もとれています。 やってみたこと ・こちらのサイトをもとに以下コマンドの実行 →変化無し !pip install plotly == 4.7.1 !wget https://github.com/plotly/orca/releases/download/v1.2.1/orca-1.2.1-x86_64.AppImage -O / usr / local / bin / orca !chmod + x / usr / local / bin / orca !apt-get install xvfb libgtk2.0-0 libgconf-2-4 import plotly.graph_objects as go 解決方法 こちらのサイトをもとにorcaではなくkaleidoを使用するようにorcaを使用する記述を削除し以下を追記する !pip install plotly # kaleidoをインストール !pip install -U kaleido !pip install --upgrade plotly →ValueError解消! また、こちらにはorcaではなくkaleidoの使用が推奨されている旨の記載がありました。 しかし、新たにAttributeErrorが表示されてしまいました。。。 続きます。。。 参考サイト https://github.com/plotly/orca/issues/290 https://stackoverflow.com/questions/58473837/plotly-missing-orca https://stackoverflow.com/questions/57262385/saving-or-downloading-plotly-iplot-images-on-google-colaboratory/57272111#57272111 https://github.com/plotly/plotly.py/issues/3298
- 投稿日:2022-01-25T18:37:02+09:00
ABC236 A~D問題 ものすごく丁寧でわかりやすい解説 python 灰色~茶色コーダー向け #AtCoder
ABC236(AtCoder Beginner Contest 236) A~D問題の解説記事です。 灰色~茶色コーダーの方向けに解説しています。 その他のABC解説、動画などは以下です。 A - chukodai 問題文の通り、a文字目とb文字目を入れ替えて出力します。 手順は以下です。 ①入力の受け取り ②一文字ずつリストへ格納 ③a文字目とb文字目を入れ替え ④リストを結合 ⑤答えを出力 ①入力の受け取り S,a,bを受け取ります。 Sは文字列、a,bは整数として受け取ります。 ②一文字ずつリストへ格納 Sを一文字ずつリストへ格納します。 list(文字列) と書くことで一文字ずつリストへ格納されます。 S_listというリストへ格納しましょう。 ③a文字目とb文字目を入れ替え pythonでは0インデックス(先頭が0、次が1、...)となっているので問題文でいうa,b文字目とリストのインデックス番号で言うa,b文字目が1ずれます。 よってa,bを予めマイナス1します。 例:a=1,b=5の場合、入れ替えるのはS_listの0番目とS_listの4番目です。 リストのx番目は リスト[x] と書きます。 S_listのa,b番目の要素を入れ替えます。 x,yを入れ替えるときは以下のように書くと楽です。 x,y=y,x よってS_listのa番目とb番目を入れ替える場合以下のようになります。 S_list[a],S_list[b]=S_list[b],S_list[a] ④リストを結合 入れ替えが出来たらリストを結合します。 リストの結合は以下のように書きます。 "".join(リスト) 少々分かりづらい書き方ですが、「python リスト 結合」などで検索すればすぐに出てくるのでいちいち覚える必要はありません。 ⑤答えを出力 結合したリストを出力すれば終わりです。 入力の受け取り、出力がわからない方は以下の記事を参考にしてください。 【提出】 # ①入力の受け取り S=input() a,b=map(int,input().split()) # ②一文字ずつリストへ格納 S_list=list(S) # 0インデックスのためマイナス1してずらす a-=1 b-=1 # ③a文字目とb文字目を入れ替え S_list[a],S_list[b]=S_list[b],S_list[a] # ④リストを結合 ans="".join(S_list) # ⑤答えを出力 print(ans) B - Who is missing? 1,2,...,Nのカードが何枚ずつあるか確認するリストを用意し、記録します。 Aの要素を一つずつ見ていって対応する要素をカウント(プラス1)していきます。 1,2,...,Nのカードについてカウントした枚数を確認していきます。 3枚しかないものが答えです。 答えを見つけたら出力して終了します。 途中で終了する場合は exit() と書きます。 【提出】 # 入力の受け取り N=int(input()) A=list(map(int,input().split())) # 枚数を数えるリスト count=[0]*(N+1) # i=0~(4N-2) for i in range(4*N-1): # A[i]をプラス一枚カウント count[A[i]]+=1 # i=1~N for i in range(1,N+1): # 枚数が3ならば if count[i]==3: # iが抜かれたカード print(i) # 途中終了 exit() C - Route Map Sの各駅についてTに含まれるか確認すればOKです。 連想配列(defaultdict)を使ってTに含まれる要素を記録し、確認します。 連想配列(defaultdict)を使ったことがない人は以下を参照してください。 defaultdict(連想配列)について 辞書または連想配列と言います。 キー:値 というような対応付を行えるデータ構造です。 連想配列はdict()と書くことでも使えますが、デフォルトの値(初期値)が設定できません。そのため存在チェックなど色々面倒が発生します。 その面倒を避けるためにdefaultdictを使います。 import:from collections import defaultdict 作成(デフォルト0):変数名=defaultdict(int) キー、値の登録:変数名[キー]=値 値の取り出し:変数名[キー] 【使用例】 # インポート from collections import defaultdict # 作成(デフォルト0):変数名=defaultdict(int) dictionary=defaultdict(int) # キー、値の登録:変数名[キー]=値 dictionary[5]=1 # 値の取り出し:変数名[キー] x=dictionary[5] 詳しく知りたい人は以下を参照してください。 Tに含まれる駅は連想配列に「1」を記録しておきます。 Sの各駅について ・連想配列の値が「1」→止まる(Yes) ・連想配列の値が「0」(デフォルトの値)→止まらない(No) となります。 【提出】 # 入力の受け取り N,M=map(int,input().split()) S=list(map(str,input().split())) T=list(map(str,input().split())) # defaultdictをインポート from collections import defaultdict # 急行が止まる駅 # 「1」なら止まる # 「0」(デフォルトの値)なら止まらない stations=defaultdict(int) # i=0~(M-1) for i in range(M): # 「1」を記録 stations[T[i]]=1 # i=0~(N-1) for i in range(N): # S[i]が止まる駅 if stations[S[i]]==1: # 「Yes」を出力 print("Yes") # 止まらない駅 else: # 「No」を出力 print("No") D - Dance まず2N人から2人組を作る組み合わせ数がいくつあるか考えてみましょう。 Nが最大の8のとき、2N=16です。 この16人に対してAというカードを2枚、Bというカードを2枚、...、Hというカードを2枚配ることを考えます。 同じカードを持っている人がペアです。(例えば1番と5番がAを持っていたら二人はペアです) 配り方はAABB...HHの順列の数に一致するので16!/(2!2!...2!)=16!/2^8となります。 さらにカードは区別する必要がないのでA,B,...,Hの順列分、すなわち8!で割ります。 結果として2人組を作る組み合わせ数は最大で (16!/(2^8))*(1/8!)=2027025≒2*10^6 となります。全探索はpythonでは無理そうですがpypyならぎりぎり間に合いそうかなーくらいですね。 次にどうやって全てのペアを列挙するか考えます。 単純にitertoolsで1~2Nの順列を作って(0番目と1番目),(2番目と3番目),...,((2N-2)番目と(2N-1))をペアとしたいところですが、ペアの重複が発生するためうまくいきません。 そこで以下のように考えます。 ①pairsというリストを作る(最終的にpairsの(0番目と1番目),(2番目と3番目),...,((2N-2)番目と(2N-1))をペアとする) ②・pairsの偶数番目の人を追加する時(pairs[0],pairs[2],...にあたる人) 残った人の中で一番番号の小さい人を追加する ・pairsに奇数番目の人を追加する時(pairs[1],pairs[3],...にあたる人) 残った人の誰かを選んで追加する(総当りで試す) ③pairsの人数が2Nになったら「楽しさ」を計算 実装には再起関数とDFS(深さ優先探索)を使います。 DFSはグラフを探索するアルゴリズムですが、本問のように総当りしたいときに応用がききます。 まず「相性」は入力のままだと使い勝手が悪いので表形式にしましょう。 以下のように受け取りします。 # 入力の受け取り N=int(input()) # 「相性」の表 A=[[0]*(2*N+1) for i in range(2*N+1)] # i=1~(2N-1) for i in range(1,2*N): # 入力を受け取る tmp=list(map(int,input().split())) # 表に記載 for j in range(len(tmp)): A[i][j+(i+1)]=tmp[j] A[j+(i+1)][i]=tmp[j] 例えば入力例1の場合だとAは以下のようになります。 例えば1番と4番の相性はA[1][4]=1と確認できます。 続いて2つのリストを作ります。 ・selected:すでに選ばれているか(pairsの中に入っているか)管理するリスト selected[x]=Falseならxは選ばれていない、Trueならすでに選ばれている ・pairs:ペアになる人を記録するリスト(最初は空) 最終的にpairsの(0番目と1番目),(2番目と3番目),...,((2N-2)番目と(2N-1))をペアとする ここまでできたらDFSで全ての組を作って確認します。 再帰関数を使って実装します。 まず関数DFSを作ります。 引数はselectedとpairsです。 「関数DFSの処理」 ・次にpairsへ追加する人が偶数番目⇔pairsの長さが偶数 selectedがFalseの中で一番番号の小さい人をpairsに追加 ・次にpairsへ追加する人が奇数番目⇔pairsの長さが奇数 selectedがFalseの人を順番にpairsへ追加 どちらの操作を行った場合でもpairs,selectedを更新し、次のDFSを始めます。 ・pairsの長さが2N 各ペアの「相性」から「楽しさ」を計算 そこまでに計算した「楽しさ」より大きければ答えを更新 処理が終わったら答えを出力します。 Pythonでは間に合わないのでpypyで提出します。 再帰関数をあまり使ったことがない人は挙動がイメージしづらいと思います。 本問についてではないですが、DFS、再帰関数について解説した動画がありますのでまずそちらをご覧ください。 その後【提出】の内容をコピペしてコードエディタのステップ実行機能を使い、どのような挙動をするか確認しましょう。 だいたい流れがわかったら今度は自力で書いてみましょう。 本問が難しければDFSの練習としてより簡単なABC213Dを先にやってみるのが良いです。 再帰関数ははじめはよくわからない、書けないという人が多いと思います。 ですがこれを使えると他の様々な問題に応用がきき、レーティングも上がっていくので頑張って自分のものにしましょう。 【提出】 # pypyで提出 # 再起回数上限を10^6へ変更 import sys sys.setrecursionlimit(10**6) # 入力の受け取り N=int(input()) # 「相性」の表 A=[[0]*(2*N+1) for i in range(2*N+1)] # i=1~(2N-1) for i in range(1,2*N): # 入力を受け取る tmp=list(map(int,input().split())) # 表に記載 for j in range(len(tmp)): A[i][j+(i+1)]=tmp[j] A[j+(i+1)][i]=tmp[j] # 答え ans=0 # 引数:selected(すでに選ばれている人) pairs(ペアの一覧) def DFS(selected,pairs): # ansを更新できるように global ans # 全員ペアができていれば # ⇔pairsの長さが2Nならば if len(pairs)==2*N: # 「楽しさ」の計算 score=0 # i=0~(2N-1)まで 2毎に増加 for i in range(0,2*N,2): # ペアになる人 x=pairs[i] y=pairs[i+1] # 「相性」から「楽しさ」を計算 score^=A[x][y] # 「楽しさ」が大きかったら答えを更新 ans=max(ans,score) # 次に追加する人が偶数番目 # ⇔pairsの長さが偶数 elif len(pairs)%2==0: # 選ばれていない中で一番小さい番号の人を選ぶ i=1 while selected[i]==True: i+=1 # pairsへ追加 pairs.append(i) # 選択済みにする selected[i]=True # 次のDFSへ DFS(selected,pairs) # 前のDFSが終わったらpairsから消す pairs.pop() # selectedをFalseへ変更 selected[i]=False # 次に追加する人が奇数番目 # ⇔pairsの長さが奇数 else: # 選ばれていない人を一人ずつ全て選ぶ for i in range(1,2*N+1): # まだ選ばれていないなら if selected[i]==False: # 追加 pairs.append(i) # 選択済みにする selected[i]=True # 次のDFSへ DFS(selected,pairs) # 前のDFSが終わったらpairsから消す pairs.pop() # selectedをFalseへ変更 selected[i]=False # すでに選ばれているか(pairsの中に入っているか)管理するリスト # =Falseなら選ばれていない、Trueならすでに選ばれている # 最初は誰も選ばれていない selected=[False]*(2*N+1) # ペアになる人を記録するリスト(最初は空) # 最終的にpairsの(0番目と1番目),(2番目と3番目),...,((2N-2)番目と(2N-1))をペアとする pairs=[] # DFSを開始 DFS(selected,pairs) # 答えの出力 print(ans) 【広告】 「AtCoder 凡人が『緑』になるための精選50問詳細解説」 AtCoderで緑になるための典型50問をひくほど丁寧に解説した本(kindle)、pdf(booth)を販売しています。 値段:100円(Kindle Unlimited対象) 【kindle】 【booth(pdf)】 1~24問目まではサンプルとして無料公開しています
- 投稿日:2022-01-25T17:03:45+09:00
Pythonスクレイピング【GCP - cloud functions - サンプルコード付】
はじめに 普段は、firebase cloud functionsで サーバー側の処理を記述しています。 今回も、node環境のサーバーでスクレイピングを行なっていたのですが 処理に時間がかかる・メモリが多く必要になるという理由で スクレイピングに関しては、python3で記述することにしました。 ちなみに、nodeでのスクレイピングは下記のnpmを使用していました。 npm i puppeteer 下記システムは、こちらのサイトでも使用しています。 Pythonの記述・ディレクトリ構造 今回は、GCPのcloud functionsの方に pythonのファイルをデプロイしたので、ディレクトリ構造の説明もしていきたいと思います。 ディレクトリ構造 ディレクトリ構造 pythonFunctions ├ main.py ├ getMetaFromSoup.py ├ requirements.txt ├ .gcloudignore └ README.md main.py[サンプルコード] main.pyは サーバー側で一番はじめに処理が実行されるファイルになります。 関数【getMeta()】では、URLからHTMLファイルを読み込んでいます。 main.py # -*- coding: utf-8 -*- import requests import bs4 from flask import jsonify # import from getMetaFromSoup import getTitle, getDescription, getImage, getDomain def getMeta(request): # クエリ文字列を取得 if request.args and 'url' in request.args: request_name = request.args.get('url') url = request_name # URL html = requests.get(url) soup = bs4.BeautifulSoup(html.content, "html.parser") params = { "title": getTitle(soup), "description": getDescription(soup), "image": getImage(soup), "domain": getDomain(url), } # 必要に応じて変更を行なってください。 headers = { 'Access-Control-Allow-Origin': 'http://yapoyapo.com/' } return (jsonify(params), 200, headers) getMetaFromSoup.py[サンプルコード] 実際にスクレイピングを行なっているファイルになります。 ※入れた方がいいmeta情報があればご共有お願いします! getMetaFromSoup.py from urllib.parse import urlparse # タイトルを取得 def getTitle(soup): title = soup.find('meta', attrs={'property': 'og:title'}) if (str(title) != 'None'): return title.get('content') title = soup.find('title') if (str(title) != 'None'): return title.text title = soup.find('meta', attrs={'name': 'twitter:title'}) if (str(title) != 'None'): return title.get('content') title = soup.find('h1') if (str(title) != 'None'): return title.text return '' # ディスクリプションを取得 def getDescription(soup): description = soup.find('meta', attrs={'property': 'og:description'}) if (str(description) != 'None'): return description.get('content') description = soup.find('description') if (str(description) != 'None'): return description.text description = soup.find('meta', attrs={'name': 'twitter:description'}) if (str(description) != 'None'): return description.get('content') description = soup.find('meta', attrs={'name': 'description'}) if (str(description) != 'None'): return description.get('content') # 画像を取得 def getImage(soup): image = soup.find('meta', attrs={'property': 'og:image'}) if (str(image) != 'None'): return image.get('content') image = soup.find('link', attrs={'rel': 'image_src'}) if (str(image) != 'None'): return image.get('href') image = soup.find('meta', attrs={'name': 'twitter:image'}) if (str(image) != 'None'): return image.get('content') image = soup.find('meta', attrs={'name': 'twitter:image:src'}) if (str(image) != 'None'): return image.get('content') image = soup.find('img') if (str(image) != 'None'): return image.get('href') # ドメインを取得 def getDomain(url): return urlparse(url).netloc requirements.txt[サンプルコード] requirements.txtでは、pipのバージョン管理を行なっています。 ローカルPCのpipのバージョンはpip listで確認できます。 requirements.txt urllib3==1.26.4 beautifulsoup4==4.10.0 requests==2.25.1 .gcloudignore[サンプルコード] .gcloudignoreでは サーバー側に必要のない情報を記載しています。 .gitignoreのような役割を担っています。 .gcloudignore README.md 最後に 重要なファイルは ・main.py ・requirements.txt になります。 また、本記事で紹介した方法で こちらのサイトを運営していますので、ぜひ使ってみてください!
- 投稿日:2022-01-25T17:01:37+09:00
【Python】enumとは
enumとは enumは、列挙型と呼ばれる複数の定数を1クラスにまとめて保持できるもの。 定義方法 enumは、標準ライブラリのenumモジュールのEnumクラスを継承させることで使用できる。 定義方法のサンプルが以下となる。 from enum import Enum class Color(Enum): #<name> = <value> RED = 0 GREEN = 1 BULE = 2 変数名がname、値がvalueとなる。enumでは、同じ名前のメンバを複数持つことができないため、下記のように定義するとエラーになる。 class Color(Enum): # <name> = <value> RED = 0 RED = 1 # TypeError: Attempted to reuse key: 'RED' 一方、同じvalueを持つメンバは複数持つことができる。もし、重複を避けたい場合は@uniqueを使うことで重複時エラーが発生する。 値の使い方 列挙型における値の扱い方は以下のようになる。 また、enumはfor文を使って値を取得することができる。 # 値の取得 print(Color.RED) # Color.RED print(Color.RED.name) # RED print(Color.RED.value) # 0 print(Color(0)) # Color.RED # for文の場合 for in Color: print(i) # Color.RED # Color.GREEN # Color.BULE
- 投稿日:2022-01-25T16:36:56+09:00
python async 実行記録
import asyncio import time async def say_after(delay, what): print(f"{time.strftime('%X')} :{what}") await asyncio.sleep(delay) print(f"{time.strftime('%X')} :{what}") async def main(): tasks = [say_after(3, 'hello'), say_after(2, 'yeeeei'), say_after(1, 'ueeeeei')] await asyncio.gather(*tasks) asyncio.run(main()) 16:38:20 :hello 16:38:20 :yeeeei 16:38:20 :ueeeeei 16:38:21 :ueeeeei 16:38:22 :yeeeei 16:38:23 :hello
- 投稿日:2022-01-25T16:17:19+09:00
google cloud natural Language APIを使用してテキストから感情を出力
ちょっと触ってみたいAPIがあったので触ってみました。 テキストから感情を読み取ってくれるAPIです。 https://cloud.google.com/natural-language 実際にやってみよう まずは以下のリンクから無料トライアルに登録(登録済みの人はスルーで) ※ 5000回解析するまではお金はかからないみたいです。 https://console.cloud.google.com/getting-started APIトサービス->ダッシュボード 新規プロジェクトの作成 名前はなんでもいいと思います。 自分は「20220125-nlp-api」としました。 下にスクロールすると 「google cloud natural Language API」が出てくると思うので こちらを選択してください こちらのAPIキーをコピーしておいてください。 これで設定の方は大体終わりです。 コード keyの値に先ほどコピーしたAPIキーを入れてください。 (隠している為、......としています。) import requests def g_nlp(text): key = 'AIzaSyBaL6o85mB..................' url = f'https://language.googleapis.com/v1/documents:analyzeSentiment?key={key}' header = {'Content-Type': 'application/json'} body = { "document": { "type": "PLAIN_TEXT", "language": "JA", "content": text } } res = requests.post(url, headers=header, json=body) result = res.json() return result text = '席が隣の先輩が怒られていた。どんまい(笑)' json = g_nlp(text) value = json['documentSentiment']['score'] if 0<= value <= 0.3: print('まぁまぁ') elif 0.4<= value <= 0.6: print('良い') elif 0.7<= value <= 0.9: print('超絶良い') elif -0.3<= value <= -0.1: print('ばっと') elif -0.6<= value <= -0.4: print('くそばっと') elif -0.9<= value <= -0.7: print('おにくそばっと') 自分はgoogle colabで実装しました。 以上でーす!
- 投稿日:2022-01-25T16:13:04+09:00
pythonでdatetimeを使って時刻をYYYYMMDDHHHMMSSで出力する
pythonでdatetimeを使って時刻をYYYYMMDDHHHMMSSで出力する test.py import datetime t_delta = datetime.timedelta(hours=9) JST = datetime.timezone(t_delta, 'JST') now = datetime.datetime.now(JST) d = now.strftime('%Y%m%d%H%M%S') print(d) $ python test.py 20220125161020 参考
- 投稿日:2022-01-25T15:57:24+09:00
pythonでディレクトリが存在しているかどうかをチェックする
pythonでディレクトリが存在しているかどうかをチェックするにはos.path.exists()を使う、 ディレクトリを作成するにはos.makedirs()を使う test.py import os checkdir = './testdir' if os.path.exists(checkdir) == False: print("{} not exists".format(checkdir)) print("{} create path".format(checkdir)) os.makedirs(checkdir) else: print("{} exists".format(checkdir)) 実行する $ python test.py ./testdir not exists ./testdir create path $ python test.py ./testdir exists $ ls testdir/ 参考
- 投稿日:2022-01-25T15:30:19+09:00
Jupyter RuntimeErrorの対処方法 asyncio.run() cannot be called from a running event loop
Jupyter自身のイベントループ上で新たにイベントループを開始しようとしたために起こったエラー。非同期処理を実行させたければ、await関数で直接実行させれば良い。 Jupyterで非同期処理を実行させる import asyncio import datetime async def display_date(): """現在時刻を5秒間表示する""" loop = asyncio.get_running_loop() end_time = loop.time() + 5.0 while True: print(datetime.datetime.now()) if(loop.time()+1.0)>=end_time: break await asyncio.sleep(1) x : asyncio.run(display_date()) ○ : await display_date() 2022-01-25 15:31:57.799997 2022-01-25 15:31:58.804521 2022-01-25 15:31:59.815091 2022-01-25 15:32:00.834119 2022-01-25 15:32:01.848780 じゃあjupyterのイベントループはどのファイルが実行してるの? 以下コードをnotebook上で実行すると、イベントループを実行していないにもかかわらず、現在イベントループが実行中という事がわかる。 import asyncio asyncio.current_task() notebook上で実行 <Task pending name='Task-2' coro=<Kernel.dispatch_queue() running at C:\Users\yourname\.pyenv\pyenv-win\versions\3.9.6\lib\site-packages\ipykernel\kernelbase.py:461> cb=[IOLoop.add_future.<locals>.<lambda>() at C:\Users\yourname\.pyenv\pyenv-win\versions\3.9.6\lib\site-packages\tornado\ioloop.py:688]> notebookはtornadoというファイルによって実行されているということが分かる。 以下コードをターミナル上から実行するとasyncioによって実行されていることが分かる。 import asyncio async def whats_event(): print(asyncio.current_task()) asyncio.run(whats_event()) ターミナル上で実行 <Task pending name='Task-1' coro=<whats_event() running at c:\Users\yourname\lessons\python_lessons\project\aysnc_test.py:5> cb=[_run_until_complete_cb() at C:\Users\yourname\.pyenv\pyenv-win\versions\3.9.6\lib\asyncio\base_events.py:184]>
- 投稿日:2022-01-25T15:30:03+09:00
【Django】IIS のサーバータイムアウト時間の変更
はじめに Django フレームワークを用いて IIS サーバーへデプロイしました その際に サーバータイムアウト の設定を IISマネージャーからどこを変えてもうんともすんとも効かなかったので、変更箇所を共有したいと思います IIS タイムアウト時間はデフォルトで 110 秒となっています ※DjangoをIISへデプロイしたり環境の用意をする記事ではありません 下記の環境を想定しています - Windows Server 2019 - IIS 10 - Python 3.9.7 - Django 3.2.10 - wfastcgi 3.0.0 - wwwroot 直下に venv にて python 仮想環境フォルダ(env)を作成 もくじ applicationHost.config の変更 applicationHost.config の変更 下記ディレクトリにある applicationHost.config を変更します C:\Windows\System32\inetsrv\Config\applicationHost.config ※任意でバックアップはとっておきましょう ファイル内にあるタグ <fastCgi> を編集します 例では 180 秒に変更しております 【変更前】 <fastCgi> <application fullPath="C:\inetpub\wwwroot\env\Scripts\python.exe" arguments="C:\inetpub\wwwroot\env\lib\site-packages\wfastcgi.py" signalBeforeTerminateSeconds="30" /> </fastCgi> 【変更後】 <fastCgi> <application fullPath="C:\inetpub\wwwroot\env\Scripts\python.exe" arguments="C:\inetpub\wwwroot\env\lib\site-packages\wfastcgi.py" signalBeforeTerminateSeconds="30" activityTimeout="180" /> </fastCgi> activityTimeout="180" を追記しました あとはサーバーを再起動すればタイムアウト時間が 180 秒に変更されます おわりに IIS は基本的に asp.net に合わせた設定が多いらしく、Django との相性は… タイムアウト伸ばすだけでしたが、情報がなかったので共有しておきます
- 投稿日:2022-01-25T15:07:24+09:00
Apache Airflow 並列処理 ハンズオン
はじめに Apache Airflow ハンズオンに続いて、The Complete Hands-On Introduction to Apache Airflow で理解を深める。本記事では、デフォルトで設定されている SQLite と SequentialExecutor での挙動を確認した後、それらを PostgreSQL と LocalExecutor に変更し、並列処理を試す。 デフォルト設定の確認 Apache Airflow では、初期化した段階でデフォルトの設定がなされている。設定は以下コマンドで確認できる。 $ airflow config get-value core sql_alchemy_conn sqlite:////home/airflow/airflow/airflow.db $ airflow config get-value core executor SequentialExecutor これを見ると、DB には SQLite が採用され、Executor は SequentialExecutor となっていることがわかる。一旦、デフォルトの設定のままでどのような挙動になるかを確認する。 以下で簡単な DAG を作成した。 dags/parallel_dag.py from datetime import datetime from airflow import DAG from airflow.operators.bash import BashOperator default_args = {"start_date": datetime(2020, 1, 1)} with DAG( "parallel_dag", schedule_interval="@daily", default_args=default_args, catchup=False ) as dag: task_1 = BashOperator(task_id="task_1", bash_command="sleep 3") task_2 = BashOperator(task_id="task_2", bash_command="sleep 3") task_3 = BashOperator(task_id="task_3", bash_command="sleep 3") task_4 = BashOperator(task_id="task_4", bash_command="sleep 3") task_1 >> [task_2, task_3] >> task_4 airflow webserver と airflow scheduler を実行し、http://localhost:8080 にアクセス。DAGs の中から parallel_dag を選択し、Graph View を選ぶと以下のような画面となる。 このグラフを見ると、並列処理が実装されていそうだが、画面左上のトグルをオンにして実行した後に Gantt 画面に遷移すると並列処理ではなく、順次実行されていることが確認できる。 これはデフォルトの Executor が Sequantial Executor であることが原因である。これら設定は ~/airflow/airflow.cfg に記述されている。 並列処理を行うための設定 並列処理を行うためには、並列処理に対応する DB と Executor に変更する必要がある。ここでは、DB として PostgreSQL を採用し、Executor を Sequantial Executor から Local Executor に変更する。 $ sudo apt update $ sudo apt install postgresql 接続するために、ユーザ postgres のパスワードを設定。 $ sudo -u postgres psql postgres=# ALTER USER postgres PASSWORD 'postgres'; ALTER ROLE Postgres を扱うためのパッケージをインストール。 $ pip install 'apache-airflow[postgres]' 以下で設定を変更していく。まず DB 設定を以下のように変更した。設定の正確なフォーマットは Database Urls を参照。 ~/airflow/airflow.cfg ... # sql_alchemy_conn = sqlite:////home/airflow/airflow/airflow.dbost:port/database sql_alchemy_conn = postgresql+psycopg2://postgres:postgres@localhost/postgres 変更を反映した後、以下コマンドが問題なく実行されれば、DB を PostgreSQL に設定できている。 $ airflow db check 続いて、Executor も変更する。 ~/airflow/airflow.cfg ... # executor = SequentialExecutor executor = LocalExecutor 設定を変更できたので、Postgres DB を初期化する。airflow webserver と airflow scheduler が立ち上がっている場合は、一旦停止しておく。 $ airflow db init ... $ airflow users create -u admin -p admin -r Admin -f admin -l admin -e admin@airflow.com ... Admin user admin created DB の初期化および新しいユーザの作成を行ったので、airflow webserver と airflow scheduler を実行し、http://localhost:8080 にアクセス。以前のユーザでログインしたままの場合は、一度ログアウトして admin/admin でログインし直す。DAGs の中から parallel_dag を選択し、Graph View を選ぶと以下のような画面となる。 並列処理されていることが確認できる。 おわりに PostgreSQL と LocalExecutor を用いて、並列処理を実行してみた。引き続き、理解を深めていきたい。
- 投稿日:2022-01-25T14:40:31+09:00
AnyMotionを利用して動きの違いを可視化してみた
概要 AIを用いた姿勢推定による動作解析APIサービスであるAnyMotionを用いて、人物の動きの違いを可視化してみました。 AnyMotionとは AnyMotionは、AIを用いた姿勢推定による動作解析APIサービスです。 人物の関節などの座標を推定して、様々な身体動作を可視化/定量化してくれます。 今回やること 動画に映る人物の関節などの座標を描画して、動きの違いを可視化する アクセストークンの取得 AnyMotionポータルで発行された、Client ID とClient Secretを用いて、AnyMotionにアクセスするためのトークンを取得します。 from urllib.parse import urljoin import requests CLIENT_ID = "<your_client_id>" CLIENT_SECRET = "<your_client_secret>" BASE_URL = "https://api.customer.jp/" AUTH_URL = urljoin(BASE_URL, "v1/oauth/accesstokens") def authenticate() -> str: headers = {"Content-Type": "application/json"} body = { "grantType": "client_credentials", "clientId": CLIENT_ID, "clientSecret": CLIENT_SECRET, } res = requests.post(AUTH_URL, json=body, headers=headers) token = res.json()["accessToken"] result = f"Bearer {token}" return result token = authenticate() 姿勢推定 取得したトークンを用いて人物の関節などの座標データを取得します。 今回用いる2つの動画をアップロードします。アップロード完了後、2つの動画それぞれに対して、座標データを取得します。 処理完了後、動画の各フレームごとに検出した17点の座標データを取得できます。 実装 import base64 import hashlib import time from pathlib import Path ANYMOTION_URL = urljoin(BASE_URL, "anymotion/v1/") CURRENT_DIR = Path(__file__).parent MEDIA_DIR = CURRENT_DIR / "media" WAIT_TIME = 1 def calc_content_md5(path: Path) -> str: alg = hashlib.md5() with open(path, "rb") as f: alg.update(f.read()) result = base64.b64encode(alg.digest()).decode() return result def create_movie(token: str, content_md5: str) -> tuple[int, str]: headers = {"Content-Type": "application/json", "Authorization": token} body = {"contentMd5": content_md5} url = urljoin(ANYMOTION_URL, "movies/") res = requests.post(url, json=body, headers=headers) movie_id, upload_url = res.json()["id"], res.json()["uploadUrl"] return movie_id, upload_url def upload(path: Path, upload_url: str, content_md5: str) -> None: with open(path, "rb") as f: headers = {"Content-MD5": content_md5} requests.put(upload_url, data=f, headers=headers) def create_keypoint(token: str, movie_id: int) -> int: headers = {"Content-Type": "application/json", "Authorization": token} body = { "movieId": movie_id, } url = urljoin(ANYMOTION_URL, "keypoints/") res = requests.post(url, json=body, headers=headers) keypoint_id = res.json()["id"] return keypoint_id def wait_for(token: str, resource: str, resource_id: int) -> None: headers = {"Authorization": token} url = urljoin(ANYMOTION_URL, f"{resource}/{resource_id}/") while True: res = requests.get(url, headers=headers) status = res.json()["execStatus"] if status == "SUCCESS": break time.sleep(WAIT_TIME) # もう1つの動画に対しても同様の処理を実施 student_path = MEDIA_DIR / "student.mp4" content_md5 = calc_content_md5(student_path) movie_id, upload_url = create_movie(token, content_md5) upload(student_path, upload_url, content_md5) source_keypoint_id = create_keypoint(token, movie_id) wait_for(token, "keypoints", source_keypoint_id) 2つの姿勢の比較描画 最後に、座標データを動画に描画します。 今回は1つの動画に座標データの描画ともう1つの動画の座標データを描画して、動きの違いを可視化します。 処理完了後、描画処理された動画のダウンロードURLが取得できるので、動画をダウンロードします。 実装 def create_drawing(token: str, source_keypoint_id: int, target_keypoint_id: int) -> int: headers = {"Content-Type": "application/json", "Authorization": token} body = { "keypointId": source_keypoint_id, "rule": [ { "drawingType": "merge", "overlap": { "targetId": target_keypoint_id, "pivot": "leftAnkle", }, } ], } url = urljoin(ANYMOTION_URL, "drawings/") res = requests.post(url, json=body, headers=headers) drawing_id = res.json()["id"] return drawing_id def get_download_url(token: str, drawing_id: int) -> str: headers = {"Authorization": token} url = urljoin(ANYMOTION_URL, f"drawings/{drawing_id}/") res = requests.get(url, headers=headers) download_url = res.json()["drawingUrl"] return download_url def download(download_url: str, filename: str, path: Path = MEDIA_DIR) -> None: res = requests.get(download_url) dest_path = path / filename with open(dest_path, "wb") as f: f.write(res.content) drawing_id = create_drawing(token, source_keypoint_id, target_keypoint_id) wait_for(token, "drawings", drawing_id) download_url = get_download_url(token, drawing_id) download(download_url, "output.mp4") 結果 おわりに 今回は、AnyMotionを用いて動きの違いを可視化してみました。 1つの動画に2人の関節などの座標を描画することで動きの違いが明らかになりました。 参考サイト AnyMotion AnyMotion APIドキュメント
- 投稿日:2022-01-25T14:17:13+09:00
APIドキュメントから生成したFastApiスタブをPysenに怒られないようにするまで
以前はFlaskを使ってゆるい型もないゆるゆるフワフワしたAPIを作って居たものです。 しかしながら、最近は型の概念がいかに素晴らしいかというのをTypeScriptやらGolangやらで体感したので、たとえPythonであっても、しっかりした型でいい感じのコードを書きたくなりました。 開発環境 Poetry+Pysen これが今どきの最新っぽい雰囲気でおすすめされてたので使います openapi-generator-cli 別に何で生成してもいいと思いますが、以前から何度か使っていて慣れているので使う Stoplight 初めからStoplight使って居るので、無いとやってられない、もはや手書きでは書けない 1 ドキュメントを作る Stoplightでガリガリ書きます 2 Python-templeteを使い PysenとPoetry入ったプロジェクトを生成 で作ったあと、git clone してくる。 (非常に助かりましたありがとうございます) 3 openapi-generator-cliで ドキュメントからスタブ生成 cloneしたプロジェクトのsrcフォルダ内でスタブを作る。 npm install -g openapi-generator-cli openapi-generator-cli generate -g python-fastapi -i server.yaml -o api_gen 4 生成したフォルダを調整 個人的にsrcフォルダ内直下に入って欲しい。なのでまずは VSCodeのファイル内検索で、from openapi_server.models を from src.modelsに一括置き換えする。 そして openapi_serverフォルダの中身を全て 1階層上に移動する。 5 テストの返り型を指定 openapi-generator-cliで生成したテストは 返り値型が付いていないので (client: TestClient): を (client: TestClient) -> None: に一括置き換えする 6 paramsの型TupleではなくDictにする そのままでは testsのclient.requestのparamsはtyping.MutableMapping[str, str]、つまりDict型を要求するにも関わらずparamsがTupleとして投げられるため、mypyが怒る。(実際には内部で上手いことやってくれるようだが。) [("localization", "en"), ("page", 1), ("keywords", "test")] を dict([("localization", "en"), ("page", 1), ("keywords", "test"))として一括置き換えして、明示的にDict型にする 7 paramsにValueに型が無いのでStrにする 先程触れたように testsのclient.requestのparamsはtyping.MutableMapping[str, str] であるため paramsを Dict[str, str]にする。そのため、params = を params: Dict[str,str] に置き換えて("page", 1)を("page", "1")に置き換える。これでtestsフォルダのフォーマットは完了。poetry run pysen run formatしてpoetry run pysen run lintして問題なければCommit 8 apisのrouterに付いている Dependsをすべて 外してインポートするようにする。 Pythonの関数のデフォルト引数は、読み込まれたタイミングで一度だけ実装される。この仕様をしらずにあとから関数を実行してトラブルになる可能性があるため、デフォルト引数に関数を与えるとPEPに怒られる。 これを回避するには、共通するDependsを全て 別ファイルに書き出してしまえば良い。 詳細は省くが、いい感じに置き換えを使う。 9 横に長過ぎる説明を改行 OpenAPIドキュメントで書いてあるコメントが丁寧すぎる場合、flake8で指定してある88文字を大幅に超えることがある。その場合は改行を入れる。 10 モデル確認 ちゃんと生成されていればここまでで動くはずだが、上手く行かない場合は、複雑なモデルの生成に失敗している可能性がある。modelsフォルダの中のモデルを眺めてみて、おかしな生成があれば修正する。 11 完了 poetry run pysen run formatして poetry run pysen run lint isort .......... OK (2.01 sec) black .......... OK (3.82 sec) flake8 .......... OK (4.04 sec) mypy .......... OK (17.52 sec) というように、4人みんなにOKを貰えたら合格、CommitしてPush。 参考 実際作っているプロジェクト テンプレートの解説ページ
- 投稿日:2022-01-25T14:16:46+09:00
FastAPIでかんたんFirebaseAuthorization+かんたんにテスト
これまで他の言語で作っていたAPIを書き直しています。以前のAPIはFirebaseAuthorizationで認証していたので、FastAPIでもFirebaseAuthorizationでユーザー認証したい。さて、どうするか。 公式ライブラリでなんとかする セキュリティ的にこれが無難だと思う。しかし、account_key.json のような認証鍵をファイルとして渡す必要があり、Dockerコンテナ化する場合はボリュームのマウントがめんどくさくなってしまう。環境変数に全部乗せることができないだろうか? サードパーティライブラリでなんとかする 実は、本当にFirebase Authorizationの認証だけがしたいならProjectIDさえあれば、署名が有効かどうかは 特定のエンドポイントにパラメータを添えてGETするだけで良い。(最悪IDトークン本体が1時間有効なJWTなので、本体をパースするだけでも確認できてしまうが、それが無効化されているかどうかを念の為Google鯖に確認しに行っているというだけ) それをすごくシンプルにできるようにしてるのがこのFastAPI-CloudAuth。本家firebase_adminと違い、深い依存関係が追加されることもないので、セキュア度が高くない用途であれば、このサードパーティでも良いと思う。今回作っているのは軽いゲームなので、今回はこっちを採用。 検証の実装例 非常にシンプルにできる仕様なので、実装も何もという感じだったが紹介。 from fastapi_cloudauth.firebase import FirebaseCurrentUser, FirebaseClaims router = FastAPI() get_current_user = FirebaseCurrentUser(project_id=os.environ["PROJECT_ID"]) @router.post( "/users", responses={ 200: {"description": "OK"}, 400: {"description": "Bad Request"}, 401: {"description": "Unauthorized"}, 409: {"description": "Conflict"}, }, tags=["users"], summary="Add a user", ) async def add_user( auth: FirebaseClaims = Depends(get_current_user), ) -> None: """Add specified new user to server""" # 認証できていればこの中にたどり着く # できていなければ401が既に返されている return auth.user_id # user_idが手に入ればあとは好きにどうぞ テスト用のスタブ例 Headerに入ったBearer 以降の部分をそのままユーザーIDとして扱うことで、テスト時はトークン関係なしに直接試せるようにする。もっとやりようはあるが、愚直にやったらこうなった。あとはテストをしようとしたら、毎回認証がスキップされて本体だけのテストを行うことができる。 seurity_api.py dependsHeader = Header(None) async def get_current_user_stub( authorization: Optional[str] = dependsHeader, ) -> FirebaseClaims: if authorization is None: raise HTTPException(status_code=401, detail="Not authenticated") separated_authorization = authorization.split("Bearer ") if len(separated_authorization) != 2: raise HTTPException(status_code=401, detail="Not verified") DUMMY_USER["user_id"] = separated_authorization[1] return DUMMY_USER get_current_user = FirebaseCurrentUser(project_id=os.environ["PROJECT_ID"]) conftest.py from src.security_api import get_current_user, get_current_user_stub @pytest.fixture def app() -> FastAPI: # ここにDependsの関数名をキーとして 別の関数を入れると オーバーライドができる application.dependency_overrides[get_current_user] = get_current_user_stub return application # type: ignore やってみた感想 Firebase Authorizationは楽にできてすごく良い
- 投稿日:2022-01-25T14:14:40+09:00
PythonでAPIを確認する
はじめに データを集める時に、APIが使えるようになると便利だなと思って基礎を学ぶことにしました。 自分の備忘録用。 利用するAPI サイトを検索すると手法習得で用いられている郵便番号のAPI。今回はその中でも、zipcloudを使うことにしました。 コードと追記 #モジュールをインポート import json import requests import pandas as pd #リクエストURL url = 'https://zipcloud.ibsnet.co.jp/api/search' # 条件指定 param={'zipcode':'100-0005'} #条件からレスポンスを取得 res = requests.get(url, params=param) #dict型に変換 response = json.loads(res.text) #results部分だけ抜き出す address = response['results'][0] #データフレームに変換(最初に郵便番号がくるように入れ替え) df = pd.DataFrame(address.values(), index=address.keys()).T re_df = df.reindex(columns=['zipcode','address1','address2','address3','kana1','kana2','kana3','prefcode']) re_df 項目 追記 リクエストURL 使いたいURLの情報を設定する 条件指定 リクエストURLに送るパラメータを決める レスポンスを取得 パラメータから返ってきた情報が得られる dict型に変換 文字として認識できるようにする resultの抜き出し 住所部分だけにする dfへの変換 データセットの形にする 上のスクリプトで得られたデータフレームがこちら。resultをそのまま抜き出すと郵便番号が最後になるので、re_dfで順番を入れ替えています(ここは好みの問題)。 まとめ APIというとかなり複雑なイメージがあったのですが、郵便番号に関しては割と簡単なコードで使えることが分かりました。これからはもっと複雑なAPIもトライしていけたらと思います。 ちなみに、〒100-0005は東京駅がある住所です。豆知識。 参考 https://www.taillook.tech/entry/python3-requests-zipcloud https://www.sejuku.net/blog/78985
- 投稿日:2022-01-25T12:36:00+09:00
PydanticのモデルがPythonの予約語と被った時の対処
みんな大好き、openapi-generator-cliで、python-fastapiジェネレータを使い、予約語と被るフィールドがあるモデルを生成した際、変な出力が出されたので、その修正策を考えました。 作ろうとしたAPIレスポンス まず、今回作ろうとしているAPIはこのような型のデータを返します。あれ、defってなんとかできるんだっけ?と初めから不安でしたが、とりあえず生成。 (残念ながら自分で定義した仕様でないので従わざるを得ない..) レスポンス型 { "query": "rating", "name": "#RATING_MIN", "def": 0, "min": 0, "max": 50, "step": 1, "display": "number" } 実際のAPIのスキーマ この型を返すAPIのスキーマを適当に定義してみます。 バリデーションに最低値が0以上と指定しておきましょう。 (すみませんめんどくさくなってしまったので、各自で定義してみてください...) 生成コマンド そしてopenapi-generator-cliで生成してみます。 openapi-generator-cli generate -i test.yaml -g python-fastapi -o api_gen 生成したモデル 生成したモデルにはこのようなモデルが出てきました src->models->search_slider_option.py(修正前) class SearchSliderOption(BaseModel): """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). Do not edit the class manually. SearchSliderOption - a model defined in OpenAPI query: The query of this SearchSliderOption. name: The name of this SearchSliderOption. type: The type of this SearchSliderOption. _def: The _def of this SearchSliderOption. min: The min of this SearchSliderOption. max: The max of this SearchSliderOption. step: The step of this SearchSliderOption. display: The display of this SearchSliderOption. """ query: str name: str type: str _def: int min: int max: int step: int display: str @validator("def") def _def_min(cls, value): assert value >= 0 return value defは Pythonの予約語なので アンダースコアが接頭辞に追加されています。なるほど、ちゃんとやっている、と思ったのですがこの段階では、pydantic.errors.ConfigError を吐かれてしまい、動作させられませんでした。この原因はエイリアスが設定されていないってことだろうと思い、普通にPydanticのエイリアス設定方法を調べます。が、どの設定方法でもうまくいきませんでした。エイリアスを設定したらその時点で動くと思ったのですが... どう解決するか 色々試したところ、そもそも変数名にアンダースコア入ってるのが悪いんじゃね?ってことに気づきました。そこで、_def を default に変えてみたら、うまくいきました。 src->models->search_slider_option.py(修正後) from pydantic import AnyUrl, BaseModel, EmailStr, Field, validator # noqa: F401 class SearchSliderOption(BaseModel): """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). Do not edit the class manually. SearchSliderOption - a model defined in OpenAPI query: The query of this SearchSliderOption. name: The name of this SearchSliderOption. type: The type of this SearchSliderOption. _def: The _def of this SearchSliderOption. min: The min of this SearchSliderOption. max: The max of this SearchSliderOption. step: The step of this SearchSliderOption. display: The display of this SearchSliderOption. """ query: str name: str type: str default: int = Field(..., alias="def") min: int max: int step: int display: str @validator("default") def default_min(cls, value): assert value >= 0 return value まじか、そんなことあるのか となりましたが、まぁ動いたのでヨシです! 参考
- 投稿日:2022-01-25T12:07:07+09:00
HHKB プログラミングコンテスト 2022(ABC235) A~D問題 ものすごく丁寧でわかりやすい解説 python 灰色~茶色コーダー向け #AtCoder
HHKB プログラミングコンテスト 2022(ABC235) A~E問題の解説記事です。 灰色~茶色コーダーの方向けに解説しています。 その他のABC解説、動画などは以下です。 株式会社PFU様について 本コンテストは株式会社PFU様が主催されています。 ちなみにHHKBはPFU様の製品であるHappy Hacking Keyboardの略称です。 気になる方は新卒採用ページをご覧ください。 社員有志の「プロコン部」というサークルもあるそうです。 A - Rotate abcを受け取り、abc,bca,cabを作って足し算します。 まずabcを文字列として受け取ります。 pythonでは文字列の先頭を0文字目、次を1文字目、...と数えます。 よってabcをXという変数へ受け取った場合は以下のようになります。 a=X[0] b=X[1] c=X[2] X[i]は「Xのi文字目」という意味です。 次にそれぞれを結合してabc,bca,cabを作ります。これは単純に「+」でつなげば良いです。 例えばbcaは bca=b+c+a となります。 このままでは文字列と認識され計算ができないので、結合したら文字列→整数へ変換します。 int(文字列) とすることで整数へ変換できます。 最後にabc,bca,cabを足して答えを出します。 入力の受け取り、出力がわからない方は以下の記事を参考にしてください。 【提出】 # 入力を受け取り X=input() # a=0文字目 a=X[0] # b=1文字目 b=X[1] # c=2文字目 c=X[2] # a,b,cを結合 abc=a+b+c # 整数へ変換 abc=int(abc) # b,c,aを結合 bca=b+c+a # 整数へ変換 bca=int(bca) # c,a,bを結合 cab=c+a+b # 整数へ変換 cab=int(cab) # 足し算を行う ans=abc+bca+cab # 答えの出力 print(ans) B - Climbing Takahashi 問題文の条件をそのまま書ければOKです。 高さの情報をHというリストに受け取ります。 今いる台をi番目の台とした場合、右の台へ進む条件は以下のようになります。 ・右端の台でない⇔i<N-1 ・右の台の方が高い⇔H[i]<H[i+1] 両方が満たされている間「右の台へ進む」⇔「iに+1」を行います。 pythonのリストは0インデックス(先頭が「0番目」、次が「1番目」、...)なので右端の台はH[N]ではなくH[N-1]となることに注意してください。 【提出】 # 入力を受け取り N=int(input()) H=list(map(int,input().split())) # i番目の台の高さを確認していく # 最初は左端 i=0 # 右端の台でなく かつ 右の台のほうが高ければ while i<N-1 and H[i]<H[i+1]: # 右の台へ移動 i+=1 # 答えの出力 print(H[i]) C - The Kth Time Query 「k回目に出てくるx」を連想配列へ記録します。 入力例1を使って説明します。 N:6 Q:8 a:1 1 2 3 1 2 「count」というのはその数が何回目に出てきたかを表します。 例えばi=5(a5)の「1」は3回目に出てくる「1」なのでcountは3になっています。 連想配列へ 青い部分(aの値,count)をキー 赤い部分(インデックス番号)を値 として記録していきます。 連想配列(defaultdict)を使ったことがない人は以下を参照してください。 defaultdict(連想配列)について 辞書または連想配列と言います。 キー:値 というような対応付を行えるデータ構造です。 連想配列はdict()と書くことでも使えますが、デフォルトの値(初期値)が設定できません。そのため存在チェックなど色々面倒が発生します。 その面倒を避けるためにdefaultdictを使います。 import:from collections import defaultdict 作成(デフォルト0):変数名=defaultdict(int) キー、値の登録:変数名[キー]=値 値の取り出し:変数名[キー] 【使用例】 # インポート from collections import defaultdict # 作成(デフォルト0):変数名=defaultdict(int) dictionary=defaultdict(int) # キー、値の登録:変数名[キー]=値 dictionary[5]=1 # 値の取り出し:変数名[キー] x=dictionary[5] 詳しく知りたい人は以下を参照してください。 キーには複数の値を登録することも出来ます。今回は(aの値,count)をキーにします。 記録するときには何回目に出てきた数かを確認するため、出てきた数をカウントする連想配列(count)を用意しておきましょう。 記録が出来たらクエリの値をキーに値を出力していくだけです。 条件を満たす要素が存在しない場合、defaultdictはデフォルトの値(=0)を返すので、その場合のみ「-1」を出力するようにしておきます。 【提出】 # 入力を受け取り N,Q=map(int,input().split()) a=list(map(int,input().split())) # defaultdictのインポート from collections import defaultdict # 何回目に出てきた数かカウントする連想配列 count=defaultdict(int) # k回目のxが出てくるインデックス番号 indx=defaultdict(int) # i=0~(N-1) for i in range(N): # count[a[i]]をプラス1 count[a[i]]+=1 # インデックス番号を記録 # 0インデックスなのでi+1とすることに注意 indx[(a[i],count[a[i]])]=i+1 # Q回 for i in range(Q): # 入力の受け取り x,k=map(int,input().split()) # k回目のxが存在しない場合 # ⇔デフォルトの値=0が記録されている場合 if indx[(x,k)]==0: # 「-1」を出力 print(-1) # そうでない場合 else: # 答えを出力 print(indx[(x,k)]) D - Multiply and Rotate BFS(幅優先探索)を使います。 「1」からスタートして2つの操作で作れる数へとどんどん進みながらそこに至るまでの操作回数を記録していきます。 2つの操作どちらを行っても桁数が減ることはないです。 2≤N<10^6という条件から操作の結果10^6を超えた数は無視して良いことがわかります。 どうやって「1」から次の数へどんどん進んでいくかですが、これにBFSを使います。 BFSはグラフを探索するアルゴリズムです。 本問は一見グラフに見えませんが操作によって次の数に進んでいくということをグラフ上の頂点間移動とみなせばBFSが使えます。 具体的な手順は以下です。 ※キューがよくわからない人は後述の「dequeについて」を見てください。 (1)各数の操作回数を記録するリスト(num)を用意(初期値は-1) (2)キューに「1」(=最初の数)を入れる (3)キューの左端から要素を取り出し、その数にたどり着くまでの操作回数を確認 (4_1)a倍して10^6未満 ならば num=-1 ならば(まだ一度もその数にたどり着いていないなら) 操作回数を記録 キューへ追加 (4_2)10より大きい かつ 10で割り切れない ならば 末尾を先頭へ移動 num=-1 ならば(まだ一度もその数にたどり着いていないなら) 操作回数を記録 キューへ追加 (5)(3)~(4)をキューが空になるまで繰り返す キューが空になったらnumに記録した「N」の操作回数を出力して終了です。 dequeについて dequeはリストのようなものですが、先頭から要素を取り出す操作をO(1)で行うことができます。 (リストだとO(N)かかります) import:from collections import deque 作成:変数名=deque() 末尾に要素追加【O(1)】:変数名.append(要素) 先頭から要素を取り出して削除【O(1)】:変数名.popleft() 【使用例】 # インポート from collections import deque # 作成:変数名=deque() que=deque() # 末尾に要素追加 O(1):変数名.append(要素) que.append(1) # 先頭から要素を取り出して削除 O(1):変数名.popleft() x=que.popleft() 詳しく知りたい人は以下のページを見てください。 本問についてではないですが、BFSについては動画での解説を作っていますのでそちらも是非ご覧ください。 【提出】 # 入力の受け取り a,N=map(int,input().split()) # 操作回数の記録リスト # 初期値は「-1」 num=[-1]*10**6 # dequeのインポート from collections import deque que=deque() # キューに「1」を追加 que.append(1) # 「1」への操作回数は0 num[1]=0 # キューが空になるまで while 0<len(que): # 今の数 now=que.popleft() # 今の操作回数 k=num[now] # a倍した場合の数 to=now*a # a倍した時10^6を超えていなければ if to<10**6: # まだ一度も出てきていない数の場合 if num[to]==-1: # 操作回数(k+1)を記録 num[to]=k+1 # キューへ que.append(to) # 今の数が 10より大きい かつ 10で割り切れない if 10<=now and now%10!=0: # 文字列へ変換 now_str=str(now) # 末尾を先頭へ to_str=now_str[-1]+now_str[:-1] # 整数へ変換 to=int(to_str) # まだ一度も出てきていない数の場合 if num[to]==-1: # 操作回数(k+1)を記録 num[to]=k+1 # キューへ que.append(to) # 答えの出力 print(num[N]) 【広告】 「AtCoder 凡人が『緑』になるための精選50問詳細解説」 AtCoderで緑になるための典型50問をひくほど丁寧に解説した本(kindle)、pdf(booth)を販売しています。 値段:100円(Kindle Unlimited対象) 【kindle】 【booth(pdf)】 1~24問目まではサンプルとして無料公開しています
- 投稿日:2022-01-25T12:06:55+09:00
ABC234 A~E問題 ものすごく丁寧でわかりやすい解説 python 灰色~茶色コーダー向け #AtCoder
ABC234(AtCoder Beginner Contest 234) A~E問題の解説記事です。 灰色~茶色コーダーの方向けに解説しています。 その他のABC解説、動画などは以下です。 A - Weird Function 今回のA問題は「やや難」です。 普段のA問題はもっと簡単なので、今回解けなかった人も落ち込まずに競技プログラミングを続けてください。 fという関数を作ってしまえば問題文に書いている計算をそのまま行うことが出来ます。 関数を作るときは def 関数名(引数): 処理内容 return 返り値 と書きます。 本文の場合 tを引数 → t^2+2t+3を返り値 とするので関数fは以下のようになります。 def f(t): return t**2+2*t+3 これを書いておくと例えばt=2の場合 f(2)=2^2+2*2+3=11 と計算ができます。 そもそもプログラミングにおける関数って何?という人は以下のページがわかりやすいので読んでみてください。 関数が作れたらあとは問題文に記載の通り、f(f(f(t)+t)+f(f(t)))を計算して出力します。 入力の受け取り、出力がわからない方は以下の記事を参考にしてください。 【提出】 # 入力の受け取り t=int(input()) # 関数の定義 # 引数;t 返り値:t^2+2t+3 def f(t): return t**2+2*t+3 # 答えの計算 ans=f(f(f(t)+t)+f(f(t))) # 答えの出力 print(ans) B - Longest Segment Nは最大で100なので全部の組み合わせを試しても100C2=4950通りしかありません。 よって全組み合わせの線分の長さを確認し、一番大きいものを出力します。 (i,j)の組全部を試すときは以下のように書きます。 # i=1~(N-1) for i in range(N): # j=(i+1)~(N-1) for j in range(i+1,N): こう書くことでいい感じに(i,j)組み合わせを作ることができます。 試しにN=5として(i,j)を出力してみましょう。 N=5 # i=1~(N-1) for i in range(N): # j=(i+1)~(N-1) for j in range(i+1,N): print(i,j) 【出力結果】 0 1 0 2 0 3 0 4 1 2 1 3 1 4 2 3 2 4 3 4 i=0についてj=1,2,3,4 i=1についてj=2,3,4、 i=2についてj=3,4、 ... と組み合わせを作れています。jの範囲を(i+1)からスタートとするのがコツです。 本問に限らずこの書き方は非常によく使うので自分で書けるようになりましょう。 2点(xi,yi),(xj,yj)の距離は以下の式で求まります。 √((xi-xj)^2+(yi-yj)^2) ルートの計算はmathをインポートしてsqrtを使います。 from math import sqrt と書くことでxの平方根をsqrt(x)と計算できます。 【提出】 # 入力の受け取り N=int(input()) # 座標の格納用 x=[] y=[] # i=0~(N-1) for i in range(N): # 入力の受け取り xi,yi=map(int,input().split()) # 座標の格納 x.append(xi) y.append(yi) # ルートの計算用 from math import sqrt # 答え ans=0 # i=1~(N-1) for i in range(N): # j=(i+1)~(N-1) for j in range(i+1,N): # 距離の計算 length=sqrt((x[i]-x[j])**2+(y[i]-y[j])**2) # 答えより大きければ更新 ans=max(ans,length) # 答えの出力 print(ans) C - Happy New Year! 問題文だけでは何を言っているのかよくわかりませんね。 こういう時はとりあえず入力例を見ます。 入力例1には 「10進数で表記したときに0,2のみからなる正整数を小さいほうから並べると2,20,22,...となります」 と記載があります。これでなんとなく意味がわかります。 「10進数で表記したときに0,2のみからなる正整数」を小さい順に書いてみましょう. 1:2 2:20 3:22 4:200 5:202 6:220 7:222 8:2000 ... 2進数の増え方に似ていますね。 実際2進数を書いてみると以下のようになっています。 1:1 2:10 3:11 4:100 5:101 6:110 7:111 8:1000 ... 要するに「10進数で表記したときに0,2のみからなる正整数」とは 2進数表記における「1」を「2」に置き換えたのもの ということがわかります。 よってNを2進数へ変換し、「1」を「2」に換えれば終了です。 Nを2進数へ変換するときは bin(N) と書きます。こう書くとNを2進数に変換したものが「文字列として」得られます。 ただし頭に2進数であることを表す「0b」という文字が入ります。 例えばbin(5)="0b101"となります。 「1」を「2」に置き換えるにはreplaceを使います。文字列中の文字を別の文字へ変換してくれるコマンドです。 使い方は以下です。 文字列.replace("変換前の文字列","変換後の文字列") 出力のときは頭の「0b」はいらないので2文字目~を出力します。 【提出】 # 入力の受け取り N=int(input()) # 2進数へ変換 N_bit=bin(N) # 「1」→「2」へ変換 ans=N_bit.replace("1","2") # 答えの出力 # 0文字目、1文字目は不要のため2文字目~ print(ans[2:]) D - Prefix K-th Max こういった問題は紙とペンを使って自分で書きながら考えるのがよいです。 入力例2を使って考えましょう。 解けなかった人は実際に紙に書きながら読んでください。 N:11 K:5 P:3 7 2 5 11 6 1 9 8 10 4 「i=5」 まずP1~P5までです。 「3 7 2 5 11」 K(=5)番目に大きい値は最小値の「2」であることがすぐにわかります。 「i=6」 次にP1~P6です。i=5までの数列にP6=6が加わります。 「3 7 2 5 11 6」 K(=5)番目に大きい値は「3」です。 ここで重要なのはこれ以降のiについて「3」より小さい「2」は絶対に答えにならないということです。 「i=7」 P1~P7です。i=6までの数列にP7=1が加わります。 「3 7 2 5 11 6 1」 K(=5)番目に大きい値は「3」です。 同様にこれ以降のiについて「3」より小さい「1」は絶対に答えになりません。 ここまでの操作を見ていると各iについて「P1~P(i-1)でK番目に大きい数」と「Pi」の大小関係が重要であることがわかります。 具体的には各iについて ・Pi<「P1~P(i-1)でK番目に大きい数」の場合 「P1~P(i-1)でK番目に大きい数」が答えです。 Piがこれ以降答えになることはありません。 ・「P1~P(i-1)でK番目に大きい数」<Piの場合 「Pi」が答えです。 「P1~P(i-1)でK番目に大きい数」が答えになることはありません。 しかしPiの追加毎に最小値の確認なりソートなりしていては間に合いません。 そこで最小値を高速で取り出せるデータ構造heapを使います、 heapqを使ったことがない人は以下を参照してください。 heapについて heapはリストから最小値をO(logN)で取り出せるデータ構造です。 import:import heapq リストのheap化 O(N):heapify(リスト) 要素の追加 O(logN):heappush(リスト,要素) 最小値の参照 O(1):リスト[0] 最小値の取り出し O(logN):heappop(リスト) 【使用例】 # インポート import heapq # リストを用意 que=list() # リストのheap化 O(N):heapify(リスト) heapq.heapify(que) # 要素の追加 O(logN):heappush(リスト,要素) heapq.heappush(que, 6) heapq.heappush(que, 1) # 最小値の参照 O(1):リスト[0] print(que[0]) # 最小値の取り出し O(logN):heappop(リスト) min_x=heapq.heappop(que) 詳しく知りたい人は以下のページを見てください。 heap化したリスト(que)を用意し、各i(K<i)毎に以下の操作を行います。 ①queの最小値を取り出す ②「queの最小値」と「Pi」のうち大きい方をqueに戻す ③「queの最小値」を出力 それでは入力例2で上記操作を行うとどうなるか見てみましょう。 「i=5」 que=[3 7 2 5 11] まずi=kについては素直にP1~PKの最小値を確認します。 最小値は「2」です。 queをheap化します。 これでこのqueから最小値を取り出す操作を高速で行うことができます。 「i=6」 que=[3 7 2 5 11] ①queの最小値を取り出す 最小値は「2」です。 ②「queの最小値」と「Pi」の大きい方をqueに戻す 「queの最小値」=2 「P6」=6 大きいのは「P6」=6です。これをqueに追加します。 que=[3 7 5 11 6] 「2」は①で取り出したため無くなっていることに注意してください。 ③「queの最小値」を出力 最小値は「3」なので「3」を出力します。 「i=7」 que=[3 7 5 11 6] ①queの最小値を取り出す 最小値は「3」です。 ②「queの最小値」と「Pi」の大きい方をqueに戻す 「queの最小値」=3 「P7」=1 大きいのは「queの最小値」=3です。queに追加(戻す)します。 que=[7 5 11 6 3] ③「queの最小値」を出力 最小値は「3」なので「3」を出力します。 この操作を繰り返せば答えを出すことが出来ます。 【提出】 # 入力の受け取り N,K=map(int,input().split()) P=list(map(int,input().split())) # heapqのインポート import heapq # リストを用意 que=[] # K個目までをリストへ入れる for i in range(K): que.append(P[i]) # 最小値を出力 print(min(que)) # queをheap化 heapq.heapify(que) # i=K~(N-1) for i in range(K,N): # ①queの最小値を取り出す x=heapq.heappop(que) # ②「queの最小値」と「Pi」の大きい方をqueに戻す heapq.heappush(que,max(x,P[i])) # ③「queの最小値」を出力 print(que[0]) E - Arithmetic Number 等差数列は(初項,公差,項数)の3つが決まれば作れます。 等差数は各桁が0~9でなければなりません。 あり得る初項は1~9です。 あり得る公差は-9~9です。 あり得る項数(桁数)は1~18です。 (Xは10^17以下ですから、19桁以上の等差数は考える必要がありません) 以上を考えると答えになりうる等差数は最大でも9*19*18=3078個以下しかないことがわかります。 ※実際には等差数を作れない組み合わせがあるのでもっと少ないです。 よって等差数を全て作り、ソートしてX以上で最小のものを探せばよいです。 【提出】 # 等差数を作る関数 # 引数:a(初項),d(公差),n(桁数) 返り値:対応する等差数 作れなかったら「-1」 def make_num(a,d,n): # 等差数 num="" # n桁分 # i=0~(n-1) for i in range(n): # i桁目 x=a+d*i # 0以上9以下になっていれば if 0<=x<=9: # i桁目を記録 num+=str(x) # そうでなければ else: # 「-1」を返す return -1 # 整数にして返す return int(num) # 等差数を記録するリスト num_list=[] # 初項:1~9 for a in range(1,10): # 公差:-9~9 for d in range(-9,10): # 桁数:1~18 for n in range(1,19): # 対応する等差数を作る num=make_num(a,d,n) # 「-1」でなければ if num!=-1: # 追加 num_list.append(num) # ソート num_list.sort() # 入力の受け取り X=int(input()) # 初めてX以上になるところを確認 i=0 while num_list[i]<X: i+=1 # 答えの出力 print(num_list[i]) 【広告】 「AtCoder 凡人が『緑』になるための精選50問詳細解説」 AtCoderで緑になるための典型50問をひくほど丁寧に解説した本(kindle)、pdf(booth)を販売しています。 値段:100円(Kindle Unlimited対象) 【kindle】 【booth(pdf)】 1~24問目まではサンプルとして無料公開しています
- 投稿日:2022-01-25T11:50:54+09:00
青空文庫を使ってネガポジ判定をやってみた
ネガポジ判定とは 文書のネガポジ判定(極性判定)とは、それぞれの文書が肯定的(ポジティブ)か否定的(ネガティブ)かを判定するタスクである。 ここでは抽出した単語のネガポジ判定をするために東北大学乾・鈴木研究室が提供している「日本語評価極性辞書」を活用する。 https://www.cl.ecei.tohoku.ac.jp/Open_Resources-Japanese_Sentiment_Polarity_Dictionary.html 実装 必要なライブラリをインポート import zipfile import urllib.request import MeCab import pandas as pd import re ネガポジ判定に使用するテキストを準備 # 青空文庫『吾輩は猫である』のファイルをダウンロード urllib.request.urlretrieve('https://www.aozora.gr.jp/cards/000148/files/789_ruby_5639.zip', '789_ruby_5639.zip') # zipファイルを解凍しデータを読み込む with zipfile.ZipFile('789_ruby_5639.zip', 'r') as zipf: data = zipf.read('wagahaiwa_nekodearu.txt') # bytes型に変換 text = data.decode('shift_jis') text[:1000] # 出力結果 '吾輩は猫である\r\n夏目漱石\r\n\r\n-------------------------------------------------------\r\n【テキスト中に現れる記号について】\r\n\r\n《》:ルビ\r\n(例)吾輩《わがはい》\r\n\r\n|:ルビの付く文字列の始まりを特定する記号\r\n(例)一番|獰悪《どうあく》\r\n\r\n[#]:入力者注\u3000主に外字の説明や、傍点の位置の指定\r\n\u3000\u3000\u3000(数字は、JIS X 0213の面区点番号またはUnicode、底本のページと行数)\r\n(例)※[#「言+墟のつくり」、第4水準2-88-74]\r\n\r\n〔〕:アクセント分解された欧文をかこむ\r\n(例)〔Quid aliud est mulier nisi amicitiae& inimica〕\r\nアクセント分解についての詳細は下記URLを参照してください\r\nhttp://www.aozora.gr.jp/accent_separation.html\r\n-------------------------------------------------------\r\n\r\n[#8字下げ]一[#「一」は中見出し]\r\n\r\n\u3000吾輩《わがはい》は猫である。名前はまだ無い。\r\n\u3000どこで生れたかとんと見当《けんとう》がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番|獰悪《どうあく》な種族であったそうだ。この書生というのは時々我々を捕《つかま》えて煮《に》て食うという話である。しかしその当時は何という考もなかったから別段恐しいとも思わなかった。ただ彼の掌《てのひら》に載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである。掌の上で少し落ちついて書生の顔を見たのがいわゆる人間というものの見始《みはじめ》であろう。この時妙なものだと思った感じが今でも残っている。第一毛をもって装飾されべきはずの顔がつるつるしてまるで薬缶《やかん》だ。その後《ご》猫にもだいぶ逢《あ》ったがこんな片輪《かたわ》には一度も出会《でく》わした事がない。のみならず顔の真中があまりに突起している。そうしてその穴の中から時々ぷうぷうと煙《けむり》を吹く。どうも咽《む》せぽくて実に弱った。これが人間の飲む煙草《たば' テキストを整形して文章リストを作成 text_formatted = re.split(r'\-{5,}', text)[2] text_formatted = text_formatted.split('底本:')[0] text_formatted = re.sub(r'《.+?》', '', text_formatted) # 《.+》に該当する文字列を消去(空文字列に置換) text_formatted = re.sub(r'[#.+?]', '', text_formatted) # [#.+](見出しに使われている)に該当する文字列を消去(空文字列に置換) text_formatted = text_formatted.strip() text_formatted = text_formatted.replace('\u3000', '') # 空白文字などを除去 text_formatted = text_formatted.replace('\r', '') # 改行コードを削除 text_formatted = text_formatted.replace('\n', '') # 改行コードを削除 sentences = text_formatted.split('。') sentences[:10] # 出力結果 ['一吾輩は猫である', '名前はまだ無い', 'どこで生れたかとんと見当がつかぬ', '何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している', '吾輩はここで始めて人間というものを見た', 'しかもあとで聞くとそれは書生という人間中で一番|獰悪な種族であったそうだ', 'この書生というのは時々我々を捕えて煮て食うという話である', 'しかしその当時は何という考もなかったから別段恐しいとも思わなかった', 'ただ彼の掌に載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである', '掌の上で少し落ちついて書生の顔を見たのがいわゆる人間というものの見始であろう'] 形態素解析を実施して文章ごとの単語リストを作成 words_list = [] t = MeCab.Tagger('-Ochasen') for sentence in sentences[:-1]: sentence_parsed = t.parse(sentence) word_s = [] for line in sentence_parsed.splitlines()[:-1]: word_s.append(line.split('\t')[2]) words_list.append(word_s) print(words_list[:10]) # 出力結果 [['一', '吾輩', 'は', '猫', 'だ', 'ある'], ['名前', 'は', 'まだ', '無い'], ['どこ', 'で', '生れる', 'た', 'か', 'とんと', '見当', 'が', 'つく', 'ぬ'], ['何', 'でも', '薄暗い', 'じめじめ', 'する', 'た', '所', 'で', 'ニャーニャー', '泣く', 'て', 'いた事', 'だけ', 'は', '記憶', 'する', 'て', 'いる'], ['吾輩', 'は', 'ここ', 'で', '始める', 'て', '人間', 'という', 'もの', 'を', '見る', 'た'], ['しかも', 'あと', 'で', '聞く', 'と', 'それ', 'は', '書生', 'という', '人間', '中', 'で', '一番', '|', '獰悪', 'だ', '種族', 'だ', 'ある', 'た', 'そう', 'だ'], ['この', '書生', 'という', 'の', 'は', '時々', '我々', 'を', '捕える', 'て', '煮る', 'て', '食う', 'という', '話', 'だ', 'ある'], ['しかし', 'その', '当時', 'は', '何', 'という', '考', 'も', 'ない', 'た', 'から', '別段', '恐い', 'いとも', '思う', 'ない', 'た'], ['ただ', '彼', 'の', '掌', 'に', '載せる', 'られる', 'て', 'スー', 'と', '持ち上げる', 'られる', 'た', '時', '何だか', 'フワフワ', 'する', 'た', '感じ', 'が', 'ある', 'た', 'ばかり', 'だ', 'ある'], ['掌', 'の', '上', 'で', '少し', '落ちつく', 'て', '書生', 'の', '顔', 'を', '見る', 'た', 'の', 'が', 'いわゆる', '人間', 'という', 'もの', 'の', '見る', '始', 'だ', 'ある', 'う']] 日本語評価極性辞書を準備 # 日本語評価極性辞書をダウンロード urllib.request.urlretrieve('https://www.cl.ecei.tohoku.ac.jp/resources/sent_lex/wago.121808.pn', 'wago.121808.pn') # 日本語評価極性辞書を読み込む wago = pd.read_csv('wago.121808.pn', header=None, sep='\t') display(wago.head(10)) ネガポジ判定を実施 ポジティブな単語は$+1$、ネガティブな単語は$-1$としてスコアリング # 単語とスコアを対応させる辞書を作成 word2score = {} values = {'ポジ(経験)': 1, 'ポジ(評価)': 1, 'ネガ(経験)': -1, 'ネガ(評価)': -1} for word, label in zip(wago.loc[:, 1], wago.loc[:, 0]): word2score[word] = values[label] # 各文章のスコアを算出 scores = [] for words in words_list: score = 0 for word in words: if word in word2score: score += word2score[word] scores.append(score) # 各文章のスコアを算出 scores = [] for words in words_list: score = 0 for word in words: if word in word2score: score += word2score[word] scores.append(score) # 各文章のスコアをデータフレーム化 scores_df = pd.DataFrame({ 'sentence': sentences[:-1], 'score': scores}) display(scores_df.head(10)) スコアでソートしたデータフレームを作成 scores_df_sorted = scores_df.sort_values('score', ascending=False) scores_df_sorted.head(10)でスコアが高い文章を確認 scores_df_sorted.tail(10)でスコアが低い文章を確認 感想 単語だけで判断しているため、ポジティブ判定になっているけどそれほどポジティブと思えない文章なども含まれていましたね。 今回は文学作品を題材としてネガポジ判定しましたが、Tweetや口コミなんかに利用するともっと納得感が得られる結果になるような気がします。
- 投稿日:2022-01-25T11:49:51+09:00
【Twitcasting】気になるあの配信者の配信開始通知をdiscordに送信方法を考えて実装してみた。
はじめに 趣味で推しの子がよくツイキャスするけど通知に気づけないからdiscordに投げるBotを作りました。 言語はPythonで書いてます。 記事の内容はほぼGitHubにあるものと同じです。メモ程度です。 実際の稼働してる様子はこんな感じ。 入れ方 とりあえず、GitHubにあげてあります。 ダウンロード出来たらconfg.pyを編集して指定された値を入力します。 最後にmain.pyを実行すれば… たぶん動きます。
- 投稿日:2022-01-25T10:52:54+09:00
Azure Key Vaultに格納されたシークレットを利用してAzure DatabricksからBlob Storageへアクセス
概要 Storageのアクセス情報をAzureDatabricksのNotebooks上に記載してアクセスするのではなく、AzureKeyVaultを利用してアクセスしてみたいと思います。 必要なリソース StorageAccountとBlobコンテナー AzureDatabricks AzureKeyVault 手順 blobコンテナーの作成 作成したStorageAccountの画面に移動し、[データストレージ]欄の[コンテナー]を選択します。[+コンテナー]を選択し、コンテナー名を入力します。 コンテナー内にテスト用のテキストファイルをアップロードしておきます。 [セキュリティとネットワーク]の下にある[アクセスキー]を選択します。[StorageAccount名]と[key1のキー]情報をメモしておきます。 ※キーの情報は、[キーの表示]をクリックすると表示されます。 AzureKeyVaultにシークレットを追加 作成したキーコンテナーの画面に移動し、[設定]欄から[シークレット]を選択します。[+生成/インポート]を選択します。 [シークレットの作成]画面で、以下の情報を指定します。残りの値はデフォルトのまま使用します。 ※ 対象StorageAccountキーのフレンドリ名はメモしておきます。 アップロードオプション:手動 名前:対象StorageAccountキーのフレンドリ名 値:先程コピーした対象StorageAccountのKey1の値 [設定]欄から[プロパティ]を選択します。[コンテナーの URI]と[リソース ID]をメモしておきます。 AzureDatabricksにシークレットスコープを作成 作成したAzureDatabricksのワークスペースを起動します。ワークスペースが別ウィンドウで開かれたら、URLの末尾に[#secrets/createScope]を追加します。 [Scope Name]を入力し、メモしておいた[コンテナーの URI]と[リソース ID]を入力します。 ※ Scope Nameはメモしておきます。 AzureDatabricksワークスペースのホームページで[New Cluster]を選択します。[Cluster Name]を入力し、クラスターを作成します。 AzureDatabricksワークスペースのホームページで[New Notebook]を選択します。 Notebook名を入力し、言語をpythonに設定します。加えて、先ほど作成したクラスターを設定します。 次のコマンドを実行して、コンテナーにマウントします。以下の値は変更してください。 your-container-name StorageAccountに作成したコンテナ名 your-storage-account-name 作成したStorageAccount名 mount-name 任意で指定したmount-nameを使用してディレクトリが作成されます。 config-key fs.azure.account.key.[your-storage-account-name].blob.core.windows.net" または、"fs.azure.sas.[your-container-name].[your-storage-account-name].blob.core.windows.net"を選択します。 scope-name Databricksで作成したシークレットスコープの名前 key-name AzureKeyVault内のStorageAccountキー用に作成したシークレットの名前 dbutils.fs.mount( source = "wasbs://<your-container-name>@<your-storage-account-name>.blob.core.windows.net", mount_point = "/mnt/<mount-name>", extra_configs = {"<conf-key>":dbutils.secrets.get(scope = "<scope-name>", key = "<key-name>")}) BlobStorageコンテナー内のテキストファイルをデータフレームに読み込みます。 df = spark.read.text("/mnt/<指定のmount-name>/<コンテナ内のfile-name>") ファイルの内容を表示します。 df.show() マウントを解除します。 dbutils.fs.unmount("/mnt/<指定のmount-name>")
- 投稿日:2022-01-25T10:50:19+09:00
【Project Euler】Problem 53: 組み合わせ数
本記事はProjectEulerの「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。 問題 53. 組み合わせ数 原文 Problem 53: Combinatoric selections 問題の要約:$1 \le n \le 100$ で以下の組み合わせ数が$10^6$を超える数を求めよ \begin{pmatrix} n \\ r \end{pmatrix} 組み合わせ数はsympyのbinomialを、それに入力する$n,r$はcombinations_with_replacementで生成します。 from sympy import binomial from itertools import combinations_with_replacement as combr n, N = 100, 10**6 print(f"Answer: {len([c for c in [binomial(n,r) for (r,n) in combr(range(1,n+1),2)] if c>N])}") (開発環境:Google Colab)
- 投稿日:2022-01-25T10:47:46+09:00
【Project Euler】Problem 52: 並び替えの倍数
本記事はProjectEulerの「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。 問題 52. 並び替えの倍数 原文 Problem 52: Permuted multiples 問題の要約:ある数の2x,3x,4,x5,x6の数が全部並び替えると元の数と同じになる最小の数を求めよ 並び替えて同じがどうかは「Problem 49: 並び替え素数」で作ったisPermSameを使います。 def isPermSame(n1, n2): return ("".join(sorted(str(n1)))) == ("".join(sorted(str(n2)))) def isPermMulSame(n, m): for mul in range(2,m+1): if not isPermSame(n, n*mul): # if not permutation same return False return True for n in range(100,167000+1): if isPermMulSame(n, 6): break print(f"Answer: {n}") (開発環境:Google Colab)