Почему параметры по умолчанию оценены во время определения в Python?

Базовая проблема здесь - то, что алгоритмы стемминга работают <ударяют> на фонетической основе просто на основе правил написания языка без фактического понимания языка, с которым они работают. Для создания реальных слов необходимо будет, вероятно, объединить вывод стеммера с некоторой формой функции поиска для преобразования основ назад в реальные слова. Я могу в основном видеть два потенциальных способа сделать это:

  1. Определяют местоположение или создают большой словарь, который отображает каждую возможную основу назад на фактическое слово. (например, сообщество-> сообщество)
  2. Создают функцию, которая сравнивает каждую основу со списком слов, которые были уменьшены до той основы, и пытается определить, который является самым подобным. (например, выдерживая сравнение "communiti" против "сообщества" и "сообществ" таким способом, которым "сообщество" будет распознано как более подобная опция)

Лично, я думаю способ, которым я сделал бы это, будет динамическая форма № 1, создавая пользовательскую базу данных словаря путем записи каждого слова, исследованного наряду с тем, что это остановило к и затем предположив, что наиболее распространенное слово является тем, которое должно использоваться. (например, Если мое тело исходного текста использует "сообщества" чаще, чем "сообщество", то отобразите сообщество-> сообщества.) Основанный на словаре подход будет более точным в целом, и создание его на основе входа стеммера предоставит результаты, настроенные Вашим текстам с основным недостатком, являющимся требуемым пространством, который является обычно не проблемой в эти дни.

35
задан Kelly S. French 30 October 2009 в 17:53
поделиться

8 ответов

Альтернатива была бы довольно тяжелой - сохранение «значений аргументов по умолчанию» в объекте функции в виде «переходов» кода, которые будут выполняться снова и снова каждый раз, когда функция вызывается без указанного значение для этого аргумента - и будет намного сложнее получить раннее связывание (связывание в определенное время), что часто бывает именно тем, что вам нужно. Например, в Python в том виде, в каком он существует:

def ack(m, n, _memo={}):
  key = m, n
  if key not in _memo:
    if m==0: v = n + 1
    elif n==0: v = ack(m-1, 1)
    else: v = ack(m-1, ack(m, n-1))
    _memo[key] = v
  return _memo[key]

... написание мемоизированной функции, подобной приведенной выше, является довольно элементарной задачей. Аналогично:

for i in range(len(buttons)):
  buttons[i].onclick(lambda i=i: say('button %s', i))

... простой i = i , основанный на раннем связывании (времени определения) значений arg по умолчанию, является тривиально простым способом получить раннее связывание. Итак, текущее правило простое, понятное и позволяет вам делать все, что вы хотите, таким образом, который чрезвычайно легко объяснить и понять: если вам нужна поздняя привязка выражения s значение, вычислить это выражение в теле функции; если вы хотите раннее связывание, оцените его как значение аргумента по умолчанию.

Альтернатива, принудительное позднее связывание для обеих ситуаций, не будет предлагать такой гибкости и заставит вас пройти через обручи (например, обернуть вашу функцию в фабрика закрытия) каждый раз, когда вам нужно было раннее связывание, как в приведенных выше примерах - еще более тяжелый шаблон, навязанный программисту этим гипотетическим дизайнерским решением (помимо «невидимых» тех, которые генерируют и многократно оценивают преобразователи повсюду ).

Другими словами, «Должен быть один, а желательно только один, очевидный способ сделать это [1]»: когда вам нужно позднее связывание, уже существует совершенно очевидный способ его достижения (поскольку все код функции выполняется только во время вызова, очевидно, что все вычисленное там связано с поздним связыванием); то, что оценка default-arg производит раннее связывание, дает вам очевидный способ достижения раннего связывания (плюс! -), а не дает ДВА очевидных способа получить позднее связывание и не дает очевидного способа получить раннее связывание (минус! -).

[1]: «Хотя сначала этот путь может быть не очевиден, если вы не голландец».

37
ответ дан 27 November 2019 в 06:59
поделиться

Конечно, в вашей ситуации это сложно понять. Но вы должны видеть, что оценка аргументов по умолчанию каждый раз будет ложиться тяжелым бременем на систему.

Также вы должны знать, что в случае типов контейнеров эта проблема может возникнуть - но вы можете обойти ее, сделав это явный:

def __init__(self, children = None):
    if children is None:
       children = []
    self.children = children
7
ответ дан 27 November 2019 в 06:59
поделиться

Я тоже думал, что это нелогично, пока не узнал, как Python реализует аргументы по умолчанию.

Функция - это объект. Во время загрузки Python создает объект функции, оценивает значения по умолчанию в операторе def , помещает их в кортеж и добавляет этот кортеж как атрибут функции с именем func_defaults . Затем при вызове функции, если вызов не предоставляет значение, Python берет значение по умолчанию из func_defaults .

Например:

>>> class C():
        pass

>>> def f(x=C()):
        pass

>>> f.func_defaults
(<__main__.C instance at 0x0298D4B8>,)

Итак, все вызовы f , которые не предоставляют аргумент, будут использовать тот же экземпляр C , потому что это значение по умолчанию.

Что касается того, почему Python делает это таким образом: ну, этот кортеж мог содержать функции, которые вызывались бы каждый раз, когда требовалось значение аргумента по умолчанию. Помимо очевидной проблемы с производительностью, вы начинаете попадать во вселенную особых случаев, таких как сохранение буквальных значений вместо функций для неизменяемых типов, чтобы избежать ненужных вызовов функций. И, конечно, это в изобилии влияет на производительность.

Фактическое поведение действительно простое. И есть тривиальный обходной путь, если вы хотите, чтобы значение по умолчанию создавалось вызовом функции во время выполнения:

def f(x = None):
   if x == None:
      x = g()
5
ответ дан 27 November 2019 в 06:59
поделиться

Это происходит из-за того, что Python делает упор на синтаксис и простоту выполнения. оператор def возникает в определенный момент во время выполнения. Когда интерпретатор python достигает этой точки, он оценивает код в этой строке, а затем создает объект кода из тела функции, который будет запущен позже, когда вы вызовете функцию.

Это простое разделение между объявлением функции и телом функции. Объявление выполняется, когда оно достигается в коде. Тело выполняется во время вызова. Обратите внимание, что объявление выполняется каждый раз, когда оно достигается, поэтому вы можете создать несколько функций с помощью цикла.

funcs = []
for x in xrange(5):
    def foo(x=x, lst=[]):
        lst.append(x)
        return lst
    funcs.append(foo)
for func in funcs:
    print "1: ", func()
    print "2: ", func()

Было создано пять отдельных функций, с отдельным списком, создаваемым каждый раз при выполнении объявления функции. В каждом цикле через функции , одна и та же функция выполняется дважды при каждом проходе, каждый раз используя один и тот же список. Это дает результаты:

1:  [0]
2:  [0, 0]
1:  [1]
2:  [1, 1]
1:  [2]
2:  [2, 2]
1:  [3]
2:  [3, 3]
1:  [4]
2:  [4, 4]

Другие предложили вам обходной путь, используя param = None и назначив список в теле, если значение None, что является полностью идиоматическим питоном. Это немного некрасиво, но простота мощная, а обходной путь не слишком болезненный.

Отредактировано для добавления: Подробнее об этом см. Статью effbot здесь: http://effbot.org/zone/ default-values.htm и справочник по языку здесь: http://docs.python.org/reference/compound_stmts.html#function

4
ответ дан 27 November 2019 в 06:59
поделиться

Потому что, если бы это было так, то кто-то разместил бы вопрос, спрашивая, почему не было наоборот :-p

Предположим теперь, что у них было. Как бы вы при необходимости реализовали текущее поведение? Внутри функции легко создавать новые объекты, но вы не можете их «убрать» (вы можете удалить их, но это не то же самое).

0
ответ дан 27 November 2019 в 06:59
поделиться

Проблема в следующем.

Слишком дорого оценивать функцию в качестве инициализатора каждый раз, когда функция вызывается .

  • 0 - это простой буквальный. Оцените ее один раз, используйте ее вечно.

  • int - это функция (например, список), которую нужно будет вычислять каждый раз, когда она требуется в качестве инициализатора.

Конструкция [] является буквальной, как ] 0 , что означает «именно этот объект».

Проблема в том, что некоторые люди надеются, что это означает список , например «оцените эту функцию для меня, пожалуйста, чтобы получить объект это инициализатор ».

Было бы непросто добавить необходимый оператор if , чтобы выполнять эту оценку все время. Это' Лучше принимать все аргументы как литералы и не выполнять дополнительную оценку функции как часть попытки выполнить оценку функции.

Кроме того, что более фундаментально, технически невозможно реализовать значения аргументов по умолчанию как оценки функций.

Задумайтесь на мгновение о рекурсивном ужасе такого рода круговоротов. Предположим, что вместо значений по умолчанию, являющихся литералами, мы разрешаем им быть функциями, которые вычисляются каждый раз, когда требуются значения по умолчанию для параметра.

[Это будет аналогично тому, как работает collections.defaultdict .]

def aFunc( a=another_func ):
    return a*2

def another_func( b=aFunc ):
    return b*3

Какое значение имеет another_func () ? Чтобы получить значение по умолчанию для b , он должен вычислить aFunc , для чего требуется eval another_func . Ой.

9
ответ дан 27 November 2019 в 06:59
поделиться

Обходной путь для этого , обсуждаемый здесь (и очень надежный):

class Node(object):
    def __init__(self, children = None):
        self.children = [] if children is None else children

Что касается того, зачем искать ответ от фон Лёвиса, но это вероятно потому, что Определение функции создает объект кода из-за архитектуры Python, и может не быть средства для работы со ссылочными типами, подобными этому, в аргументах по умолчанию.

7
ответ дан 27 November 2019 в 06:59
поделиться

Python function definitions are just code, like all the other code; they're not "magical" in the way that some languages are. For example, in Java you could refer "now" to something defined "later":

public static void foo() { bar(); }
public static void main(String[] args) { foo(); }
public static void bar() {}

but in Python

def foo(): bar()
foo()   # boom! "bar" has no binding yet
def bar(): pass
foo()   # ok

So, the default argument is evaluated at the moment that that line of code is evaluated!

0
ответ дан 27 November 2019 в 06:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: