听说 Ruby 支持分数字面量呢——
irb(main):001:0> 1/3r => (1/3) irb(main):002:0> 1/3r+1/2r => (5/6)
可是在 Python 里只能这样子:
>>> from fractions import Fraction as R >>> R(1, 3) Fraction(1, 3) >>> R(1, 3) + R(1, 2) Fraction(5, 6)
学习《用 `accio` 代替 `import`:深入理解自举和 Python 语法》改改 Python 解释器,让它也支持分数字面量,应该会很有趣呢 =w=
去翻了一下 Grammar/Grammar
文件,有些失望。那里只有语法定义,没有词法定义呢。以为词法定义在另一个地方,所以找了找,无果。只有 C 代码。想到复数的 j
字尾,直接拿 ag 搜带引号的 "j"
和 'j'
,最终确认它在 Parser/tokenizer.c
里。也就是说,Python 的词法分析是自己实现的。
在那个四千多行的tok_get
函数里,有一部分代码在尝试解析一个数,也就是语法里的 NUMBER
。照着解析复数的办法,把 d
后缀和 r
后缀也加进去:
diff -r bf65e7db066d Parser/tokenizer.c --- a/Parser/tokenizer.c Mon Apr 14 22:27:27 2014 -0400 +++ b/Parser/tokenizer.c Fri May 30 20:12:07 2014 +0800 @@ -1528,6 +1528,10 @@ goto fraction; if (c == 'j' || c == 'J') goto imaginary; + if (c == 'd' || c == 'D') + goto decimal; + if (c == 'r' || c == 'R') + goto rational; if (c == 'x' || c == 'X') { /* Hex */ @@ -1621,6 +1625,12 @@ /* Imaginary part */ imaginary: c = tok_nextc(tok); + else if (c == 'd' || c == 'D') + decimal: + c = tok_nextc(tok); + else if (c == 'r' || c == 'R') + rational: + c = tok_nextc(tok); } } tok_backup(tok, c);
d
后缀是我给十进制数——就是会计里会用到的精确的十进制小数——准备的。
然后可以编译出来试试。这个 configure 命令是从 Arch 官方编译脚本里找的。
./configure --enable-shared --with-threads --with-computed-gotos --enable-ipv6 --with-valgrind --with-system-expat --with-dbmliborder=gdbm:ndbm --with-system-ffi --with-system-libmpdec --without-ensurepip make
因为我不执行安装步骤,而又用了共享库,所以要这样子执行:
LD_LIBRARY_PATH=. ./python
试试看:
>>> 4d ValueError: could not convert string to float: 4d
有效果,不报语法错了呢。
现在报ValueError
,因为我还没告诉 Python 如何解析我新加的字面量表示呢。解析代码位于Python/ast.c
的parsenumber
函数。最终的补丁如下:
diff -r bf65e7db066d Python/ast.c --- a/Python/ast.c Mon Apr 14 22:27:27 2014 -0400 +++ b/Python/ast.c Fri May 30 20:12:07 2014 +0800 @@ -3650,12 +3650,29 @@ long x; double dx; Py_complex compl; - int imflag; + char typeflag; + PyObject *mod, *type, *ret; assert(s != NULL); errno = 0; end = s + strlen(s) - 1; - imflag = *end == 'j' || *end == 'J'; + switch(*end){ + case 'j': + case 'J': + typeflag = 'j'; + break; + case 'd': + case 'D': + typeflag = 'd'; + break; + case 'r': + case 'R': + typeflag = 'r'; + break; + default: + typeflag = 'i'; + } + if (s[0] == '0') { x = (long) PyOS_strtoul(s, (char **)&end, 0); if (x < 0 && errno == 0) { @@ -3670,13 +3687,43 @@ return PyLong_FromLong(x); } /* XXX Huge floats may silently fail */ - if (imflag) { + if (typeflag == 'j') { compl.real = 0.; compl.imag = PyOS_string_to_double(s, (char **)&end, NULL); if (compl.imag == -1.0 && PyErr_Occurred()) return NULL; return PyComplex_FromCComplex(compl); } + else if (typeflag == 'd') { + mod = PyImport_ImportModule("decimal"); + if (mod == NULL) + return NULL; + + type = PyObject_GetAttrString(mod, "Decimal"); + if (type == NULL) { + Py_DECREF(mod); + return NULL; + } + ret = PyObject_CallFunction(type, "s#", s, strlen(s)-1); + Py_DECREF(type); + Py_DECREF(mod); + return ret; + } + else if (typeflag == 'r') { + mod = PyImport_ImportModule("fractions"); + if (mod == NULL) + return NULL; + + type = PyObject_GetAttrString(mod, "Fraction"); + if (type == NULL) { + Py_DECREF(mod); + return NULL; + } + ret = PyObject_CallFunction(type, "s#", s, strlen(s)-1); + Py_DECREF(type); + Py_DECREF(mod); + return ret; + } else { dx = PyOS_string_to_double(s, NULL, NULL);
因为只是玩玩,所以不太认真,没仔细做错误处理;因为decimal
和fractions
模块是从外部文件导入的,所以可能被覆盖掉,从而导致报错,并且这错误是无法通过异常处理捕获的。
不出问题的话,再次make
之后,就可以开始玩了。不过在此之前,再多做几个补丁,让 Python 把分数和十进制数显示得简洁好看一点:
diff -r bf65e7db066d Lib/decimal.py --- a/Lib/decimal.py Mon Apr 14 22:27:27 2014 -0400 +++ b/Lib/decimal.py Fri May 30 20:12:07 2014 +0800 @@ -1015,7 +1015,7 @@ def __repr__(self): """Represents the number as an instance of Decimal.""" # Invariant: eval(repr(d)) == d - return "Decimal('%s')" % str(self) + return str(self) + 'd' def __str__(self, eng=False, context=None): """Return string representation of the number in scientific notation. diff -r bf65e7db066d Lib/fractions.py --- a/Lib/fractions.py Mon Apr 14 22:27:27 2014 -0400 +++ b/Lib/fractions.py Fri May 30 20:12:07 2014 +0800 @@ -280,7 +280,7 @@ def __repr__(self): """repr(self)""" - return ('Fraction(%s, %s)' % (self._numerator, self._denominator)) + return str(self) + 'r' def __str__(self): """str(self)""" diff -r bf65e7db066d Modules/_decimal/_decimal.c --- a/Modules/_decimal/_decimal.c Mon Apr 14 22:27:27 2014 -0400 +++ b/Modules/_decimal/_decimal.c Fri May 30 20:12:07 2014 +0800 @@ -3092,18 +3092,10 @@ static PyObject * dec_repr(PyObject *dec) { - PyObject *res, *context; - char *cp; - - CURRENT_CONTEXT(context); - cp = mpd_to_sci(MPD(dec), CtxCaps(context)); - if (cp == NULL) { - PyErr_NoMemory(); - return NULL; - } - - res = PyUnicode_FromFormat("Decimal('%s')", cp); - mpd_free(cp); + PyObject *res, *str; + str = dec_str(dec); + res = PyUnicode_FromFormat("%Ud", str); + Py_DECREF(str); return res; }
下面是最终成果啦:
>>> 0.1 + 0.2 == 0.3 False >>> 0.1d + 0.2d == 0.3d True >>> 1/3r + 1/2r 5/6r >>> 0.4/1.2r 0.33333333333333337 >>> 0.4r/1.2r 1/3r
可以看到,与复数类似,分数字面量其实包含了一次除法。所以如果分子写浮点数的话,最终结果是会被转成浮点数的呢。这个和 Ruby 的行为是一样的 =w=