プロパティについて理解したのでまとめる

プロパティについて

研究室で質問されたのでPythonのプロパティについて解説しようと思う。

最初にプロパティとはなにかをIT用語辞典で引いていみると、

プロパティとは、オブジェクト指向プログラミングで使用される オブジェクトが保持している、そのオブジェクトの性質を表すデータ。 例えば、画像データのオブジェクトならば、高さや幅などのデータを プロパティとして持っている。

という定義になっている。 つまり、オブジェクト指向プログラミングでクラスのインスタンスから アクセスできるimg.widthとかimg.heightとかの、widthheightのことである。

しかし、カプセル化の概念からすると、直接プロパティをいじれてしまうのは良くない。 そこで、オブジェクト指向プログラミングでは以下のコードのような アクセサというメソッドとアクセス修飾子を使って、プロパティが 不用意にいじくりまわされるのを防いでいる。以下の例で言うと、 getWidthgetHeightsetWidthsetHeightがアクセサである。

class Image {
    private int width;
    private int height;

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

}

class Spam {
    public static void main(String args[]) {
        Image img = new Image();
        img.setWidth(200);
        img.setHeight(100);
        System.out.println(img.getWidth());
        System.out.println(img.getHeight());
    }
}

JavaC++などではこのようにしてプロパティへアクセス制御をしているが、 ただアクセスを制御したいだけなら、確かに冗長な感じはする。

PythonC#ではプロパティをインスタンス(使う側)からはメンバ変数に見えるが、 クラス(実装)ではコードブロック(メソッド)のようにして制御を行う。

具体的な例をPythonで示してみる。 以下のコードがプロパティを利用してアクセス制御を行っているコードになる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
class Image(object):
    def __init__(self):
        self._width = 300
        self._height = 400

    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, width):
        self._width = width

    @property
    def height(self):
        return self._height

    @height.setter
    def height(self, height):
        self._height = height

if __name__ == '__main__':
    img = Image()
    img.width = 200
    img.height = 100
    print img.width
    print img.height

クラスのメンバである_width_heightに対し、デコレータを利用してwidthメソッドや heightメソッドをproperty型に一度変換している(デコレータに関しては 前の記事を参照)。

propertyの第一引数がgetterになっているため、property型でデコレートした メソッドでは、そのプロパティが返したい値を返している。 また、Pythonのプロパティはsetterメソッドを持っているため、そのsetterメソッドで メソッドをデコレートすることで、img.width = 200のような形でアクセスできるように なっている。

今回の例では代入と読み出しの両方を行っていたが、例えば、 読み出しは許可するが、代入を禁止したい場合、以下のようにsetterメソッドが 呼び出されたら例外を投げるなどをして、プロパティへのアクセス制御が行える。

class Image(object):
    ~~~
    @width.setter
    def width(self, width):
        raise ValueError()
    ~~~

このようにプロパティを制御することで、クラスの設計者が意図しない値の代入や、 読み出し、不用意なプロパティの削除を防ぐことができる。

デコレータの基本について理解したのでまとめる

Pythonのデコレータに関して、理解していることをまとめてみる。

@を使ったデコレータの書き方はただのシンタックスシュガー (読み書きのしやすさのために導入される構文)。

それでは、何に対するシンタックスシュガーなのか。

まず、以下のようなプログラムがあるとする。

def egg():
    return "egg"

if __name__ == '__main__':
    print(egg()) # egg

実行すると、当たり前だが以下の様な結果になる。

egg

次に、先ほどのファイルにscrambleというメソッドを追加する。

def scramble(egg):
    def _scramble():
        return "scramble " + egg() + "!"
    return _scramble

def egg():
    return "egg"

if __name__ == '__main__':
    scramble_egg = scramble(egg)
    print(scramble_egg()) # scramble egg!

scrambleメソッドは、オブジェクトを1つ受け取り、内部に定義されている_scrambleメソッドを返している。 つまり、scrambleメソッド自体は引数に対して何かを行うことはなく、 ただ内部のメソッドを返しているだけの機能しか持っていない。

返される内部メソッドの_scrambleは、scrambleメソッドが受け取ったの返り値に、 "scramble "と"!"という文字列を装飾(デコレート)して返している。

if __name__ == '__main__'では、scrambleメソッドにeggメソッド渡し、 返り値をscramble_eggという変数に格納している。 scrambleメソッドの返り値は_scrambleのため、scramble_egg変数は _scrambleメソッドの内容と同じ挙動をする。

そのため、ファイルを実行すると以下の様な実行結果になる。

scramble egg!

さて、if __name__ == '__main__'の中ではscrambleメソッドの返り値はscramble_eggという変数に格納していた。 ここで、すでに定義されているeggメソッドにscrambleの返り値を格納してみる。 Pythonはメソッドも変数も全てがオブジェクトのため、こういうことができる。

def scramble(egg):
    def _scramble():
        return "scramble " + egg() + "!"
    return _scramble

def egg():
    return "egg"

if __name__ == '__main__':
    egg = scramble(egg) # この部分と
    print(egg())        # この部分

実行結果は上記と同じscramble egg!

eggに対してscrambleメソッドの返り値を格納しているため、eggメソッドは 元の「"egg"という文字列を返す」という機能が上書きされている。 しかし、ただ上書きされたわけではなく、scrambleメソッドに元の機能を引数として渡しているため、 「"egg"という文字列を返す」機能は、_scramble内で使われることになる。

この状態におけるscrambleメソッドがデコレータとなる。 文字通り別のメソッドを装飾することができる。

これまでの手順を@を使って書き換えると、以下のようなコードになる。

def scramble(egg):
    def _scramble():
        return "scramble " + egg() + "!"
    return _scramble

@scramble
def egg():
    return "egg"

if __name__ == '__main__':
    print(egg()) # scramble egg!

Webサーバをapacheからnginxに移行

今までapacheで動かしていたWebサーバをnginxにようやく切り替えた。 その中でしたnginxの設定とか詰まったところとかメモ。

nginxの設定

全体に関わる設定

http://www.nametake.infoのように、特に設定をしていないサブドメインに アクセスがあっても、全部nametake.infoのページと同じ内容が表示されるようにしたい。 ということで、以下のようにdefault.confファイルを書いて、デフォルトのバーチャルホストを設定する。 また、server_name_にすることで、catch-allサーバとして設定。

default.conf
server {
    listen          80 default_server;
    server_name     _;
    root            /var/www/html;
    ~~~略~~~
}

あとは、nginxのバージョン番号とかOSの情報をレスポンスヘッダに含めたくないので、以下をnginx.confに追記。

nginx.conf
http {
    ~~~略~~~
    server_tokens off;
}

SSLに関わる設定

やりたかったこと

  • nametake.infoサブドメインで動かしているownCloudとかampacheの通信経路を暗号化したい
  • 設定ファイルはサービスごとに分割したい
  • owncloud.confとかampache.confとかに証明書の情報をいちいち書きたくない

やったこと

以下のようにSSL全体の設定も記述(と言ってもほとんどexample_ssl.confのまんま)。 サーバ証明書秘密鍵は、apacheを設定した時に作成したオレオレ証明書があるので、それをそのまま流用。

変更したのはlistenの部分のdefault_serverと、server_nameの部分のバーチャルホスト。

ssl.conf
server {
    listen       443 ssl default_server;
    server_name  .nametake.info;

    ssl_certificate         /my/cert/file;
    ssl_certificate_key     /my/cert/key/file;

    ~~~略~~~
}

上記のようにssl.confとしてSSL全体の設定ファイルを作成することで、それぞれのサービスで SSLを使用するときは、以下のように記述するだけでOK。

owncloud.conf
server {
    # 80番ポートでアクセスしてきたら443番ポートにリダイレクト
    listen 80;
    server_name cloud.nametake.info;
    # enforce https
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name cloud.nametake.info;
    ~~~略~~~
}

SSL通信したいサーバでlisten 443 sslと書くだけ。これで個別のファイルに証明書の情報を書かなくてよくなる。

サービスごとの細かいnginxの設定は[nginx (サービス名)]とかで検索して出てきた結果をコピペ。

詰まったところ

ownCloudのログイン画面でリダイレクトループする

原因

/var/lib/php/session/パーミッションapache:apacheのままになっていた。

解決策

/var/lib/php/session/パーミッションnginx:nginxに変更。

こんなところのパーミッションの設定もapacheになっていたのは想定外だった…… (serverのrootに指定したディレクトリの所有者がapacheのままになっていたのに気付かなくて30分ぐらい悩んだのは内緒)

.gitignoreの範囲の指定方法

過去に書いた記事をそのまま移行します。

前提

$HOMEディレクトリ上でドットファイルをgitを管理している。 .gitignoreはホワイトリスト方式で記述している。

やりたかったこと

$HOME
└── .vim
     ├── bundle
     ├── snippet
     ├── syntax
     ├── template
     └── userautoload

上記の構造になっているvimの設定ファイルのbundleディレクトリ以外をgitの管理下に置きたい。 (bundleディレクトリだけはgitで管理したくない)

やったこと

ホワイトリスト方式で.gitignoreを設定したが、階層構造をとっている時の設定でつまずいた。

ダメだったパターン

以下のように記述したら$HOME/.vim/以下のディレクトリ内部のファイルが読み込まれなかった。

# all file ignore
/*
/.*

# target not ignore
!/.gitignore
!/.vimrc
!/.vim/template/template.*
!/.vim/userautoload/*.vim
!/.vim/snippet/*.vim
!/.vim/syntax/*.vim

よく考えると、上の3行ですべてのファイルとディレクトリを場外しているため、 $HOME/.vimディレクトリがそもそもgitから見えない。 そのため、!/.vim/userautoload/*.vimのようにファイルを指定しても、 !/存在しないディレクトリ/userautoload/*.vimとなっているので、 読み込まれることがなかった。

成功したパターン

以下のように記述したら成功した。

# all file ignore
/*
/.*

# target not ignore
!/.gitignore
!/.vimrc

# .vim directry
!/.vim/
/.vim/*

!/.vim/template/
!template.*

!/.vim/syntax/
!*.vim

!/.vim/snippet/
!*.vim

!/.vim/userautoload/
!*.vim

やっていることは、

  1. !/.vim/$HOME/.vim/ディレクトリをホワイトリストに追加
  2. /.vim/*$HOME/.vim/以下のファイルとディレクトリを除外
  3. !/.vim/userautoload/で該当のディレクトリをホワイトリストに追加
  4. !*.vim*.vimファイルをホワイトリストに追加

という感じ。

ただ、一つのファイルで管理すると見栄えが悪かったので以下のように分割。

# $HOME/.gitignore
# all file ignore
/*
/.*

# target not ignore
!/.gitignore
!/.vimrc
!/.vim/
# $HOME/.vim/.gitignore
# all file ignore
/*
/.*

!/.gitignore

# target not ignore
!/template/
!template.*

!/syntax/
!*.vim

!/snippet/
!*.vim

!/userautoload/
!*.vim

これで管理しやすくなった。