HOME | Python | download | 書き込む |
Python は関数型、オブジェクト指向などのいろいろなパラダイムを使うことができます。そのため、
言語からの制約がほとんどなく、自由にプログラムをデザインすることができます。
そこで、Scheme 版と同様に、演算子をデータとして扱い、
個々の演算子と、計算手順を完全に分離するデザインを採用します。
このデザインでは、演算子を追加してもロジックに変更を加える必要が無いので、電卓プログラムの内部設計として
優れていると思います。
また、Python の優れた点として、
[code 1] (calc.py)
001: #!/usr/bin/env python 002: # -*- coding:euc-jp -*- 003: 004: u''' 005: Python で書いた関数電卓プログラムです。 006: 動的型付けと関数オブジェクトを使うと簡潔に書けます。 007: ''' 008: 009: 010: import math, re, sys, operator 011: 012: 013: 014: 015: 016: class Operator: 017: u'''演算子と定数のクラスです''' 018: 019: # 演算子の優先度。大きい方から優先的に計算される 020: P_CST=6 # 定数の優先度 021: P_LHB=5 # ! の優先度 022: P_FUN=4 # 関数の優先度 023: P_POW=3 # 累乗の優先度 024: P_UPM=2 # 単項の + と - の優先度 025: P_MD=1 # *, /, % の優先度 026: P_BPM=0 # 2項の +, - の優先度 027: 028: 029: def __init__(self, name, fun, priority): 030: self.name=name 031: self.fun=fun 032: self.priority=priority 033: 034: def __call__(self, x=None, y=None): 035: return self.fun if self.is_const() else \ 036: self.fun(x) if self.is_unary() or self.is_lhb() else \ 037: self.fun(x, y) 038: 039: def __gt__(self, other): 040: u'''self が other より優先度が高いとき True を返す''' 041: assert other is None or isinstance(other, Operator) 042: return other is None or \ 043: self.priority > other.priority or \ 044: ( self.priority in (Operator.P_FUN, Operator.P_POW, Operator.P_UPM) and self.priority == other.priority) 045: 046: def __str__(self): 047: return self.name 048: 049: def __repr__(self): 050: return repr(self.name) 051: 052: def is_const(self): 053: return self.priority==Operator.P_CST 054: 055: def is_lhb(self): 056: return self.priority==Operator.P_LHB 057: 058: def is_upm(self): 059: return self.priority==Operator.P_UPM 060: 061: def is_func(self): 062: return self.priority==Operator.P_FUN 063: 064: def is_unary(self): 065: return self.priority in (Operator.P_FUN, Operator.P_UPM) 066: 067: def is_binary(self): 068: return self.priority in (Operator.P_POW, Operator.P_MD, Operator.P_BPM) 069: 070: 071: 072: # 関数 073: 074: def is_natural(n): 075: u'''自然数?''' 076: return type(n) is int and n>=0 077: 078: 079: 080: def fact(n): 081: u'''階乗''' 082: assert is_natural(n) 083: return reduce(operator.__mul__, range(1, n+1)) if n>0 else 1 084: 085: 086: 087: def permutation(m,n): 088: u'''順列''' 089: assert is_natural(m) and is_natural(n) and m>=n 090: return reduce(operator.__mul__, range(m-n+1, m+1), 1) 091: 092: 093: 094: def combination(m,n): 095: u'''組み合わせ''' 096: assert is_natural(m) and is_natural(n) and m>=n 097: return permutation(m,n)/fact(n) 098: 099: 100: 101: 102: 103: # 演算子と定数の辞書。 104: # 文字列をキーとし、対応する関数と優先度のタプル または 対応する定数を値にとる。 105: L_OP= [ \ 106: Operator('@+', operator.__pos__, Operator.P_UPM), \ 107: Operator('@-', operator.__neg__, Operator.P_UPM), \ 108: Operator('+', operator.__add__, Operator.P_BPM),\ 109: Operator('-', operator.__sub__, Operator.P_BPM), \ 110: Operator('*', operator.__mul__, Operator.P_MD),\ 111: Operator('/', operator.__truediv__, Operator.P_MD), \ 112: Operator('%', operator.__mod__, Operator.P_MD), \ 113: Operator('<<', operator.__lshift__, Operator.P_POW), \ 114: Operator('>>', operator.__rshift__, Operator.P_POW), \ 115: Operator('**', math.pow, Operator.P_POW), \ 116: Operator('^', math.pow, Operator.P_POW), \ 117: Operator('exp', math.exp, Operator.P_FUN), \ 118: Operator('log', math.log, Operator.P_FUN), \ 119: Operator('log10', math.log10, Operator.P_FUN), \ 120: Operator('sqrt', math.sqrt, Operator.P_FUN), \ 121: Operator('abs', operator.__abs__, Operator.P_FUN), \ 122: Operator('sin', math.sin, Operator.P_FUN), \ 123: Operator('cos', math.cos, Operator.P_FUN), \ 124: Operator('tan', math.tan, Operator.P_FUN), \ 125: Operator('asin', math.asin, Operator.P_FUN), \ 126: Operator('acos', math.acos, Operator.P_FUN), \ 127: Operator('atan', math.atan, Operator.P_FUN), \ 128: Operator('!', fact, Operator.P_LHB), \ 129: Operator('P', permutation, Operator.P_POW), \ 130: Operator('C', combination, Operator.P_POW), \ 131: Operator('pi', math.pi, Operator.P_CST), \ 132: Operator('e', math.e, Operator.P_CST), \ 133: ] 134: 135: # 演算子の名前をキーにしてハッシュ表を作る 136: H_OP=dict([(str(op), op) for op in L_OP]) 137: 138: 139: def convert_op_name(op): 140: u'''演算子を正規表現文字列に変換して返す''' 141: 142: return ''.join([(c if c.isalnum() else '\\'+c) for c in str(op)]) + \ 143: (r'(?=\W|$)' if op.is_const() else r'(?=[\s\(])' if op.is_func() else '') 144: 145: 146: 147: # 式の要素を表す正規表現 148: RE_FORM = re.compile( \ 149: r'''(?P<nest>\() | # 入れ子 150: (?P<num>\d+(?P<after_dot>\.\d+)?(?:[eE][+-]?\d+)?) | # 数値 151: (?P<op_name>%s) # 演算子 152: ''' % ('|'.join([ convert_op_name(op) for op in sorted([op for op in L_OP if not op.is_upm()], key=lambda x:len(str(x)), reverse=True)]),), \ 153: re.VERBOSE) 154: 155: 156: 157: 158: # ツール 159: def cons(obj, ls): 160: u'''Lisp 風にするため''' 161: ls.append(obj) 162: return ls 163: 164: 165: 166: def operator_position (ls): 167: u'''ls の中で最初に計算する演算子の位置を返す''' 168: 169: tprev, term0, pos = None, None, -1 170: 171: for i, term in enumerate(ls): 172: if isinstance(term, Operator) and (term > term0 or (isinstance(tprev, Operator) and term.is_upm())): 173: term0, pos = term, i 174: tprev=term 175: 176: return term0, pos 177: 178: 179: 180: def eval_ls(ls): 181: u'''数値と演算子からなるリストを計算して結果を返す''' 182: 183: if operator.isNumberType(ls): return ls # 数値ならそれを返す 184: elif len(ls)==1: 185: i=ls[0] 186: return eval_ls(i() if isinstance(i, Operator) else i) # 要素が1つのリストならそれを取り出す 187: else: 188: op, pos =operator_position(ls) # 最初の使う演算子を探す 189: 190: if op.is_const(): # 定数 191: return eval_ls(cons(op(), ls[:pos]) + ls[pos+1:]) 192: 193: elif op.is_unary() and pos < len(ls)-1: # 単項演算子 194: return eval_ls(cons(op(eval_ls(ls[pos+1])), ls[0:pos]) + ls[pos+2:]) 195: 196: elif op.is_lhb() and pos>0: # ! 197: return eval_ls(cons(op(eval_ls(ls[pos-1])), ls[0:pos-1]) + ls[pos+1:]) 198: 199: elif op.is_binary() and 0 < pos < len(ls)-1: # 二項演算子 200: return eval_ls(cons(op( eval_ls(ls[pos-1]), eval_ls(ls[pos+1]) ), ls[0:pos-1]) + ls[pos+2:]) 201: 202: else: 203: raise RuntimeError, "invalid formmula: (%r)" % (ls,) 204: 205: 206: 207: def find_pair(s0): 208: u'''対応する閉じ括弧を探す''' 209: n=0 210: for i, c in enumerate(s0): 211: if c in '()': n+= (1 if c=='(' else -1) 212: if n==0: return i 213: else: 214: raise RuntimeError, "Cannot find the close parenthesis!" 215: 216: 217: 218: def read_str(str): 219: u'''文字列を読み込んで、数値と演算子のリストを返す''' 220: def _iter(s, ls): 221: s=s.strip() 222: if(s): 223: obj=RE_FORM.match(s) 224: if obj and obj.group('nest'): 225: idx=find_pair(s) 226: return _iter(s[idx+1:], cons(_iter(s[1:idx], []), ls)) 227: elif obj: 228: s1=s[obj.end():] 229: if obj.group('num'): 230: return _iter(s1, cons((float if obj.group('after_dot') else int)(obj.group('num')), ls)) 231: else: 232: op_name = obj.group('op_name') 233: if op_name in '+-' and (not ls or (isinstance(ls[-1], Operator) and not ls[-1].is_lhb())): # 単項の +/- を識別する 234: op_name = '@' + op_name 235: return _iter(s1, cons(H_OP[op_name], ls)) 236: else: 237: raise RuntimeError, "Cannot parse input!" 238: else: 239: return ls 240: 241: return _iter(str, []) 242: 243: 244: 245: if __name__=='__main__': 246: 247: interactive= len(sys.argv)>1 and sys.argv[1]=='-i' 248: if interactive: 249: sys.stderr.write("Available operators and constants:\n" + \ 250: ', '.join([str(op) for op in L_OP if not op.is_upm()]) + \ 251: "\nq:quit\n\n") 252: 253: while(1): 254: if interactive: sys.stderr.write('> ') 255: str=sys.stdin.readline() 256: if str: 257: str=str.rstrip() 258: if interactive and str=='q': break 259: try: 260: print eval_ls(read_str(str)) 261: except Exception, strerror: 262: print strerror 263: else: 264: break 265: 266:
$ python calc.py -i Available operators and constants: +, -, *, /, %, <<, >>, P, C, **, ^, exp, log, log10, sqrt, abs, sin, cos, tan, sin, acos, atan, !, pi, e q:quit > 4!*7+3 171 > 64>>2 16 > 3<<4 48 > sqrt sin (pi/3) 0.930604859102 > 1233333333333333333333333333333333 * 2222222222222222244444444444444 2740740740740740768148148148147599259259259259259251851851851852 > log exp 40 40.0 > q $次のようにパイプ処理することもできます。
python calc.py < input.txt > output.txt
定数名 | 値 | 説明 |
---|---|---|
P_CST | 6 | 定数の優先度 |
P_LHB | 5 | ! の優先度 |
P_FUN | 4 | 算術関数の優先度 |
P_POW | 3 | 累乗の優先度 |
P_UPM | 2 | 単項の + と - の優先度 |
P_MD | 1 | *, /, % の優先度 |
P_BPM | 0 | 2項の +, - の優先度 |
単項の +/- と二項の +/- を区別するため、 単項の方の名前を "@+", "@-" にします。
Python はいろいろなパラダイムがつかえ、正規表現などのライブラリやエラー処理が充実しています。 そのため簡潔にプログラムを書くことができます。 また、特殊メソッドを用いて演算子をオーバーロードすることによって 直感的にわかりやすいコードになります。
HOME | Python | download | 書き込む |