Прощення Python проти дозволу та друку качок


44

У Python я часто чую, що краще "просити прощення" (вилучення винятків), а не "запитувати дозвіл" (перевірка типу / умови). Що стосується примусового набору качок в Python, це так

try:
    x = foo.bar
except AttributeError:
    pass
else:
    do(x)

краще чи гірше, ніж

if hasattr(foo, "bar"):
    do(foo.bar)
else:
    pass

з точки зору продуктивності, читабельності, "пітонічності" чи якогось іншого важливого чинника?


17
є третій варіант, не робіть нічого і не поводьтеся з жодною людиною без бару як з помилкою
jk.

Я пам’ятаю слухання, яке hasattrреалізується саме з такою точною спробою / ловом. Не впевнений, чи це правда ... (це діятиме по-іншому щодо властивостей, чи не так? Можливо, я думаю про це getattr).
Izkata

@Izkata: Реалізаціяhasattr використовує еквівалент C-API getattr(повертається, Trueякщо це успішно, Falseякщо ні), але обробка винятків у C набагато швидша.
Martijn Pieters

2
Я прийняв відповідь martijn, але хотів би додати, що якщо ви намагаєтеся встановити атрибут, вам обов'язково слід подумати про використання try / catch, оскільки це може бути властивість без сеттера, і в цьому випадку hasattr буде істинним , але це все одно підвищить AttributeError.
темна лінія

Відповіді:


60

Це дійсно залежить від того, як часто ви думаєте, що виняток буде кинутий.

Обидва підходи, на мою думку, однаково справедливі, принаймні з точки зору читабельності та пітонічності. Але якщо 90% ваші об'єктів дійсно НЕ мають атрибута barви помітите різницю виразною продуктивності між двома підходами:

>>> import timeit
>>> def askforgiveness(foo=object()):
...     try:
...         x = foo.bar
...     except AttributeError:
...         pass
... 
>>> def askpermission(foo=object()):
...     if hasattr(foo, 'bar'):
...         x = foo.bar
... 
>>> timeit.timeit('testfunc()', 'from __main__ import askforgiveness as testfunc')
2.9459929466247559
>>> timeit.timeit('testfunc()', 'from __main__ import askpermission as testfunc')
1.0396890640258789

Але якщо 90% ваших об'єктів дійсно мають атрибут, таблиці були повернені:

>>> class Foo(object):
...     bar = None
... 
>>> foo = Foo()
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askforgiveness as testfunc, foo')
0.31336188316345215
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askpermission as testfunc, foo')
0.4864199161529541

Отже, з точки зору продуктивності, вам потрібно підібрати підхід, який найкраще підходить для ваших обставин.

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


1
Деякі тижні тому я задав це запитання: programmers.stackexchange.com/questions/161798/… Там я запитав, чи потрібно в мовчасто набраних мовах працювати додатково, перевіряючи тип, і мене обстрілювали люди, які казали, що ви цього не зробили. Знай, я бачу, що ти маєш.
Tulains Córdova

@ user1598390: Коли ви визначаєте API, який очікує однорідну суміш типів, вам доведеться зробити кілька тестів. Здебільшого ви цього не робите. Боюся, це специфічна область, з якої ви не можете отримати правила щодо парадигм в цілому.
Martijn Pieters

Ну, будь-яка серйозна розробка системи передбачає визначення API. Тому я думаю, що для цього найкраще вводити суворі мови, оскільки вам доведеться менше кодувати, оскільки компілятор перевіряє типи для вас під час компіляції.
Tulains Córdova

1
@GarethRees: він встановлює зразок для останньої половини відповіді, де я передаю аргумент тестувальній функції.
Martijn Pieters

1
Зауважте, що для hasattrцього він фактично робить еквівалент С-api спроби, за винятком під кришкою, так як єдиний загальний спосіб визначити, чи має об’єкт атрибут у Python - це спробувати отримати доступ до нього.
user2357112

11

У python ви часто отримуєте кращі показники, роблячи речі Python. Для інших мов використання винятків для контролю потоку зазвичай вважається жахливою ідеєю, оскільки винятки зазвичай накладають надзвичайні витрати. Але оскільки ця методика явно схвалена в Python, інтерпретатор оптимізований для цього типу коду.

Як і у всіх питаннях щодо продуктивності, єдиний спосіб бути певним - це профіль вашого коду. Напишіть обидві версії та подивіться, яка з них працює швидше. Хоча з мого досвіду, "шлях Python", як правило, є найшвидшим способом.


3

Я вважаю, що продуктивність є другорядним питанням. Якщо вона виникає, профілер допоможе вам зосередитись на реальних вузьких місцях, які можуть бути, а можуть і не бути способами, як ви ставитесь до можливих незаконних аргументів.

З іншого боку, зрозумілість та простота - це завжди головна проблема. Тут немає жорстких правил, просто використовуйте своє судження.

Це питання універсальне, але домовленості, що стосуються середовища чи мови, є актуальними. Наприклад, в Python зазвичай добре просто використовувати атрибут, який ви очікуєте, і дозволити можливому AttributeError досягти абонента.


-1

З точки зору правильності , я думаю, що обробка винятків - це шлях (хоча іноді я і сам використовую підхід hasattr (). Основна проблема, покладаючись на hasattr (), полягає в тому, що це перетворює порушення кодових контрактів у тихі відмови (це велика проблема в JavaScript, яка не кидає на неіснуючі властивості).


3
Здається, ваша відповідь не набагато перевищує те, що вже заявили інші. На відміну від інших сайтів, програмісти очікують відповідей, які вивчають причину відповіді. Ви торкаєтесь кращого питання з мовчазною проблемою невдач, але не надаєте належної справедливості. Читання наступного може допомогти: programmers.stackexchange.com/help/how-to-answer

Останню відповідь, яку я зробив, критикували за те, що вона занадто широка. Я думав, що спробую короткий і короткий.
Джо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.