«Наименьшее удивление» и изменчивый аргумент по умолчанию

Почти. Необходимо предложить «[» как матрицу из двух столбцов:

dat$matval <- mat[ cbind(dat$I, dat$J) ] # should do it.

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

2395
задан dreftymac 22 March 2019 в 22:58
поделиться

10 ответов

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

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

В любом случае, Effbot имеет очень хорошее объяснение причин такого поведения в Значения параметров по умолчанию в Python .
Мне он показался очень ясным, и я действительно предлагаю прочитать его, чтобы лучше понять, как работают объекты функций.

1550
ответ дан 22 November 2019 в 19:55
поделиться

Самым коротким ответом, вероятно, будет «определение это исполнение ", поэтому весь аргумент не имеет строгого смысла. В качестве более надуманного примера вы можете процитировать следующее:

def a(): return []

def b(x=a()):
    print x

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

Я согласен, что это ошибка, когда вы пытаетесь использовать конструкторы по умолчанию.

23
ответ дан 22 November 2019 в 19:55
поделиться

Вы спрашиваете, почему это:

def func(a=[], b = 2):
    pass

внутренне не эквивалентно этому:

def func(a=None, b = None):
    a_default = lambda: []
    b_default = lambda: 2
    def actual_func(a=None, b=None):
        if a is None: a = a_default()
        if b is None: b = b_default()
    return actual_func
func = func()

, за исключением случая явного вызова func (None, None), который мы ' будет проигнорировать.

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

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

33
ответ дан 22 November 2019 в 19:55
поделиться

Это оптимизация производительности. В результате этой функциональности Как вы думаете, какой из этих двух вызовов функций быстрее?

def print_tuple(some_tuple=(1,2,3)):
    print some_tuple

print_tuple()        #1
print_tuple((1,2,3)) #2

Я дам вам подсказку. Вот разборка (см. http://docs.python.org/library/dis.html ):

# 1

0 LOAD_GLOBAL              0 (print_tuple)
3 CALL_FUNCTION            0
6 POP_TOP
7 LOAD_CONST               0 (None)
10 RETURN_VALUE

# 2

 0 LOAD_GLOBAL              0 (print_tuple)
 3 LOAD_CONST               4 ((1, 2, 3))
 6 CALL_FUNCTION            1
 9 POP_TOP
10 LOAD_CONST               0 (None)
13 RETURN_VALUE

Я сомневаюсь в Опытное поведение имеет практическое применение (кто действительно использовал статические переменные в C, не создавая ошибок?)

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

org / library / dis.html ):

# 1

0 LOAD_GLOBAL              0 (print_tuple)
3 CALL_FUNCTION            0
6 POP_TOP
7 LOAD_CONST               0 (None)
10 RETURN_VALUE

# 2

 0 LOAD_GLOBAL              0 (print_tuple)
 3 LOAD_CONST               4 ((1, 2, 3))
 6 CALL_FUNCTION            1
 9 POP_TOP
10 LOAD_CONST               0 (None)
13 RETURN_VALUE

Я сомневаюсь, что опытное поведение имеет практическое применение (кто действительно использовал статические переменные в C, без разведение ошибок?)

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

org / library / dis.html ):

# 1

0 LOAD_GLOBAL              0 (print_tuple)
3 CALL_FUNCTION            0
6 POP_TOP
7 LOAD_CONST               0 (None)
10 RETURN_VALUE

# 2

 0 LOAD_GLOBAL              0 (print_tuple)
 3 LOAD_CONST               4 ((1, 2, 3))
 6 CALL_FUNCTION            1
 9 POP_TOP
10 LOAD_CONST               0 (None)
13 RETURN_VALUE

Я сомневаюсь, что опытное поведение имеет практическое применение (кто действительно использовал статические переменные в C, без разведение ошибок?)

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

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

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

25
ответ дан 22 November 2019 в 19:55
поделиться

Раньше я думал, что создание объектов во время выполнения будет лучшим подходом. Сейчас я менее уверен, поскольку вы теряете некоторые полезные функции, хотя, возможно, это того стоит, просто для предотвращения путаницы новичков. Недостатки этого:

1. Производительность

def foo(arg=something_expensive_to_compute())):
    ...

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

2. Принудительная привязка параметров

Полезным трюком является привязка параметров лямбда к текущему связыванию переменной при создании лямбда. Например:

funcs = [ lambda i=i: i for i in range(10)]

Это возвращает список функций, которые возвращают 0,1,2,3 ... соответственно. Если поведение изменилось, они вместо этого привяжут i к значению времени вызова i, так что вы получите список функций, которые все вернули 9 .

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

def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]

3. Самоанализ

Рассмотрим код:

def foo(a='test', b=100, c=[]):
   print a,b,c

Мы можем получить информацию об аргументах и ​​значениях по умолчанию с помощью модуля inspect , который

>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))

Эта информация очень полезна для таких вещей, как создание документов, метапрограммирование, декораторы и т. Д. .

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

_undefined = object()  # sentinel value

def foo(a=_undefined, b=_undefined, c=_undefined)
    if a is _undefined: a='test'
    if b is _undefined: b=100
    if c is _undefined: c=[]

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

57
ответ дан 22 November 2019 в 19:55
поделиться

This behavior is easy explained by:

  1. function (class etc.) declaration is executed only once, creating all default value objects
  2. everything is passed by reference

So:

def x(a=0, b=[], c=[], d=0):
    a = a + 1
    b = b + [1]
    c.append(1)
    print a, b, c
  1. a doesn't change - every assignment call creates new int object - new object is printed
  2. b doesn't change - new array is build from default value and printed
  3. c changes - operation is performed on same object - and it is printed
49
ответ дан 22 November 2019 в 19:55
поделиться

Причина проста в том, что привязки выполняются при выполнении кода, а определение функции выполняется, ну ... когда функции определены.

Сравните это:

class BananaBunch:
    bananas = []

    def addBanana(self, banana):
        self.bananas.append(banana)

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

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

Да, это неожиданно. Но как только копейка падает, она идеально подходит к тому, как работает Python в целом. Фактически, это ' это хорошее учебное пособие, и как только вы поймете, почему это происходит, вы будете лучше разбираться в Python.

Тем не менее, он должен занимать видное место в любом хорошем учебнике по Python. Потому что, как вы упомянули, каждый рано или поздно сталкивается с этой проблемой.

81
ответ дан 22 November 2019 в 19:55
поделиться

Я ничего не знаю о внутренней работе интерпретатора Python (и я не являюсь экспертом в компиляторах и интерпретаторах), поэтому не обвиняйте меня, если я предлагаю что-то непонятное или невозможное.

При условии, что объекты python изменяемы , я думаю, что это следует учитывать при разработке аргументов по умолчанию. Когда вы создаете список:

a = []

, вы ожидаете получить новый список, на который ссылается a .

Почему a = [] в

def x(a=[]):

создавать новый список при определении функции, а не при ее вызове? Это похоже на то, как вы спрашиваете: «Если пользователь не предоставляет аргумент, то создает новый список и использует его, как если бы он был создан вызывающей стороной». Я думаю, что это неоднозначно: пользователь

def x(a=datetime.datetime.now()):

, хотите ли вы, чтобы для a по умолчанию было установлено значение datetime, соответствующее тому, когда вы определяете или выполняете x ? В этом случае, как и в предыдущем, я сохраню такое же поведение, как если бы аргумент по умолчанию «назначение» был первой инструкцией функции ( datetime.now () , вызываемой при вызове функции). С другой стороны, если пользователь хочет отображение времени определения, он может написать:

b = datetime.datetime.now()
def x(a=b):

Я знаю, я знаю: это закрытие. В качестве альтернативы Python может предоставить ключевое слово для принудительной привязки времени определения:

def x(static a=b):
110
ответ дан 22 November 2019 в 19:55
поделиться

Suppose you have the following code

fruits = ("apples", "bananas", "loganberries")

def eat(food=fruits):
    ...

When I see the declaration of eat, the least astonishing thing is to think that if the first parameter is not given, that it will be equal to the tuple ("apples", "bananas", "loganberries")

However, supposed later on in the code, I do something like

def some_random_function():
    global fruits
    fruits = ("blueberries", "mangos")

then if default parameters were bound at function execution rather than function declaration then I would be astonished (in a very bad way) to discover that fruits had been changed. This would be more astonishing IMO than discovering that your foo function above was mutating the list.

The real problem lies with mutable variables, and all languages have this problem to some extent. Here's a question: suppose in Java I have the following code:

StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) );  // does this work?

Now, does my map use the value of the StringBuffer key when it was placed into the map, or does it store the key by reference? Either way, someone is astonished; either the person who tried to get the object out of the Map using a value identical to the one they put it in with, or the person who can't seem to retrieve their object even though the key they're using is literally the same object that was used to put it into the map (this is actually why Python doesn't allow its mutable built-in data types to be used as dictionary keys).

Your example is a good one of a case where Python newcomers will be surprised and bitten. But I'd argue that if we "fixed" this, then that would only create a different situation where they'd be bitten instead, and that one would be even less intuitive. Moreover, this is always the case when dealing with mutable variables; you always run into cases where someone could intuitively expect one or the opposite behavior depending on what code they're writing.

I personally like Python's current approach: default function arguments are evaluated when the function is defined and that object is always the default. I suppose they could special-case using an empty list, but that kind of special casing would cause even more astonishment, not to mention be backwards incompatible.

266
ответ дан 22 November 2019 в 19:55
поделиться

Это может быть правдой, что:

  1. Кто-то использует все возможности языка / библиотеки, и
  2. изменение поведения здесь было бы нецелесообразным, но

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

  1. Это сбивающая с толку функция, и она неудачна для Python.

Другие ответы или, по крайней мере, некоторые из них либо делают пункты 1 и 2, но не 3, или сделать пункт 3 и преуменьшить значение пунктов 1 и 2. Но все три верны.

Возможно, смена лошадей на полпути здесь потребует значительных поломок и может возникнуть больше проблем. изменив Python на интуитивно понятную обработку начального фрагмента Стефано. И может быть правда, что кто-то, кто хорошо знал внутреннее устройство Python, мог объяснить минное поле последствий. Однако,

Существующее поведение не является питоническим, и Python является успешным, потому что очень мало в языке нарушает принцип наименьшего удивления где-либо около так сильно. Это реальная проблема, было бы разумно искоренить ее или нет. Это недостаток дизайна. Если вы понимаете язык намного лучше, пытаясь отследить поведение, я могу сказать, что C ++ делает все это и многое другое; вы многому научитесь, перемещаясь, например, по незаметным ошибкам указателя. Но это не Pythonic: люди, которые заботятся о Python достаточно, чтобы упорствовать перед лицом такого поведения, - это люди, которых привлекает этот язык, потому что в Python гораздо меньше сюрпризов, чем в другом языке. Любопытные и любопытные становятся питонистами, когда удивляются тому, как мало времени требуется, чтобы что-то заработало - не из-за дизайна, - я имею в виду,

12
ответ дан 22 November 2019 в 19:55
поделиться
Другие вопросы по тегам:

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