Как работает sympy? Как он взаимодействует с интерактивной оболочкой Python и как работает интерактивная оболочка Python?
что происходит внутри, когда я нажимаю Enter?
моя мотивация спрашивать, помимо простого любопытства, чтобы выяснить, что происходит, когда вы
from sympy import *
и введите выражение. Как же Enter вызов
__sympifyit_wrapper(a,b)
в sympy.ядро.декораторы? (Это первое место winpdb взял меня, когда я попытался проверить оценку.) Я бы предположил, что есть какая-то встроенная функция eval, которая обычно вызывается и переопределяется, когда вы импортируете sympy?
4 ответов
хорошо, поиграв с ним еще немного, я думаю, что он у меня есть.. когда я впервые задал вопрос, я не знал о перегрузка операторов.
Итак, что происходит в этой сессии python?
>>> from sympy import *
>>> x = Symbol(x)
>>> x + x
2*x
оказывается, нет ничего особенного в том, как интерпретатор оценивает выражение; важно то, что python переводится как
x + x
на
x.__add__(x)
и символ наследуется от базового класса, который определяет __add__(self, other)
вернуться Add(self, other)
. (Эти классы находятся в sympy.ядро.символ, sympy.ядро.базовые, и sympy.ядро.добавьте, если хотите взглянуть.)
так как 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)
...
не забудьте реализовать оператор равенства для Символы:
class Symbol(Basic):
...
def __eq__(self, other):
if type(self) == type(other):
return repr(self) == repr(other)
else:
return False
...
и вуаля. В любом случае, вы можете думать о всех других вещах для реализации, таких как приоритет оператора, оценка с подстановкой, расширенное упрощение, дифференциация и т. д., но я думаю, это довольно круто, что основы настолько просты.
это не имеет большого отношения к secondbanana реальные вопрос - это просто выстрел в Omnifarious ' bounty;)
сам переводчик довольно прост. На самом деле вы можете написать простой (нигде не идеальный, не обрабатывает исключения и т. д.) себя:
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...
настоящая магия происходит в лексер/парсер.
лексический анализ, или лексический разбивает входные данные на отдельные токены. Токены-это ключевые слова или" неделимые " элементы. Например, =
, if
, try
, :
, for
, pass
и import
все токены Python. Чтобы узнать, как питон маркирует программу можно использовать tokenize
модуль.
поместите некоторый код в файл под названием 'test.py-и запустите в этом каталоге следующее:
из tokenize импорт tokenize Ф= открыть('test.py') маркировки(Ф.с readline)
на print "Hello World!"
вы получите:
1,0-1,5: имя 'печать'
1,6-1,19: строка '"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)
конечно, все языки lex, разбор, компиляция, а затем выполнить свои программы. Python lexes, анализирует и компилирует байт-код. Затем байт-код "компилируется" (перевод может быть более точным) в машинный код, который затем выполняется. Это основное различие между интерпретируемыми и скомпилированными языками-скомпилированные языки компилируются непосредственно в машинный код из исходного источника, что означает, что вы нужно только lex / parse перед компиляцией, а затем вы можете напрямую выполнить программу. Это означает более быстрое время выполнения (без этапа lex/parse), но также означает, что для получения начального времени выполнения вам нужно потратить гораздо больше времени, потому что вся программа должна быть скомпилирована.
Я только что проверил код sympy (at http://github.com/sympy/sympy ) и это выглядит как __sympifyit_wrapper
- декоратор. Причина, по которой он будет вызван, заключается в том, что где-то есть код, который выглядит так:
class Foo(object):
@_sympifyit
def func(self):
pass
и __sympifyit_wrapper
- Это оболочка, возвращаемая @_sympifyit
. Если вы продолжили отладку, возможно, вы нашли функцию (в моем примере с именем func
).
Я собираю в одном из многих модулей и пакетов, импортированных в sympy/__init__.py
некоторые встроенный код заменяется на версии sympy. Эти симпатизирующие версии, вероятно, используют этого декоратора.
exec
как >>>
не будут заменены, объекты, которые эксплуатируются, будут.
интерактивный интерпретатор Python не делает много, что отличается от любого другого времени, когда код Python запускается. У него есть магия, чтобы ловить исключения и обнаруживать неполные многострочные операторы перед их выполнением, чтобы вы могли закончить их печатать, но это все.
Если вам действительно интересно, стандартный модуль код является довольно полной реализацией интерактивной подсказки Python. Я думаю, это не совсем то, что Python фактически использует (то есть, я считаю, реализован в C), но вы можете копаться в каталоге системной библиотеки Python и фактически смотреть, как это делается. Мой на /usr/lib/python2.5/code.py