sympy是如何工作的?它如何与交互式Python shell互动?交互式Python shell又是如何工作的?
当我按下 Enter 键时,内部发生了什么?
我之所以问这个问题,除了出于好奇,还有一个原因是想弄清楚当你
from sympy import *
并输入一个表达式时,背后到底发生了什么。它是如何从按下 Enter 键转变为调用
__sympifyit_wrapper(a,b)
在 sympy.core.decorators 中的内容的?(这是我在尝试检查一个计算时,winpdb 带我去的第一个地方。)我猜想可能有一个内置的 eval 函数会被正常调用,而在你导入 sympy 时,这个函数会被覆盖?
4 个回答
我刚刚查看了sympy的代码(在http://github.com/sympy/sympy上),发现__sympifyit_wrapper
是一个装饰器。它会被调用的原因是因为某处有段代码看起来像这样:
class Foo(object):
@_sympifyit
def func(self):
pass
而__sympifyit_wrapper
是由@_sympifyit
返回的一个包装器。如果你继续调试,可能会找到这个函数(在我的例子中叫func
)。
我了解到在sympy/__init__.py
中导入的许多模块和包里,有些内置的代码被sympy的版本替代了。这些sympy版本可能使用了那个装饰器。
使用exec
时,像>>>
这样的操作不会被替换,但操作的对象会被替换。
这段内容其实和secondbanana的真正问题关系不大,只是想回应一下Omnifarious的悬赏而已;)
解释器本身其实很简单。事实上,你自己也可以写一个简单的(虽然远不完美,也不处理异常等等)解释器:
print "Wayne's Python Prompt"
def getline(prompt):
return raw_input(prompt).rstrip()
myinput = ''
while myinput.lower() not in ('exit()', 'q', 'quit'):
myinput = getline('>>> ')
if myinput:
while myinput[-1] in (':', '\\', ','):
myinput += '\n' + getline('... ')
exec(myinput)
在普通的命令行中,你可以做大部分你习惯的事情:
Waynes Python Prompt
>>> print 'hi'
hi
>>> def foo():
... print 3
>>> foo()
3
>>> from dis import dis
>>> dis(foo)
2 0 LOAD_CONST 1 (3)
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 0 (None)
8 RETURN_VALUE
>>> quit
Hit any key to close this window...
真正的“魔法”发生在词法分析器和解析器中。
词法分析,或者说lexing,就是把输入的内容分解成一个个小的“标记”。这些标记是关键字或者是“不可分割”的元素。例如,=
、if
、try
、:
、for
、pass
和import
都是Python的标记。想要看看Python是如何对程序进行标记的,可以使用tokenize
模块。
把一些代码放到一个名为'test.py'的文件里,然后在这个目录下运行以下代码:
from tokenize import tokenize
f = open('test.py')
tokenize(f.readline)
对于print "Hello World!"
这行代码,你会得到以下结果:
1,0-1,5: NAME 'print'
1,6-1,19: STRING '"hello world"'
1,19-1,20: NEWLINE '\n'
2,0-2,0: ENDMARKER ''
一旦代码被标记后,就会被解析成一个抽象语法树。最终结果是你程序的Python字节码表示。对于print "Hello World!"
,你可以看到这个过程的结果:
from dis import dis
def heyworld():
print "Hello World!"
dis(heyworld)
当然,所有编程语言都会进行词法分析、解析、编译,然后执行它们的程序。Python会进行词法分析、解析,并编译成字节码。然后,这个字节码会被“编译”(翻译可能更准确)成机器代码,最后执行。这就是解释型语言和编译型语言之间的主要区别——编译型语言是直接从原始源代码编译成机器代码,这意味着你只需要在编译前进行词法分析和解析,然后就可以直接执行程序。这意味着执行速度更快(没有词法分析和解析的阶段),但也意味着为了达到初始的执行速度,你需要花费更多的时间,因为整个程序必须被编译。
好吧,经过一番尝试,我觉得我明白了。当我最初提问时,我并不知道有运算符重载这个概念。
那么,在这个 Python 会话中发生了什么呢?
>>> from sympy import *
>>> x = Symbol(x)
>>> x + x
2*x
其实,解释器评估这个表达式的方式并没有什么特别的;重要的是 Python 会把
x + x
转换成
x.__add__(x)
而且 Symbol 类是从 Basic 类继承而来的,Basic 类定义了 __add__(self, other)
方法,返回 Add(self, other)
。如果你想看看,这些类可以在 sympy.core.symbol、sympy.core.basic 和 sympy.core.add 中找到。
正如 Jerub 所说,Symbol.__add__()
有一个叫做 _sympifyit
的装饰器,它基本上是在评估函数之前把函数的第二个参数转换成一个 sympy 表达式,过程中返回一个叫 __sympifyit_wrapper
的函数,这就是我之前看到的。
用对象来定义操作是个很酷的概念;通过定义自己的运算符和字符串表示,你可以很轻松地实现一个简单的符号代数系统:
symbolic.py --
class Symbol(object):
def __init__(self, name):
self.name = name
def __add__(self, other):
return Add(self, other)
def __repr__(self):
return self.name
class Add(object):
def __init__(self, left, right):
self.left = left
self.right = right
def __repr__(self):
return self.left + '+' + self.right
现在我们可以做:
>>> from symbolic import *
>>> x = Symbol('x')
>>> x+x
x+x
稍微重构一下,就可以很容易地扩展到处理所有基本算术运算:
class Basic(object):
def __add__(self, other):
return Add(self, other)
def __radd__(self, other): # if other hasn't implemented __add__() for Symbols
return Add(other, self)
def __mul__(self, other):
return Mul(self, other)
def __rmul__(self, other):
return Mul(other, self)
# ...
class Symbol(Basic):
def __init__(self, name):
self.name = name
def __repr__(self):
return self.name
class Operator(Basic):
def __init__(self, symbol, left, right):
self.symbol = symbol
self.left = left
self.right = right
def __repr__(self):
return '{0}{1}{2}'.format(self.left, self.symbol, self.right)
class Add(Operator):
def __init__(self, left, right):
self.left = left
self.right = right
Operator.__init__(self, '+', left, right)
class Mul(Operator):
def __init__(self, left, right):
self.left = left
self.right = right
Operator.__init__(self, '*', left, right)
# ...
再稍微调整一下,我们就能得到和一开始的 sympy 会话相同的行为……我们会修改 Add
,让它在参数相等时返回一个 Mul
实例。这有点棘手,因为我们必须在实例创建之前处理这个问题;我们需要使用__new__()
而不是 __init__()
:
class Add(Operator):
def __new__(cls, left, right):
if left == right:
return Mul(2, left)
return Operator.__new__(cls)
...
别忘了为 Symbols 实现等于运算符:
class Symbol(Basic):
...
def __eq__(self, other):
if type(self) == type(other):
return repr(self) == repr(other)
else:
return False
...
好了。总之,你可以想出各种其他的实现,比如运算符优先级、带替换的评估、高级简化、微分等等,但我觉得基础部分这么简单真是太酷了。