Аргументы по умолчанию получают оценку во время компиляции функции в объект функции. При использовании этой функции, несколько раз с помощью этой функции, они являются и остаются одним и тем же объектом.
Когда они изменяются, когда они мутируются (например, добавляя к нему элемент), они остаются мутированными по последовательным вызовам.
Они остаются мутированными, потому что каждый раз они являются одним и тем же объектом .
Поскольку список связан с функцией, когда объект функции компилируется и инстанцируется, это:
def foo(mutable_default_argument=[]): # make a list the default argument
"""function that uses a list"""
почти точно эквивалентно этому:
_a_list = [] # create a list in the globals
def foo(mutable_default_argument=_a_list): # make it the default argument
"""function that uses a list"""
del _a_list # remove globals name binding
Вот демонстрация - вы можете проверить, что они являются одним и тем же объектом каждый раз, когда они ссылаются на
example.py
print('1. Global scope being evaluated')
def create_list():
'''noisily create a list for usage as a kwarg'''
l = []
print('3. list being created and returned, id: ' + str(id(l)))
return l
print('2. example_function about to be compiled to an object')
def example_function(default_kwarg1=create_list()):
print('appending "a" in default default_kwarg1')
default_kwarg1.append("a")
print('list with id: ' + str(id(default_kwarg1)) +
' - is now: ' + repr(default_kwarg1))
print('4. example_function compiled: ' + repr(example_function))
if __name__ == '__main__':
print('5. calling example_function twice!:')
example_function()
example_function()
и работает это с python example.py
:
1. Global scope being evaluated
2. example_function about to be compiled to an object
3. list being created and returned, id: 140502758808032
4. example_function compiled:
5. calling example_function twice!:
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a']
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a', 'a']
Этот порядок выполнения часто путает новых пользователей Python. Если вы понимаете модель исполнения Python, это становится вполне ожидаемым.
Но поэтому обычная инструкция для новых пользователей заключается в том, чтобы вместо этого создать свои аргументы по умолчанию:
def example_function_2(default_kwarg=None):
if default_kwarg is None:
default_kwarg = []
Это использует None singleton как объект-дозор, чтобы сообщить функции, есть ли у нас аргумент, отличный от значения по умолчанию. Если мы не получаем никакого аргумента, то мы действительно хотим использовать новый пустой список []
в качестве значения по умолчанию.
Как говорится в разделе в разделе управления потоком :
Если вы не хотите, чтобы по умолчанию были разделены между последующими вызовами, вы можете написать такую функцию, как это:
def f(a, L=None): if L is None: L = [] L.append(a) return L
Numpy и Pandas используют разные алгоритмы для isin
. В некоторых случаях версия Numpy быстрее, а для некоторых панд. Для вашего теста NumPy кажется быстрее.
Версия Pandas, однако, имеет лучшее асимптотическое время выполнения, она выиграет для больших наборов данных.
Предположим, что в серии данных есть n
элементов (в вашем примере df
) и в запросе m
(в вашем примере vals
).
Обычно алгоритм Numpy выполняет следующие действия:
np.unique(..)
, чтобы найти все уникальные элементы в ряду. Таким образом, выполняется сортировка, т. Е. O(n*log(n))
, может быть N<=n
уникальных элементов. O(m*log(N))
в целом. Что приводит к общему времени работы O(n*log(n) + m*log(N))
.
Существуют некоторые жестко запрограммированные оптимизации для случаев, когда vals
только несколько элементов, и для этих случаев действительно блестит numpy.
Панды делают что-то другое:
khash
-функция), чтобы найти все уникальные элементы, которая занимает O(n)
. O(1)
для каждого запроса, т. Е. O(m)
в целом. В целом, время работы составляет O(n)+O(m)
, что намного лучше, чем у Нампи.
Однако, для меньших входных данных постоянные факторы, а не асимптотическое поведение - это то, что имеет значение, и это просто намного лучше для Numpy. Есть и другие соображения, такие как потребление памяти (которое выше у панд), которое может сыграть свою роль.
Но если мы возьмем больший набор запросов, ситуация будет совершенно другой:
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randint(0,10,(10**6),dtype='int8'),columns=['A'])
vals = np.array([5,7],dtype='int64')
vals2 = np.random.randint(0,10,(10**6),dtype='int64')
И теперь:
%timeit df['A'].isin(vals) # 17.0 ms
%timeit df['A'].isin(vals2) # 16.8 ms
%timeit pd.np.in1d(df['A'],vals) # 1.36
%timeit pd.np.in1d(df['A'],vals2) # 82.9 ms
Numpy действительно сдает позиции, пока есть больше запросы. Также можно видеть, что создание хеш-карты является узким местом для панд, а не для запросов.
В конце концов, не имеет большого смысла (даже если бы я это сделал!) Оценивать производительность только для одного размера ввода - это должно быть сделано для диапазона размеров ввода - есть некоторые сюрпризы, которые нужно обнаружить!
Например, Интересный факт: если вы возьмете
df = pd.DataFrame(np.random.randint(0,10,(10**6+1), dtype='int8'),columns=['A'])
, т.е. 10^6+1
вместо 10^6
, панды прибегнут к алгоритму numpy (который, на мой взгляд, не очень умен) и станут лучше для маленьких входов, но хуже для больших:
%timeit df['A'].isin(vals) # 6ms was 17.0 ms
%timeit df['A'].isin(vals2) # 100ms was 16.8 ms