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