『退屈なことはPythonにやらせよう』4章 演習プロジェクト
4章の演習プロジェクトは2つある。 まず、「カンマ付け」。
これはforループを使わずにカンマ区切りでjoin()する方がシンプルで良いな、と思ったので改良版を作った。
そして、「文字絵グリッド」。
こっちは、本当は与えられたリスト内リストの長さが均一かどうかを判定する処理も入れた上で
for i in range(6):
の部分を固定ではなくリスト内リストの長さぶんだけ回したかった。そうすれば何行×何列のグリッドでも回転できる。後々改良版を作りたいと思う。
Gistをブログ記事に埋め込んでみる:『退屈なことはPythonにやらせよう』3章 演習プロジェクト
Scrapyのチュートリアルが動かなかったりして心が折れている間に、『退屈なことはPythonにやらせよう』というPythonの人権を無視したタイトルの本を買って、勉強の寄り道をし始めた。 日々の雑務の自動化とPythonの理解を深めることが同時にできれば素晴らしいし、スクレイピングの課題も出てくるのでちょうどいいと思ったのだった。 しかし、この本の3章以降の最後に付いている「演習プロジェクト」には解答が載っておらず、自力でプログラムを完成させなければならない。 せっかくなので、この機会に自力で書いたプログラムをGistにアップしてブログに埋め込んでみようと思い、とりあえず3章の演習プロジェクト「コラッツ数列」のプログラムをアップしてみる。 「コラッツ数列」とは、
- nが偶数なら2で割る
- nが奇数なら3をかけて1を足す
の操作を繰り返すと、どんな数字から始めても最終的に1になる(そして、1になった後は1→4→2→1というループに入る)という問題の結果として得られる数列である。サバンナ八木の「パナキ」のような感じなのだが、「パナキ」のプログラムを書いてる人がやっぱりいた(この方は「パナキへの最短ルート」のテキストデータを使って、Rubyで実装している)。
メモ:ねづっちプログラムを考えてみる
Pythonの環境構築をやり直す
以前、使用しているMacのAnaconda環境でScrapyのチュートリアルが動かないという事態になったため、いっそ仮想環境化も含めてPythonの環境構築をやり直すことにした。 以下、Mac OSXでの環境構築のメモ。
Anacondaをアンインストールする
https://docs.continuum.io/anaconda/install/uninstall.html ほとんどの場合、Anacondaをフォルダごと削除するだけでいいようだ。その場合はターミナルで
rm -rf ~/anaconda3
を実行する。完全にアンインストールする場合は、Anacondaフォルダを削除する前にターミナルで
conda install anaconda-clean
を実行し、各ファイルを削除する前に確認を入れたい場合は
anaconda-clean
を、確認なしで全削除する場合は
anaconda-clean --yes
を実行する。Mac OSXまたはLinuxの場合、さらに.bash_profileから
export PATH="/Users/jsmith/anaconda3/bin:$PATH"
を削除して保存すれば完璧。
Homebrewをインストールする
https://brew.sh/index_ja.html ターミナルで
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
を実行する。
brewでPython3をインストールする
ターミナルで
brew install python 3
を実行する。
venvで仮想環境を作る
ターミナルで
python3 -m venv [ディレクトリ名]
を実行する。ディレクトリ名が仮想環境名になる。適宜ふさわしいものをつける。仮想環境に入るには
. [ディレクトリ名]/bin/activate
を実行する。仮想環境から抜けるには
deactivate
を実行する。
Pythonの文字列操作の便利なメソッドについて
Pythonには、文字列操作に便利なメソッドが用意されている。以下のメソッドはそれぞれ、文字列が
- isalpha():1文字以上の英字のみによる文字列かどうか
- isalnum():1文字以上の英数字のみによる文字列かどうか
- isdecimal():1文字以上の数字のみによる文字列かどうか
- istitle():1文字めが大文字で後はすべて小文字の英単語による文字列かどうか
を判定し、boolean値を返す。これらは、ユーザーのフォーム入力をバリデートする際などに有効。
- startswith()
- endswith()
は、文字列がカッコ内に指定された文字列で始まるかどうか/終わるかどうかを判定し、boolean値を返す。
- strip()
- lstrip()
- rstrip()
はそれぞれ、文頭文末、文頭、文末の空白文字を取り除く。カッコ内に文字列を指定して、その文字列に含まれる文字を取り除くこともできる。
Pythonの参照について
Pythonのリストや辞書では、それらへの「参照」が変数に格納される。
mame = 3 uma = mame mame = 6
とした場合は、mameに代入された値3がumaにコピーされ、その後でmameに別の値6を代入してもumaの値3は変わらない。 mameとumaは異なる値を格納する異なる変数である。しかし、リストや辞書の場合は事情が違ってくる。
mame = [0, 1, 2, 3, 4, 5, 6] uma = mame uma.pop(-1)
こうした場合、uma.pop(-1)でumaの最後の要素を削除するとmameの内容も同じく変わる。 これは、リストや辞書の場合は「リスト・辞書への参照」のみが変数に格納されるからである。mameとumaには同じリストへの異なる参照が格納されていることになる。 もし参照ではなく元のリストに変更を加えずに保存しておきたい場合は、copyモジュールのcopy()とdeepcopy()を使う。
import copy mame = [0, 1, 2, 3, 4, 5, 6] uma = copy.copy(mame) uma.pop(-1)
こうすると、mameとumaはそれぞれ異なるリストを参照するようになり、mameをコピーしたumaに変更を加えてもmameは変更されない。 リストがリストを内包する場合は、deepcopy()でコピーすれば内包されたリストのコピーも作られる。
Python3でScrapyを使う
残念ながら、Scrapyは、Python2.7では動きますが、Python3.xバージョンはまだリリースされていません。
と書いてあり、ウワアめんどくせえ、これが複数バージョンのPythonを仮想環境で分離するとよいと書いてあった理由か、と思ったが、調べてみるとPython3に対応したScrapyがリリースされたらしい。
$ pip install scrapy
とすると無事インストールできたようだ。ひとまず、仮想環境の問題が先送りにできた。
追記(2017.5.16)
その後、本に載っているサンプルコード(wikispider)を写経して実行してみたのだが、エラーが起きてうまく動かなかった。Scrapyのバージョンが違うのが原因なんじゃないか、というニオイがする。ここで無駄にハマるよりは、とりあえずScrapyが動いているところを自分の目で確かめておくのがいい!と判断し、この記事にあるコードを写経して実行したところ、正常に動いた。 チュートリアルをひととおりやってみた方がいいかもしれない、と思ってやってみたら、本のサンプルコードを実行したときと同じく
$ scrapy cralw quotes
が通らず、
raise KeyError("Spider not found: {}".format(spider_name)) KeyError: 'Spider not found: quotes'
というエラーが出る。これ、やっぱりPythonとScrapyのバージョンの問題なのかもしれない。AnacondaとScrapyの組み合わせが何か怪しそうだ。Scrapyのインストールガイドにも目を通した方がいいかもしれない。やはり仮想環境で分離する必要があるか…。
urlparseでURLを分解する/組み立てる
urllib.parseのurlparseを使うと、受け取ったURLを解析して分解したり、組み立て直すことができる。 urllibについては、本の冒頭で「繰り返し出てくるからドキュメントを読んでおけ」とあったとおり、細かい部分は一切説明がないので、メモしておく。
scheme://netloc/path;parameters?query#fragment
このURLの構造にもとづいて、受け取ったURLがscheme、netloc、path、parameters、query、fragmentの6つの要素からなるタプルになり、
from urllib.parse import urlparse url = "http://mankuro.xyz/blog/2017/05/11/relax/#respond" parsedUrl = urlparse(url)
こうした場合、
parsedUrl.scheme parsedUrl.netloc parsedUrl.path parsedUrl.parameters parsedUrl.query parsedUrl.fragment
でそれぞれ取り出すことができる。なるほど。 が、しかし、ちょっと待てよ?僕が知っているタプルの特徴は、「シーケンス型」なので値にアクセスするにはインデックスを使う、というものだ。
for i in range(len(parsedUrl)): print(parsedUrl[i])
みたいにやらないといけないのではなかったのか?
戻り値は実際にはtupleのサブクラスのインスタンスです。
この辺に秘密がありそうな気がするが、どうやらPythonで「.(ドット)」でつなぐ場合にどのようなものがあるか、というのをもう少しちゃんと理解する必要がありそうだ。 ともかく、urlparseの使い方自体は理解した。たとえば、受け取ったURLの階層が深かったりした場合に、一回分解してからparsedUrl.schemeとparsedUrl.netlocを合体したものと正規表現などを組み合わせることで、トップレベルからアクセスし直したりできる、ということだ。