Чистыйкод дляпродолжающи х
Скачать 7.85 Mb.
|
animals = ['cat', 'dog', 'moose'] >>> for i, animal in enumerate(animals): ... print(i, animal) 0 cat 1 dog 2 moose Код с enumerate() получается чуть более чистым, чем код с range(len()) . Если вам нужны только элементы, но не индексы, все равно можно напрямую перебрать список питоническим способом: >>> # Пример питонического кода >>> animals = ['cat', 'dog', 'moose'] >>> for animal in animals: ... print(animal) cat dog moose Вызов enumerate() с прямым перебором последовательности предпочтительнее использования устаревшей схемы range(len()) Использование команды with вместо open() и close() Функция open() возвращает объект файла, содержащий методы для чтения и запи си в файл. После завершения работы метод close() объекта файла делает файл до- ступным для чтения и записи со стороны других программ. Эти функции можно использовать по отдельности, но такой подход не соответствует питоническому стилю. Например, введите следующий фрагмент в интерактивной оболочке, чтобы вывести текст «Hello, world!» в файл с именем spam.txt : 122 Глава 6.Написание питонического кода >>> # Пример непитонического кода >>> fileObj = open('spam.txt', 'w') >>> fileObj.write('Hello, world!') 13 >>> fileObj.close() Такой код может привести к тому, что файл останется открытым — скажем, если в блоке try возникнет ошибка и вызов close() будет пропущен. Пример: >>> # Пример непитонического кода >>> try: ... fileObj = open('spam.txt', 'w') ... eggs = 42 / 0 # Здесь происходит ошибка деления на нуль. ... fileObj.close() # Эта строка никогда не выполняется. ... except: ... print('Some error occurred.') Some error occurred. При возникновении ошибки деления на нуль выполнение передается в блок except , вызов close() пропускается, а файл останется открытым. Позднее это может при- вести к повреждению структуры файла, причины которой будет трудно связать с блоком try Но можно воспользоваться командой with , чтобы функция close() автоматически вызывалась при выходе управления из блока with . Следующий питонический при- мер делает то же самое, что и первый пример в этом разделе: >>> # Пример питонического кода. >>> with open('spam.txt', 'w') as fileObj: ... fileObj.write('Hello, world!') Несмотря на отсутствие явного вызова close() , команда with обязательно вызовет ее при выходе управления за пределы блока. Использование is для сравнения с None вместо == Оператор == сравнивает значения двух объектов, тогда как оператор is сравнивает два объекта на тождественность. Различия между равенством и тождественно- стью объясняются в главе 7. Два объекта могут хранить одинаковые значения, но если это два разных объекта, они не тождественны. Однако при сравнении значения с None практически всегда следует использовать оператор is вместо оператора == В некоторых случаях выражение spam == None может дать результат True , даже если spam всего лишь содержит None . Это может произойти из-за перегрузки оператора == Форматирование строк 123 (эта тема более подробно рассматривается в главе 17). Но выражение spam is None про- верит, является ли значение, хранящееся в переменной spam , буквальным значением None . Так как None является единственным значением типа данных NoneType , в любой программе Python существует только один объект None . Если переменной присвоено значение None , сравнение is None всегда дает результат True . Особенности перегрузки оператора == рассматриваются в главе 17, но ниже приведен пример такого кода: >>> class SomeClass: ... def __eq__(self, other): ... if other is None: ... return True >>> spam = SomeClass() >>> spam == None True >>> spam is None False Вероятность того, что класс перегружает оператор == подобным образом, невелика, но в Python стало идиоматичным всегда использовать is None вместо == None про- сто на всякий случай. Наконец, оператор is не следует использовать со значениями True и False . Оператор проверки равенства == может использоваться для сравнения значения с True или False (например, spam == True или spam == False ). Еще чаще оператор и логическое значение полностью опускаются, а код записывается в виде if spam: или if not spam: вместо if spam == True: или if spam == False: Форматирование строк Строки встречаются почти во всех компьютерных программах независимо от языка. Это весьма распространенный тип данных, и не приходится удивляться тому, что существует множество подходов к выполнению строковых операций и формати- рованию строк. В этом разделе я показываю пару приемов. Использование необработанных строк, если строка содержит много символов \ (обратный слэш) Escape-символы позволяют вставлять в строковые литералы текст, который в про- тивном случае было бы невозможно включить 1 . Например, в строку 'Zophie\'s 1 Escape-последовательности, то есть последовательности, которые начинаются с симво- ла «\», за которым следует один или более символов, также называют экранированными последовательностями. — Примеч. ред. 124 Глава 6.Написание питонического кода chair' необходимо включить символ \ , чтобы Python интерпретировал вторую кавычку как часть строки, а не как символ, завершающий конец строки. Так как символ \ является escape-символом, если вы хотите включить литеральный сим- вол \ в строку, его необходимо ввести в виде \\ Необработанные (raw) строки представляют собой строковые литералы с префик- сом r ; они не интерпретируют символы \ как escape-символы. Вместо этого в строку просто включается символ \ . Например, следующая строка с путем к файлу Windows должна содержать множество экранированных символов \ , что не соответствует питоническому стилю: >>> # Пример непитонического кода >>> print('The file is in C:\\Users\\Al\\Desktop\\Info\\Archive\\Spam') The file is in C:\Users\Al\Desktop\Info\Archive\Spam Необработанная строка генерирует то же строковое значение, но читается гораздо лучше: >>> # Пример питонического кода >>> print(r'The file is in C:\Users\Al\Desktop\Info\Archive\Spam') The file is in C:\Users\Al\Desktop\Info\Archive\Spam Необработанные строки не определяют другой тип строковых данных; это все- го лишь удобный способ записи строковых литералов, содержащих несколько внутренних символов \ . Необработанные строки часто используются для записи регулярных выражений или путей к файлам Windows с внутренними символами \ , которые было бы слишком неудобно экранировать по отдельности символами \\ Форматирование с использованием F-строк Строковой интерполяцией называется процесс создания строк, включающих другие строки; интерполяция имеет долгую историю в Python. Сначала для конкатенации строк можно было использовать оператор + , но это приводило к появлению кода с множеством кавычек и плюсов: 'Hello, ' + name + '. Today is ' + day + ' and it is ' + weather + '.' . Спецификатор преобразования % несколько упростил этот синтак- сис: 'Hello, %s. Today is %s and it is %s.' % (name, day, weather) . В обоих случаях строки из переменных name, day и weather вставляются в строковые литералы для генерирования нового строкового значения: 'Hello, Al. Today is Sunday and it is sunny.' Строковый метод format() добавляет мини-язык форматных спецификаций (https://docs.python.org/3/library/string.html#formatspec), в котором пары фигурных скобок {} используются способом, напоминающим спецификатор формата %s . Тем не менее это довольно запутанный способ, который может привести к созданию нечитаемого кода, поэтому использовать его я не рекомендую. Поверхностное копирование списков 125 Но с выходом Python 3.6 f-строки (сокращение от format strings) предоставляют более удобный способ создания строк, включающих другие строки. Подобно тому как у необработанных строк перед первой кавычкой ставится префикс r , f-строки помечаются префиксом f . В f-строки можно включать имена переменных в фигур- ных скобках, чтобы вставлять строки, хранящиеся в этих переменных: >>> name, day, weather = 'Al', 'Sunday', 'sunny' >>> f'Hello, {name}. Today is {day} and it is {weather}.' 'Hello, Al. Today is Sunday and it is sunny.' Фигурные скобки могут содержать целые выражения: >>> width, length = 10, 12 >>> f'A {width} by {length} room has an area of {width * length}.' 'A 10 by 12 room has an area of 120.' Если в f-строку нужно включить фигурную скобку как литерал, экранируйте ее дополнительной фигурной скобкой: >>> spam = 42 >>> f'This prints the value in spam: {spam}' 'This prints the value in spam: 42' >>> f'This prints literal curly braces: {{spam}}' 'This prints literal curly braces: {spam}' Так как имена переменных и выражений можно встраивать прямо в строку, код читается лучше, чем со старыми средствами форматирования строк. Все эти разные способы форматирования противоречат тезису из свода правил «Дзен Python», согласно которому должно существовать одно — и желательно только одно — очевидное решение. Но f-строки являются усовершенствованием языка (по моему мнению), а, как указано в других рекомендациях, практичность важнее безупречности. Если вы пишете код только для Python 3.6 и выше, исполь- зуйте f-строки. Если ваш код может выполняться в более ранних версиях Python, придерживайтесь метода format() или спецификаторов преобразования %s Поверхностное копирование списков Синтаксис сегментов позволяет легко создавать новые строки или списки на базе уже существующих. Чтобы увидеть, как это делается, введите следующие команды в интерактивной оболочке: >>> 'Hello, world!'[7:12] # Создание строки из большей строки. 'world' >>> 'Hello, world!'[:5] # Создание строки из большей строки. 'Hello' 126 Глава 6.Написание питонического кода >>> ['cat', 'dog', 'rat', 'eel'][2:] # Создание списка из большего списка. ['rat', 'eel'] Двоеточие ( : ) разделяет начальный и конечный индексы элементов, помещаемые в создаваемый список. Если начальный индекс перед двоеточием не указан (как в 'Hello, world!'[:5] ), то начальный индекс по умолчанию равен 0. Если опустить конечный индекс после двоеточия, как в ['cat', 'dog', 'rat', 'eel'][2:] , то ко- нечным индексом по умолчанию становится конец списка. Если опущены оба индекса, то начальный индекс равен 0 (начало списка), а ко- нечный индекс соответствует концу списка. Фактически эта конструкция создает копию списка: >>> spam = ['cat', 'dog', 'rat', 'eel'] >>> eggs = spam[:] >>> eggs ['cat', 'dog', 'rat', 'eel'] >>> id(spam) == id(eggs) False Обратите внимание: списки spam и eggs не тождественны. Строка eggs = spam[:] создает поверхностную копию списка в spam , тогда как eggs = spam копирует только ссылку на список. Но синтаксис [:] выглядит немного странно, а создание по- верхностной копии списка функцией copy() модуля copy читается значительно лучше: >>> # Пример питонического кода. >>> import copy >>> spam = ['cat', 'dog', 'rat', 'eel'] >>> eggs = copy.copy(spam) >>> id(spam) == id(eggs) False Вы должны знать об этом странном синтаксисе на случай, если вам попадется код Python, в котором он используется, но я не рекомендую применять его в своем коде. Помните, что как [:] , так и вызов copy.copy() создают поверхностные копии. Питонические способы использования словарей Словари играют важную роль во многих программах Python из-за гибкости пар «ключ — значение» (см. главу 7), связывающих один вид данных с другим. А значит, вам пригодятся некоторые словарные идиомы, часто используемые в коде Python. За дополнительной информацией о словарях обращайтесь к великолепным до- кладам программиста Python Брэндона Родса (Brandon Rhodes), посвященным словарям и тому, как они работают: «The Mighty Dictionary» на конференции Питонические способы использования словарей 127 PyCon 2010 (https://invpy.com/mightydictionary) и «The Dictionary Even Mightier» на конференции PyCon 2017 (https://invpy.com/dictionaryevenmightier). Использование get() и setdefault() со словарями Попытка обратиться к несуществующему ключу словаря приводит к ошибке KeyError , поэтому для предотвращения ошибки программисты часто пишут не- питонический код: >>> # Пример непитонического кода >>> numberOfPets = {'dogs': 2} >>> if 'cats' in numberOfPets: # Проверить, существует ли ключ 'cats'. ... print('I have', numberOfPets['cats'], 'cats.') ... else: ... print('I have 0 cats.') I have 0 cats. Этот код проверяет, существует ли строка 'cats' как ключ в словаре numberOfPets Если ключ существует, то вызов print() обращается к numberOfPets['cats'] как части сообщения для пользователя. Если ключ не существует, то другой вызов print() выводит строку без обращения к numberOfPets['cats'] , поэтому исклю- чение KeyError не выдается. Данная схема встречается настолько часто, что у словарей имеется метод get() , который позволяет задать значение по умолчанию, возвращаемое в случае, если ключ не существует в словаре. Следующий питонический код эквивалентен пре- дыдущему примеру: >>> # Пример питонического кода. >>> numberOfPets = {'dogs': 2} >>> print('I have', numberOfPets.get('cats', 0), 'cats.') I have 0 cats. Вызов numberOfPets.get('cats', 0) проверяет, существует ли ключ 'cats' в сло- варе numberOfPets . Если он существует, то вызов метода возвращает значение для ключа 'cats' . Если ключ не существует, вместо значения возвращается второй аргумент 0. Использование метода get() с определением значения по умолчанию, которое должно использоваться для несуществующих ключей, короче и лучше читается, чем решение с командами if - else И наоборот, может потребоваться задать значение по умолчанию, если ключ не существует. Например, если словарь из numberOfPets не содержит ключа 'cats' , команда numberOfPets['cats'] += 10 приводит к ошибке KeyError . Можно доба- вить код, который проверяет возможное отсутствие ключа и задает значение по умолчанию: 128 Глава 6.Написание питонического кода >>> # Пример непитонического кода >>> numberOfPets = {'dogs': 2} >>> if 'cats' not in numberOfPets: ... numberOfPets['cats'] = 0 >>> numberOfPets['cats'] += 10 >>> numberOfPets['cats'] 10 Но этот паттерн встречается настолько часто, что у словарей имеется более пито- нический метод setdefault() . Следующий код эквивалентен предыдущему: >>> # Пример питонического кода. >>> numberOfPets = {'dogs': 2} >>> numberOfPets.setdefault('cats', 0) # Ничего не делать, если 'cats' существует. 0 >>> workDetails['cats'] += 10 >>> workDetails['cats'] 10 Если вы пишете команды if , которые проверяют, существует ли код в словаре, и за- дают значение по умолчанию при его отсутствии, используйте метод setdefault() Использование collections.defaultdict для значений по умолчанию Класс collections.defaultdict можно использовать для полного устранения ошибок KeyError . Этот класс позволяет создать словарь по умолчанию; для этого импортируйте модуль collections и вызовите метод collections.defaultdict() , передав ему тип данных, который должен использоваться для значения по умолча- нию. Например, передавая int методу collections.defaultdict() , можно создать объект, похожий на словарь, в котором 0 используется как значение по умолчанию для несуществующих ключей. Введите следующий фрагмент в интерактивной оболочке: >>> import collections >>> scores = collections.defaultdict(int) >>> scores defaultdict( >>> scores['Al'] += 1 # Не нужно сначала задавать значение для ключа 'Al'. >>> scores defaultdict( >>> scores['Zophie'] # Не нужно сначала задавать значение для ключа 'Zophie'. 0 >>> scores['Zophie'] += 40 >>> scores defaultdict( Питонические способы использования словарей 129 Обратите внимание: вы передаете функцию int() , а не вызываете ее, что позволяет опустить круглые скобки после int в collections.defaultdict(int) . Также можно передать список, который будет использоваться как пустой список, в значении по умолчанию. Введите следующий фрагмент в интерактивной оболочке: >>> import collections >>> booksReadBy = collections.defaultdict(list) >>> booksReadBy['Al'].append('Oryx and Crake') >>> booksReadBy['Al'].append('American Gods') >>> len(booksReadBy['Al']) 2 >>> len(booksReadBy['Zophie']) # Значение по умолчанию - пустой список. 0 Если вам нужно значение по умолчанию для каждого возможного ключа, исполь- зовать collections.defaultdict() намного проще, чем использовать обычный словарь и многократно вызывать метод setdefault() Использование словарей вместо команды switch В таких языках, как Java, существует команда switch — разновидность команды if-elif - else , выполняющей код в зависимости от того, какое из многих значений содержит конкретная переменная. В Python нет команды switch , поэтому програм- мисты Python иногда пишут такой код, как в следующем примере. Он выполняет разные команды присваивания в зависимости от того, какое из многих значений содержит переменная season : # Все следующие условия if и elif содержат "season ==": if season == 'Winter': holiday = 'New Year\'s Day' elif season == 'Spring': holiday = 'May Day' elif season == 'Summer': holiday = 'Juneteenth' elif season == 'Fall': holiday = 'Halloween' else: holiday = 'Personal day off' Этот код не обязательно является непитоническим, но он получается слишком длинным. По умолчанию команды switch в Java работают по принципу «сквозного прохождения», из-за которого каждый блок должен завершаться командой break В противном случае выполнение продолжается в следующем блоке. Забытые коман- ды break часто становятся источником ошибок. Но обилие команд if - elif в нашем примере выглядит однообразно. Некоторые программисты Python предпочитают 130 Глава 6.Написание питонического кода создать словарь, вместо того чтобы использовать команды if - elif . Следующий компактный и питонический код эквивалентен следующему примеру: holiday = {'Winter': 'New Year\'s Day', 'Spring': 'May Day', 'Summer': 'Juneteenth', 'Fall': 'Halloween'}.get(season, 'Personal day off') Этот код является одной командой присваивания. В holiday сохраняется возвра- щаемое значение вызова метода get() , который возвращает значение для ключа, присвоенного season . Если ключ season не существует, то get() возвращает строку 'Personal day off' . Использование словаря делает код более компактным, но также усложняет чтение кода. Решайте сами, хотите вы использовать этот паттерн или нет. |