Pythonで読み込み専用フィールドを作る
読み込み専用のフィールドをPythonを作るとき、propertyを使う方法があると思います。しかし単純にpropertyを使うだけだと、フィールドが変更することは不可能ではありません。
def test_property(self): class A(object): def __init__(self, i): self.i = i def get_i(self): return self.i def set_i(self, i): self.i = i x = property(get_i) a = A(2) self.assertEqual(2, a.i) self.assertEqual(2, a.x) def f(a): a.x = 3 self.assertRaises(AttributeError, f, a) a.set_i(3) self.assertEqual(3, a.i) self.assertEqual(3, a.x)
本当の意味で読み込み専用にする方法としては、tupleを継承する方法があります。この方法はjinja2のlexer.Tokenクラスの実装で実際に使われている方法です。
import operator class Token(tuple): type, value = (property(operator.itemgetter(x)) for x in range(2)) def __new__(cls, type, value): return tuple.__new__(cls, (type, value))
Tokenは次のテストをパスします。Class Aのiフィールドのような、余計なものがないので、変数をセットすることも不可能です。
#!/usr/bin/env python2.6 import unittest from test import test_support from MY_jinja2.lexer import * class LexerTests(unittest.TestCase): def test_Token(self): data = u'hello, world' token = Token('data', data) self.assertEqual('data', token.type) self.assertEqual(data, token.value) def test_Token_is_immutable(self): data = u'hello, world' token = Token('data', data) def f_value(token): token.value = 'value' def f_type(token): token.type = 'type' self.assertRaises(AttributeError, f_value, token) self.assertRaises(AttributeError, f_type, token) if __name__ == '__main__': test_support.run_unittest(LexerTests)
なお、operator.itemgetterの動作については次のようなものです。
def test_itemgetter(self): f = operator.itemgetter(3) l = range(10) self.assertEqual( l[3], f(l))