Комментарии 32
def f(x, d=datetime.now()):
print(x, d)
скорее всего результат будет не тот которого ожидаешь
Поэтому правильный вариант, очевидно:
def f(x, val_getter=datetime.now):
print(x, val_getter())
Хотя можно и чуточку усложнить, дабы предупредить более "плоское" использование, но тогда логике посыпится в редких случаях, но они специфичны и довольно легко обходятся:
def f(x, val_handler=datetime.now):
if callable(val_handler):
print(x, val_handler())
else:
print(x, val_handler)
Вообще, статья какая-то игрушечная, я бы сказал — кликбейторская. Обладая простейшим пониманием как работают ссылки (и немного про область видимости в третьей "задаче") — "решения" наиочевиднейшие.
def f(x, d=None):
print(x, d or datetime.now())
мне больше нравится вот такое решение
Это если ожидается datetime, здесь это никоим образом не продемонстрированно, а из контекста непонятно. Но и такой вариант жизнеспособен, конечно.
А вот этот вот setter — это на цирковой трюк похоже. Потому что запаковывать дату с лямбду можно, конечно — но очень уж напоминает вырезание гланд через задний проход.
Мне кажется, что во второй задаче, вместо решения
if s is None:
s = set()
будет более идиоматично использовать:
s = s or set()
Ну и это решение сразу показывает другую особенность Python, когда логический оператор возвращает значение.
Да, вы правы но, если мы обсуждаем коня сферического в вакууме то:
- идиоматичным (причем не только в Python), как раз, является возврат значения из функции, а не изменение входных аргументов и, таким образом, неявный возврат значения (да исключения бывают).
- в данном примере ничего не планировалось возвращать из функции, это синтетический пример, суть которого в объяснении одной из особенностей языка Python, связанной с временем вычисления аргумента по умолчанию.
Если ваша цель изменять входные аргументы, то, естественно, предложенное решение не подходит. Но потом не удивляйтесь советам типа, передавайте в функцию копию вашей структуры, чтобы функция не могла изменить ее.
Если ваша цель изменять входные аргументы, то, естественно, предложенное решение не подходит.Ультрагениально: решение, призванное решить проблему изменения дефолтных параметров — отлично работает, если вы эти параметры не меняете. Класс. Высший пилотаж.
А давайте я вам продам отличный бронежилет. Прекрасно выглядящий, ноский… защищает от всех пуль, когда в вас не стреляют.
Ну или грузовик — прекрасно ездящий если не пытаться на нём грузы перевозить…
Но потом не удивляйтесь советам типа, передавайте в функцию копию вашей структуры, чтобы функция не могла изменить ее.Это нормально — ненормально писать код, который ведёт себя правильно только в том случае, когда он не нужен.
Совсем ненормально — объяснять, что код-то отличный, идеоматичный, красивый… а что он не работает как нужно — ну так то вообще дело десятое…
Нравятся функции с неявным возвратом значений через изменение аргументов (побочные эффекты)? Ну тогда не используйте предложенную конструкцию. На SO достаточно веток, обсуждающих "правила безопасности" функций с побочными эффектами, но все реально.
Хотите создавать функции без побочных эффектов с явным возвратом значений ("Explicit is better than implicit." — Zen Of Python)? Можете использовать как одну, так и другую конструкции. Оба варианта будут полностью работоспособными. На SO есть ветки обсуждающие что "правильнее".
На суть статьи ни один из вариантов никак не влияет, хотя-бы потому, что "как нужно" не указано. Мы же не будем, устраивать полноценный code review для синтаксического, учебного примера?
Ну и не надо забывать, что вариант с s is None
не является эквивалентом s or set()
, надо четко представлять в чем разница и плюсы и минусы обеих конструкций. В жизни приходится применять оба подхода. Всегда лучше иметь выбор и знать о том, что логические операторы в Python могут использоваться подобным образом.
Эпитеты, пожалуйста, оставьте при себе.
Хотите создавать функции без побочных эффектов с явным возвратом значений («Explicit is better than implicit.» — Zen Of Python)? Можете использовать как одну, так и другую конструкции. Оба варианта будут полностью работоспособными.Ну что за детский сад, честное слово.
Ни одна из этих конструкций работать, очевидно, не будет, потому что ни в одной из них множество не копируется.
Если весь дальнейший код это множество, после этого, ещё и не меняет — то ни одна из этих конструкций не нужна. Если меняет — ни одна из них недостаточна, нужно либо
copy()
, либо, возможно, даже deepcopy() использовать.На суть статьи ни один из вариантов никак не влияет, хотя-бы потому, что «как нужно» не указано.Не указано. А ещё там не указано, что данная функция не должна отсылать содержимое
/etc/passwd
на сервер ботнета. И ешё не указано, что она не должна ломаться в пятницу, 13го. Обозначает ли это что решение, содержащее «по приколу» что-нибудь «развлекательно-познавательное», ну, скажем… if random.randrange(1000000) < 7: sys.exit()
будет хорошим решением?А чё? В условиях же не сказано.
Мы же не будем, устраивать полноценный code review для синтаксического, учебного примера?А почему нет, собственно? Как вообще на учебных примерах человек может чему-то научиться если они по идиотски написаны?
Ну и не надо забывать, что вариант сНо вы же забыли. Или специально решили «подловить новичка на подлости» (что ещё хуже).s is None
не является эквивалентомs or set()
, надо четко представлять в чем разница и плюсы и минусы обеих конструкций.
Может быть вы хотели написать что-нибудь типа
s = (s or set()).copy()
?Да, это, возможно, будет работать — но это, извините, тоже ребус. Уж в этом-то случае проще написать
s = set()
в параметрах и s = s.copy()
в качестве первой строки…На SO есть ветки обсуждающие что «правильнее».Отлично — покажите, пожалуйста, где, кто и когда изобретённую вами семантику «чтобы получить результат работы через переданное вами множество нужно в него, предварительно, засунуть фиктивный элемент» считает правильной. Особенно интересно увидеть как они предлагают боротьсь с тем, что этот элемент может оказаться и в результатах работы тоже… а главное — зачем такое может потребоваться…
Эпитеты, пожалуйста, оставьте при себе.Знаете — когда тебе, на полном серьёзе, предлагают что-то из книжки «вредные советы»… это не так-то просто сделать…
Если бы a был изменяемым объектом, то команда a.append(5) меняла бы исходный объект, и тогда список s «видел» бы изменение.
Но в данном случае же a — изменяемый объект, и его изменение влияет на s, нет?
a = [1]
b = [2]
s = [a, b]
a.append(5)
>>print(s)
>>[[1, 5], [2]]
Если бы a был изменяемым объектом, то команда a.append(5) меняла бы исходный объект, и тогда список s «видел» бы изменение.
так вроде ж так и есть?
почему бы?
Скучно. Нате поинтереснее
def f():
a = 1
def g():
nonlocal a
print(a)
a+=1
return g
g1 = f()
g2= f()
g1()
g2()
g1()
nonlocal
вёл себя как global
— я бы удивился… а так — всё работает самым очевидным образом, я бы сказал…Ок, ок! Ну так то хоть повеселее?
def f():
a = 1
def g():
nonlocal a
print(a)
a += 1
def update(x):
nonlocal a
a=x
g.update=update
return g
g1 = f()
g2 = f()
g1()
g2()
g1()
g1.update(100)
g1()
dictionary
, то такие примеры распутываются легко. Но когда у вас там начинает уже фигурировать десяток объектов, то тут уже нужно на бумажке рисовать куда, чего и когда вы двигаете. Но ничего принципиально не меняется.Ну кроме того, что если функции ничего не возвращают, то результат — всегда
None
… но я надеюсь пример был не об этом…Однако попытка изменить неизменяемую переменную во второй функции y += x приводит к тому, что y начинает ссылаться на другой адрес в памяти: исходная y будет забыта, что приведёт к ошибке UnboundLocalError.
Здесь мы пытаемся использовать неинициализированную переменную. Потому как: y += x это y = y + x. Справа доступ к неинициализированной y.
Потому как:y += x
этоy = y + x
. Справа доступ к неинициализированнойy
.
Не совсем.
Во-первых, y += x
это не y = y + x
, а вовсе даже y = operator.iadd(y, x)
.
Во-вторых, а с чего она там не инициализирована-то? — мы же вроде как захватили ее из родительского скоупа, см. пример 1. Более того, если вместо y += x
вы напишете z = y + x
все снова будет прекрасно работать с кложурно захваченным y
. y
становится неинициализированным внутри скоупа функции когда парсер натыкается на y =
. Тут происходит неявный хойстинг и внутри всего скоупа — бамс! — y
не инициализирован. Смотрите:
>>> def g():
... y = 0
... def inner():
... print(y) # BOOM!
... y = 42
... return y
... return inner
...
>>> g()()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in inner
UnboundLocalError: local variable 'y' referenced before assignment
Я, кстати, в жизни не написал ни строчки кода на питоне, но такие вещи хорошо бы понимать просто по факту принадлежности к CS, скоупинг так организован примерно во всех скриптовых языках.
Понятие hoisting немного смежное, но к питону это не имеет отношения. При загрузке модуля (скрипта), пока мы не дойдем до объявления класса/функции (и не выполним этот код!) — обращаться к таким объектам нельзя. Переменные вообще невозможно объявить без инициализации.
Бывают специфические для языков штуки. Вот как оно в CS бывает.
Зачем это на бумаге уметь выводить/помнить — хз.
Почему так происходит? Когда мы исполняем l.append(x), меняется изменяемый объект, созданный при определении функции. Но переменная l всё ещё ссылается на старый адрес в памяти. Однако попытка изменить неизменяемую переменную во второй функции y += x приводит к тому, что y начинает ссылаться на другой адрес в памяти: исходная y будет забыта, что приведёт к ошибке UnboundLocalError.
Полнейшая чепуха.
>>> def f(): ... l = [1] ... def inner(x): ... l += [x] ... return l ... return inner ... >>> f_inner = f() >>> print(f_inner(2)) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in inner UnboundLocalError: local variable 'l' referenced before assignment
Если следовать логике автора, то эта функция должна работать точно так же, как и её собственная
f()
: тут точно такой же изменяемый объект-список.import dis
a = 10
b = 20
def foo():
print(b)
print(a)
a += 10
dis.dis(foo)
6 0 LOAD_GLOBAL 0 (print)
2 LOAD_GLOBAL 1 (b)
4 CALL_FUNCTION 1
6 POP_TOP
7 8 LOAD_GLOBAL 0 (print)
10 LOAD_FAST 0 (a)
12 CALL_FUNCTION 1
14 POP_TOP
8 16 LOAD_FAST 0 (a)
18 LOAD_CONST 1 (10)
20 INPLACE_ADD
22 STORE_FAST 0 (a)
24 LOAD_CONST 0 (None)
26 RETURN_VALUE
У нас есть a и b. Для их чтения используются разные инструкции (LOAD_FAST, LOAD_GLOBAL).
При построении байткода если есть локальная переменная, все обращения к ней локальны (LOAD_FAST), вне зависимости от порядка их следования.
Ничего себе: питонисты открывают для себя разницу между ссылочными типами и типами-значениями.
На самом деле в питоне все есть объекты. Вот сколько например у int методов:
i = 1
print(type(i))
print(dir(i))
<class 'int'>
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
И все передается по ссылке, ниже id выводит адрес объекта в памяти:
def foo(value):
print('2)', id(value))
i = 1
print('1)', id(i))
foo(i)
# 1) 93983712618240
# 2) 93983712618240
Здесь другая ситуация. При попытке сделать assignment для переменной, котороя ссылается на immutable объект, питон создает новый объект. Встроенный int это объект, но не изменяемый.
x = 1 # int is immutable
print(id(x))
x += 1
print(id(x))
# 94029430744832
# 94029430744864
x = (1, 2) # tuple is immutable
print(id(x))
x += (3, 4)
print(id(x))
# 140065572803008
# 140065572117216
x = [] # list is mutable
print(id(x))
x += [1, 2]
print(id(x))
# 140381673357056
# 140381673357056
Все операции типа +, += это вызов специальных методов. x += y это x.__iadd__(y). В таком методе мы можем поменять состояние объекта и вернуть себя же (self), или же создать и вернуть новый объект.
class Immutable:
def __iadd__(self, x):
return self._value + x # ну или Immutable(self._value + x)
class Mutable:
def __iadd__(self, x):
self._value += x
return self
А вы можете решить эти три (обманчиво) простые задачи на Python?