Python3 – property – getter/setter

一般的な書き方

クラス定義でgetter/setterなどのアクセサを一般的に書くと次のようになる。

Pythonでは、メンバ変数(インスタンス変数)はパブリックアクセスが可能だが、メンバ変数への代入や参照を行った際に、裏でセッターやゲッターを呼び出すようにすることができる。また同じ手順で、インスタンスのdelete時の処理もプロパティとして登録できる。

このように見かけ上の参照・代入・削除処理から各アクセサを呼び出すために、Pythonでは組み込み関数のproperty()を使う方法と、@マークに続けたデコレータを使う方法がある。

property()関数

property関数を使うことで、getterなどをプロパティとして登録できる。次の例では、インスタンス変数_xに関するgetter、setter、deleterをxという名前のプロパティとして登録している。

property()関数の引数は以下の通り。

property(fget=None, fset=None, fdel=None, doc=None)

  • fget getterのメソッド名
  • fset setterのメソッド名
  • fdel deleterのメソッド名
  • doc プロパティの説明文(ドキュメント文字列)

propertyデコレータ

使い方

以下のように@マークに続けて書くデコレータでアクセサを定義できる。ただし以下の点に注意。

  • デコレータの最初は、@property
  • @propertyで定義できるのはgetterのみ

つまり、必ず最初に@propertyで、かつgetterを定義しなければならない。

@propertyは最初でなければならない

たとえば次のように@propertyがsetterよりも後にあると、”setterでxが定義されていませんよ”と怒られる。

@propertyはgetterしか定義できない

また@propertyでsetterを定義し、@*.getterでgetterを定義しようとすると、getterは実行されてsetterの方で”引数をセットできない”とエラーになる。

@propertyでダミーのアクセサを定義する方法

@propertyでダミーのアクセサを定義して、その後にgetterのデコレータで定義をするのは通る。

property()でドキュメント文字列だけを定義する方法

また、property()関数でメンバ変数のドキュメント文字列だけを定義したのちに、getter/setter/deleterのデコレータでそれぞれのメソッドを定義する方法もあるらしい。

 

 

 

Python3 – オブジェクトの文字列表現

__str__/__repr__

__str__メソッドはprint文やformat文の引数にオブジェクトが指定された場合に呼び出され、オブジェクトの”非公式の”文字列表現を返す。

__repr__メソッドはより正式なオブジェクトの内容を文字列で返し、2つのオブジェクトの同値性をインタープリタがチェックするときに使われる。

print文やformat文での__str____repr__の使われ方は次の通り。

  • __str__のみが定義されていれば__str__が使われる
  • __repr__のみが定義されていれば__repr__が使われる
  • __str____repr__の両方が定義されていれば__str__が使われる

エラー~str()関数による明示的な変換

ただし、以下のように文字列として直接操作しようとするとエラーになる。

これはprint文で扱われるかどうか以前の問題で、文字列にオブジェクトを結合しようとしたときに起こる。先行のオペランドが文字列で、その次に結合演算子が出てきたのでオブジェクトを文字列に変換しようとして、”オブジェクトを暗黙で文字列に変換できませんよ”とエラーになる。

また演算の順序を入れ替えると、次のようなエラーになる。

今度はオブジェクトが先行しているので、これに対して+演算子を読んだ瞬間に、インタプリタは”このオブジェクトと文字列の演算子として+はサポートされていない”と言ってくる。

これを解決するには、str()関数でオブジェクトを明示的に文字列に変換してやればよい。

 

Python3 – __iter()__によるイテレーターの実装

標準形

Pythonの特殊メソッド__iter____next__を使ってオブジェクトのイテレーターをつくることができる。ジェネレーターがyield文によって任意の値を生成するのに対して、イテレーターはコレクションの要素を順次取り出すときなどに有用。

__iter____next__の2つのメソッドを持ったオブジェクトがfor文で参照されるたびに、以下のように動作する。

  1. forを始める前に__iter__メソッドが実行される
  2. forブロックのループのたびに__next__メソッドが実行される

2つのメソッドの書き方は以下の通り。

__iter__
戻り値としてオブジェクト自身(self)を返す。
__next__
イテレートされるべきプロパティを変更し、戻り値として現在値を返す。もし終了条件に合致しいている場合、イテレート終了の例外を発する。

次の例では、1から初めて最初に設定した値まで1ずつ増える数列のイテレーターをつくっている。

最初の__init__メソッドでは引数から数列の最大値をプロパティにセット。

__iter__メソッドはカウンタになるプロパティに初期値をセットして、selfを返す。

__next__メソッドはそれが呼ばれるたびに次のことを行う。

  • カウンタ値が最大値を超えていたらforブロックから抜け出るよう例外を返す
  • 現在のカウンタ値を返す
  • カウンタ値をインクリメントする

forブロックから抜け出るための例外はStopIterationが準備されていて、forブロックがこの例外を受け取ると、エラーを発生させることなくforブロックの次の処理に移る。

インスタンス変数のイテレーターの取得

たとえばあるクラス内にリストがあって、リストそのものの構造は安全に守ったまま、その要素にアクセスする方法を考えてみた。

インスタンス変数のリストそのものやgetterなどで取得したリストへの参照をメソッドで返すと、リストそのものの操作までできてしまうが、それを防ぎたい。

そこで、インスタンス変数のイテレーターを生成するクラスを元のクラスのインナークラスとして準備して、外部へはイテレーターのインスタンスを渡すようにしてみた。

インナークラスでなくてもできるが、元のクラスのインスタンス変数の構造と密接に関連しているなら、インナークラスでまとめた方が明快だと思う。

 

 

 

Python3 – ネストクラス

クラスを入れ子にして、インナークラスを定義することができる。

次の例では、MainClassの中でInnerClassを定義して、それぞれのクラスのプロパティにアクセスしている。

留意点として、アウタークラスからインナークラスを参照する際に、[アウタークラス名].[インナークラス名]とすること。インナークラス名だけで定義しようとすると「そんなクラスはないよ」と怒られる。

なお、インナークラスの定義位置は、必ずしもアウタークラスの冒頭でなくてもよい。

 

 

Python3 – switch/caseはない

とても残念なことに、Pythonにはswitch/case構文がない(もちろん予約語でさえない)。

一般的には、この機能を実現するのにif~elif~elseを使えばよいということになっている。

いくつかの(いや多くの)素敵なサイトでdictionaryを使えばいいよ、と教えてくれるのに出会った。

「構文」として定まっていはいないが、かなり見やすいように思う。

 

 

 

Python3 – 条件分岐

if

標準形

他の言語とほぼ同じだが、else ifはelifと書く。

ブロックの実行内容が1行の場合は、if文などの後ろに続けて1行で書ききれる。

後置形(三項演算子)

この形は「文」ではなくて「式」で、[真の時の値] if [条件] else [偽の時の値]と書く。

switch/caseはない

Pythonにはswitch/case文は存在しない。

if-elif-elseで書くか、以下のように辞書を利用する。

 

 

 

 

Python3 – 繰り返し~ループ

for

標準形

for [変数] in [オブジェクト]:の形で変数がオブジェクトの要素を1つずつ取り出して実行する。

range関数とfor文

for文でrange関数を使うと、多数回のループを簡易に書ける。このとき、rangeの終了値は指定した値-1になることに注意。

enumerateによるインデックスの取得

Pythonのforループでは、リストなどの要素を直接取り出せるので表現が簡潔だが、逆に引数の値(配列の順序値など)が欲しいときがある。そのような場合は、enumerateを使ってインデックスを取得することができる。

enumerateでリストのインデックスが得られる

zipによる複数リストの並行処理

複数のリストの要素を同時に並行して取り出したいとき、zip()関数を使うとよい。

zipによる複数リストの並行ループ

enumerateとzipの複合処理

zipで複数のコレクションの要素を取り出しつつ、enumerateで全体の番号も取得したいとき。zipで取得する要素をタプルにする。

forループの最初と最後だけ処理を変える

forループでは各ループごとに同じ処理内容が繰り返されるが、これを最初と最後だけ変更したい場合の処理を考えてみる。

forループの最初と最後だけ処理を変えたい

while

while [条件式]:で条件式が真である間ループが実行される。

else~終了時の処理

else:ブロックを書くと、forやwhileのループ終了時にその内容が実行される。ループの後に実行文を書くのと動作は変わらないが、終了時処理をより明確にできる。

 continue

continue文は、それ以降の処理を飛ばしてループの先頭に飛ぶ。

次の例では、iが偶数の時はそれ以降のprint文を実行せずに次のステップに移る。

break

break文は、そのbreak文が含まれている一番内側のループから抜け出す。

次の例では、内側のループでjが3を超えるたびにループを終了し、外側の次のステップへ移る。外側のループはiが1を超えたときにそれ以降の処理をせずループを終了する。

 

 

R – rbindについて

rbind()で困った点

データフレームにrbindでアイテムを追加する場合、紹介されている例ではみな、データをベクトルで与えている(注:文字列型が勝手にFactor型にされてしまうのを4行目で回避している)。

一見するとうまくいっているが、実は問題がある。ageの項目が数字のはずだと内容を参照したり計算しようとすると・・・

となってしまう。

これはRのベクトルの仕様で、1つのベクトルの要素の型がすべて同じでなければならず、文字列型と数値型が混在した場合は数値が文字列に変換されてしまうため。

これを回避するために幾つかの方法があるが、たとえばここではas.numeric()を用いてみる。

うむ、うまくいっている、と思って、これに新たなデータを追加すると・・・

またしても怒られた。これはデータ追加のたびにベクトルの段階で文字列への変換が行われてしまうためらしく、上の9~10行目で前に数値型にしたはずのデータまで文字列に変換されてしまっている。

ベクトルではなくリストを使う

そこで、rbindでデータ群を与えるのに、ベクトルではなくリストを使ってみた。

見た目はベクトルの時と同じく、うまくいっているように見える。そこで各要素をチェックしてみると・・・

ちゃんと数値として扱われている。それでは新たにデータを追加しても大丈夫か。

問題なし。

よって、文字列と数値が混在する場合は、rbindの引数にはリストを使うべき。

 

R – 因子型(Factor型)

準備

以下のように文字列型のベクトルを準備。

因子型への変換

factor()関数を使って因子型へ返還。このとき因子の順番はシステムで自動的に割り振られ、ソートするとその順番で並べ替えられる。

因子を指定した要素の抽出

要素の抽出は因子のラベルを指定して行う。
因子のオーダでの指定はできない。

因子の順序の指定

factor()関数のlevels指定で明示的に因子の順序を指定できる。

因子の大小関係の指定

上記のベクトルyやzの各因子は順序関係を持っているが、それらは順序については特定できるが大小判定は行えない。

大小判定可能な値とするにはordered()関数を通す必要がある。

Factorベクトルの新規生成

一つ目の要素を定義する場合

通常紹介されている方法は、文字列型などのベクトルがあらかじめ準備されていて、それをFactor型に変換するというもの。

一つ目の要素を定義して、以降付け足していきたい場合は以下のようにする。

またlevelsを指定しないと、初期値以外はNAとなって警告が出る。

またlevelsで指定した以外のラベルを指定するとNAとなり、最初の要素は無視され、その後追加しようとした要素はNAとして保存される。

要素数ゼロから定義する場合

最初からFactor型のベクトルを定義してゼロから要素を追加するには、以下のようにするとよい。

下記の例では、factor()関数の第1引数に仮のベクトルをnumeric(0)で与えているが、character(0)でも同じ結果となり、単なるプレースホルダとなっているらしい。

データフレーム生成の場合の因子化について

データフレーム生成時に、文字列型ベクトルが自動的に因子型に変換される。これを抑止する方法についてはこちらを参照。

 

R – 繰り返し処理

for

構文は

最も基本的な例は連番のリストによる回数指定。

繰り返し内容が複数にわたる場合は{}で囲む。

break文で最も内側のループから抜ける。

next文はループ内の残りの部分を実行せず次のループへ。

ループ範囲に変数を使った場合、その変数は中で参照・変更可能だが、ループ回数そのものは変更されない。

以下の例では、xで指定された1:5はforループ開始時点でディープコピーされ、ループの中でxが変更されても、繰り返し回数は変化していない。

while

他の言語と同じ。

break文やnext文も使える。

repeat

repeatで無限ループを実行。