Python: использование ..%(var)s..% locals() является хорошей практикой?

Я обнаружил этот паттерн (или антипаттерн) и очень им доволен.

Я чувствую, что это очень подвижно:

def example():
    age = ...
    name = ...
    print "hello %(name)s you are %(age)s years old" % locals()

Иногда я использую его двоюродного брата:

def example2(obj):
    print "The file at %(path)s has %(length)s bytes" % obj.__dict__

Мне не нужно создавать искусственный кортеж, подсчитывать параметры и сохранять совпадающие позиции %s внутри кортежа.

Вам это нравится? Используете/будете ли использовать? Да/Нет, поясните.


person flybywire    schedule 11.10.2009    source источник


Ответы (7)


Это нормально для небольших приложений и якобы «одноразовых» сценариев, особенно с улучшением vars, упомянутым @kaizer.se, и версией .format, упомянутой @RedGlyph.

Однако для больших приложений с длительным сроком обслуживания и большим количеством сопровождающих эта практика может привести к головным болям при обслуживании, и я думаю, что именно отсюда исходит ответ @S.Lott. Позвольте мне объяснить некоторые связанные с этим проблемы, так как они могут быть неочевидными для тех, у кого нет шрамов от разработки и поддержки больших приложений (или многоразовых компонентов для таких зверей).

В «серьезном» приложении у вас не было бы жестко запрограммированной строки формата — или, если бы она была, она была бы в какой-то форме, такой как _('Hello {name}.'), где _ происходит от gettext или аналогичные фреймворки i18n/L10n. Дело в том, что такое приложение (или повторно используемые модули, которые могут использоваться в таких приложениях) должны поддерживать интернационализацию (AKA i18n) и локализацию (AKA L10n): вы хотите, чтобы ваше приложение могло выдавать «Hello Paul» в определенных случаях. странах и культурах, «Hola Paul» в некоторых других, «Ciao Paul» в других и так далее. Таким образом, строка формата более или менее автоматически заменяется другой во время выполнения, в зависимости от текущих настроек локализации; вместо того, чтобы быть жестко запрограммированным, он живет в какой-то базе данных. Для всех намерений и целей представьте, что строка формата всегда является переменной, а не строковым литералом.

Итак, то, что у вас есть, по сути

formatstring.format(**locals())

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

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

Если у вас есть превосходное интеграционное/регрессионное тестирование, поломка будет обнаружена до бета-релиза, но QA будет кричать на вас, и релиз будет отложен... и, давайте будем честными, стремясь к 100% охвату с помощью модульные тесты разумны, но на самом деле не с интеграционными тестами, если принять во внимание комбинаторный взрыв настроек [[для L10N и по многим другим причинам]] и поддерживаемых версий всех зависимости. Таким образом, вы просто не рискуете сломаться, потому что «они будут пойманы в QA» (если вы это сделаете, вы не сможете долго продержаться в среде, которая разрабатывает большие приложения или повторно используемые компоненты;-).

Таким образом, на практике вы никогда не удалите локальную переменную «имя», даже несмотря на то, что специалисты по пользовательскому опыту уже давно заменили это приветствие на более подходящее «Добро пожаловать, Dread Overlord!» (и соответственно их версии L10n'ed). Все потому, что ты пошел на locals()...

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

Но подождите, есть хуже! Среди множества полезных услуг, которые может сделать вам lint-подобная программа (например, pylint), заключается в том, чтобы предупредить вас о неиспользуемых локальных переменных (хотелось бы, чтобы это можно было сделать и для неиспользуемых глобальных переменных, но для повторно используемых компонентов это слишком сложно ;-). Таким образом, вы сможете очень быстро и дешево отловить большинство случайных орфографических ошибок, таких как if ...: nmae = ..., вместо того, чтобы наблюдать за сбоем в модульном тесте и проводить тщательную работу, чтобы выяснить, почему он сломался (вы делаете< /em> есть навязчивые, всепроникающие модульные тесты, которые обнаружат это в конце концов, верно?-) -- lint сообщит вам о неиспользуемой локальной переменной nmae, и вы немедленно это исправите.

Но если в вашем коде есть blah.format(**locals()) или, что то же самое, blah % locals()... вы SOL, приятель!-) Как бедняга lint узнает, действительно ли nmae является неиспользуемой переменной, или она действительно используется кем-то еще? внешняя функция или метод, которому вы передаете locals()? Он не может - либо он все равно будет предупреждать (вызывая эффект «кричащего волка», который в конечном итоге приводит к тому, что вы игнорируете или отключаете такие предупреждения), либо он никогда не будет предупреждать (с тем же конечным эффектом: никаких предупреждений ;-) .

Сравните это с альтернативой "явное лучше, чем неявное"...:

blah.format(name=name)

Там -- больше не нужно заботиться об обслуживании, производительности и не буду ли я мешать ворсинкам; блаженство! Вы немедленно даете понять всем заинтересованным сторонам (включая lint;-), что именно какие локальные переменные используются и для каких целей.

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

Итак, резюмируя: "γνῶθι σεαυτόν!" Хм, в смысле, "познай себя!". И под «самим собой» я на самом деле подразумеваю «цель и объем вашего кода». Если это что-то вроде 1-го или около того, никогда не будет i18n'd и L10n'd, вряд ли будет нуждаться в будущем обслуживании, никогда не будет повторно использоваться в более широком контексте и т. д., и т. д., тогда используйте locals() для его маленькое, но аккуратное удобство; если вы знаете иначе или даже если вы не совсем уверены, ошибитесь из-за осторожности и сделайте вещи более явными - потерпите небольшое неудобство, объясняя, что именно вы собираетесь, и наслаждайтесь всеми полученными преимуществами.

Кстати, это всего лишь один из примеров, когда Python стремится поддерживать как «небольшое, одноразовое, исследовательское, возможно, интерактивное» программирование (позволяя и поддерживая рискованные удобства, выходящие далеко за рамки locals() — подумайте о import *, eval, exec , и несколько других способов, которыми вы можете смешивать пространства имен и рисковать последствиями обслуживания ради удобства), а также «большие, многоразовые, корпоративные» приложения и компоненты. Он может неплохо справляться с обеими задачами, но только если вы «знаете себя» и избегаете использования «удобных» частей, за исключением тех случаев, когда вы абсолютно уверены, что действительно можете себе это позволить. Чаще всего ключевым соображением является «что это делает с моими пространствами имен и осведомленностью об их формировании и использовании компилятором, lint & c, людьми, читающими и сопровождающими, и так далее?».

Помните: «Пространства имен — это отличная идея — давайте сделаем больше таких!» таков вывод Zen of Python... но Python как «язык для взрослых по обоюдному согласию» позволяет вам определять границы того, что это подразумевает, как следствие вашей среды разработки, целей и практики. Используйте эту силу ответственно!-)

person Alex Martelli    schedule 11.10.2009
comment
Отличный ответ. Я думаю, что большинство программ не интернационализированы, поэтому в очень большом количестве случаев это не проблема. Но в этих случаях да, интерполяция строк — это плохо. - person Paul Biggar; 11.10.2009
comment
@Paul, позволю себе не согласиться: интерполяция строк отлична, особенно для поддержки i18n/L10n — это просто должно происходить для переменных с явным именем! Проблема не в интерполяции, а в передаче locals() внешним функциям или методам. Кстати, растущая поддержка Python для Unicode (теперь текстовая строка по умолчанию в 3.*) — это именно попытка помочь изменить тот факт, что большинство программ не i18n'd — гораздо больше должно быть, чем в настоящее время < я; по мере того, как Интернет (через смартфоны, нетбуки и т. д.) бум в Китае и т. д., англоцентрические предположения становятся все более и более странными ;-). - person Alex Martelli; 11.10.2009
comment
Я думаю, что есть возможность возиться с локалями/gettext для вставки {self},{password} или других объектов, которые вы не ожидаете отображать в строке формата. Это может быть угрозой безопасности. Лучше всего быть явным для реального кода - person John La Rooy; 12.10.2009
comment
@Alex: я бы определил "hello %(name)s you are %(age)s years old" % locals() как интерполяцию строк (во всяком случае, в стиле Perl или PHP). У вас есть другое определение? - person Paul Biggar; 14.10.2009
comment
@Paul, я называю это форматированием строк — имена совершенно произвольны (очевидно, это одно и то же утверждение, независимо от того, используете ли вы специально созданный словарь или уже существующий), и интерполяция не используется — см. определение интерполяции на en.wikipedia.org/wiki/Interpolation . Объединение "a" и "c" для получения "b", теперь это будет интерполяцией строк. - person Alex Martelli; 14.10.2009
comment
@Alex: в PHP или Perl "hello {$name}s you are $age years old" - это интерполяция строк. Это тот же шаблон в Python. Я предполагаю, что вы шутите по поводу определения интерполяции — строковая интерполяция долгое время была термином для описания этого типа форматирования строк. Это даже название PEP215. - person Paul Biggar; 14.10.2009
comment
Подумайте, почему PEP 215 был отвергнут: прямой поиск идентификаторов и требование буквальной строки сильно мешали i18n/L10N: сегодняшний метод форматирования OTOH, как я объяснял выше, довольно удобен для i18n/L10n. Но базовая функциональность одинакова, независимо от того, называете ли вы это форматированием или интерполяцией, например. search.cpan.org/~nobull/String- Interpolate-0.3/lib/String/ имеет так называемую интерполяцию, близкую к .format (конечно, более богатую и сложную, но ключевой момент, явные хэши [& ties, поскольку это Perl], является общим) . - person Alex Martelli; 14.10.2009
comment
В принципе, программы lint могут распознавать идиому и подсчитывать использование в строках формата. - person Mechanical snail; 22.03.2012
comment
но ждать. _('Hello {name}.') выполняет ту же функцию, что и .format(**locals), но также выполняет интернационализацию? Потому что это намного проще набирать, и было бы неплохо, если бы это был стандартный способ сделать это. См. stackoverflow.com/q/19549980/125507. - person endolith; 09.12.2013
comment
Согласен с Алексом. Кроме того, использование **locals() позволяет получить доступ только к локальным идентификаторам. В зависимости от идентификаторов, указанных в вашей строке формата, вам придется использовать **locals() или **globals() или оба в комбинированном словаре. Обычно вы хотите, чтобы любой идентификатор, на который вы ссылаетесь, соответствовал обычным правилам области видимости Python. Если вы переместите объект из глобальной области в локальную или наоборот, строка формата с явными идентификаторами продолжит работать, но строка с использованием **locals() или **globals() сломается. Подход **locals() заставляет вас осознавать область действия так, как обычно это не требуется. - person Chris Johnson; 20.02.2014
comment
Что касается i18n и gettext: кажется, что можно легко просто вызвать функцию подчеркивания для любых строковых литералов, которые будут интерполированы через строку формата. - person martineau; 15.03.2014

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

Я никогда не пишу код, который мне не нужно писать — меньше кода лучше, чем больше кода, и эта практика использования locals(), например, позволяет мне писать меньше кода, а также очень легко читать и понимать.

person Andrew Hare    schedule 11.10.2009
comment
Мне нравится использовать его в верхней части функции, когда мне нужен словарь, построенный из входных параметров. Это довольно эффективно, и я также чувствую его питоничность. Иногда можно злоупотреблять, я это понимаю. - person radtek; 22.07.2016

Никогда за миллион лет. Неясно, каков контекст форматирования: locals может включать практически любую переменную. self.__dict__ не так расплывчато. Совершенно ужасно, чтобы будущие разработчики ломали головы над тем, что локально, а что нет.

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

person S.Lott    schedule 11.10.2009
comment
Контекст — это функция, в которой появляется locals(). Если это хорошая короткая функция, вы можете просто посмотреть на переменные. Если это очень длинная функция, ее следует реорганизовать. Чем self.__dict__ понятнее? - person foosion; 11.10.2009
comment
Я не понимаю, почему ссылка на имя локальной переменной в строке формата более неясна, чем ссылка на имя локальной переменной в коде. - person Robert Rossney; 11.10.2009
comment
self.__dict__ основан на определении класса, которое часто связано с методом __init__() и тщательно задокументировано в строке документа. locals() часто представляет собой довольно случайный набор имен. - person S.Lott; 11.10.2009
comment
Мне это тоже не особо нравится, но не потому, что непонятно. Область видимости в Python проще, чем во многих других языках, но подобный шаблон проектирования по-прежнему вызывает проблемы, потому что вы обнаружите, что ленитесь и используете что-то вне специально определенной функции, а затем вы БУДЕТЕ сталкиваться с проблемами области видимости. - person Paul McMillan; 11.10.2009
comment
@Robert Rossney: я никогда не говорил, что переменная name непонятна. Я сказал, что locals() неясно, потому что имена переменных труднее найти. они скрыты в строке формата. - person S.Lott; 13.10.2009
comment
@PaulMcMillan: С какими проблемами вы столкнетесь? - person endolith; 07.03.2012
comment
@endolith: вы сталкиваетесь с проблемами, когда вы позже меняете область видимости или переименовываете переменную в соответствии с новым семантическим значением, или кто-то еще вводит что-то в вашу область видимости, или... это просто недостаточно явно, чтобы его можно было поддерживать в реальном проекте . - person Paul McMillan; 19.03.2012

Что касается «двоюродного брата», вместо obj.__dict__ он выглядит намного лучше с новым форматированием строки:

def example2(obj):
    print "The file at {o.path} has {o.length} bytes".format(o=obj)

Я часто использую это для методов repr, например.

def __repr__(self):
    return "{s.time}/{s.place}/{s.warning}".format(s=self)
person pfctdayelise    schedule 24.05.2011

"%(name)s" % <dictionary> или даже лучше, "{name}".format(<parameters>) имеют преимущество

  • быть более читаемым, чем "%0s"
  • быть независимым от порядка аргументов
  • не обязательно использовать все аргументы в строке

Я бы предпочел str.format(), так как это должно быть способом сделать это в Python 3 (согласно PEP 3101) и уже доступен с версии 2.6. Однако с locals() вам придется сделать это:

print("hello {name} you are {age} years old".format(**locals()))
person RedGlyph    schedule 11.10.2009

Использование встроенной vars([object]) (документации) может улучшить второй вид. тебе:

def example2(obj):
    print "The file at %(path)s has %(length)s bytes" % vars(obj)

Эффект, конечно, тот же.

person u0b34a0f6ae    schedule 11.10.2009

Теперь для Python 3.6.0 существует официальный способ сделать это: форматированные строковые литералы.

Это работает следующим образом:

f'normal string text {local_variable_name}'

Например. вместо этих:

"hello %(name)s you are %(age)s years old" % locals()
"hello {name} you are {age} years old".format(**locals())
"hello {} you are {} years old".format(name, age)

просто сделайте это:

f"hello {name} you are {age} years old"

Вот официальный пример:

>>> name = "Fred"
>>> f"He said his name is {name}."
'He said his name is Fred.'
>>> width = 10
>>> precision = 4
>>> value = decimal.Decimal("12.34567")
>>> f"result: {value:{width}.{precision}}"  # nested fields
'result:      12.35'

Ссылка:

person Jacktose    schedule 08.06.2017
comment
Это сделало мой день. я собираюсь использовать это для всего моего кода Python, который не ограничен поддержкой 2.x или, в данном случае, ‹3.6! - person svenevs; 10.04.2018