萬九郎の硬い船

プログラミング学習記録など

『退屈なことは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は変わらない。 mameumaは異なる値を格納する異なる変数である。しかし、リストや辞書の場合は事情が違ってくる。

mame = [0, 1, 2, 3, 4, 5, 6]
uma = mame
uma.pop(-1)

こうした場合、uma.pop(-1)でumaの最後の要素を削除するとmameの内容も同じく変わる。 これは、リストや辞書の場合は「リスト・辞書への参照」のみが変数に格納されるからである。mameumaには同じリストへの異なる参照が格納されていることになる。 もし参照ではなく元のリストに変更を加えずに保存しておきたい場合は、copyモジュールのcopy()とdeepcopy()を使う。

import copy
mame = [0, 1, 2, 3, 4, 5, 6]
uma = copy.copy(mame)
uma.pop(-1)

こうすると、mameumaはそれぞれ異なるリストを参照するようになり、mameをコピーしたumaに変更を加えてもmameは変更されない。 リストがリストを内包する場合は、deepcopy()でコピーすれば内包されたリストのコピーも作られる。

Python3でScrapyを使う

PythonによるWebスクレイピング』には、

残念ながら、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を合体したものと正規表現などを組み合わせることで、トップレベルからアクセスし直したりできる、ということだ。