Еще о генераторах
Списки, пожалуй, самая распространенная цель для генераторов, но генераторы работают с любой последовательностью элементов, включая строки. Как и в примерах со списками из предыдущего раздела, если генератор списка выполняется для строки, элементы (то есть символы) строки берутся по одному, обрабатываются согласно заданному выражению и сохраняются в списке.
name = "Peter Python"
uppercased = [character.upper() for character in name]
print(uppercased)['P', 'E', 'T', 'E', 'R', ' ', 'P', 'Y', 'T', 'H', 'O', 'N']
Результат действительно является списком, как и задается квадратными скобками вокруг выражения генератора. Если бы нам нужна была строка, можно было бы использовать строковый метод join, чтобы преобразовать список в строку. Помните, метод вызывается у строки, которую мы хотим использовать как "клей" между символами. Рассмотрим несколько примеров:
name = "Peter"
char_list = list(name)
print(char_list)
print("".join(char_list))
print(" ".join(char_list))
print(",".join(char_list))
print(" and ".join(char_list))['P', 'e', 't', 'e', 'r'] Peter P e t e r P,e,t,e,r P and e and t and e and r
Генераторы списков и метод join упрощают создание новых строк на основе других строк. Например, мы могли бы создать строку, содержащую только гласные из другой строки:
test_string = "Hello there, this is a test!"
vowels = [character for character in test_string if character in "aeiou"]
new_string = "".join(vowels)
print(new_string)eoeeiiae
В примере выше генератор списка и метод join находятся на отдельных строках, но их можно объединить в одно выражение:
test_string = "Hello there, this is a test!"
vowel_string = "".join([character for character in test_string if character in "aeiou"])
print(vowel_string)Многие программисты на Python любят такие однострочные выражения, поэтому определенно стоит научиться их читать. Мы могли бы даже добавить в смесь метод split, чтобы эффективно обрабатывать целые предложения одной инструкцией. В примере ниже из каждого слова предложения удаляется первый символ:
sentence = "Sheila keeps on selling seashells on the seashore"
sentence_no_initials = " ".join([word[1:] for word in sentence.split()])
print(sentence_no_initials)heila eeps n elling eashells n he eashore
Разберем это пошагово:
word[1:]извлекает подстроку со второго символа (с индексом 1) и дальшеsentence.split()разделяет предложение на части по заданному символу. В данном случае аргумент методу не передан, поэтому по умолчанию предложение делится по пробельным символам" ".join()объединяет элементы списка в новую строку, используя пробел между элементами
Более традиционный итеративный подход мог бы выглядеть так:
sentence = "Sheila keeps on selling seashells on the seashore"
word_list = []
words = sentence.split()
for word in words:
word_no_initials = word[1:]
word_list.append(word_no_initials)
sentence_no_initials = " ".join(word_list)
print(sentence_no_initials)Собственные классы и генераторы
Генераторы могут быть полезным инструментом для обработки или создания экземпляров собственных классов, как мы увидим в следующих примерах.
Сначала рассмотрим класс Country, который является простой моделью одной страны с атрибутами имени и населения. В главной функции ниже мы сначала создаем несколько объектов Country, а затем используем генератор списка, чтобы выбрать только те, население которых больше пяти миллионов.
class Country:
""" This class models a single country with population """
def __init__(self, name: str, population: int):
self.name = name
self.population = population
if __name__ == "__main__":
finland = Country("Finland", 6000000)
malta = Country("Malta", 500000)
sweden = Country("Sweden", 10000000)
iceland = Country("Iceland", 350000)
countries = [finland, malta, sweden, iceland]
bigger_countries = [country.name for country in countries if country.population > 5000000]
for country in bigger_countries:
print(country)
Finland Sweden
В генераторе списка выше мы выбрали из объектов Country только атрибут name, поэтому содержимое списка можно было вывести напрямую. Можно было бы также создать новый список самих стран и обращаться к атрибуту name в цикле for. Это было бы полезно, если тот же список стран использовался бы позже в программе или если бы в цикле for нам также был нужен атрибут population:
if __name__ == "__main__":
finland = Country("Finland", 6000000)
malta = Country("Malta", 500000)
sweden = Country("Sweden", 10000000)
iceland = Country("Iceland", 350000)
countries = [finland, malta, sweden, iceland]
bigger_countries = [country for country in countries if country.population > 5000000]
for country in bigger_countries:
print(country.name, country.population)В следующем примере у нас есть класс RunningEvent, который моделирует отдельное соревнование по бегу с атрибутами длины и названия забега. Мы будем использовать генераторы списков для создания объектов RunningEvent на основе списка дистанций.
Параметр name имеет значение по умолчанию в конструкторе класса RunningEvent, поэтому нам не нужно передавать имя в качестве аргумента.
class RunningEvent:
""" The class models a foot race event of a length of n metres """
def __init__(self, length: int, name: str = "no name"):
self.length = length
self.name = name
def __repr__(self):
return f"{self.length} m. ({self.name})"
if __name__ == "__main__":
lengths = [100, 200, 1500, 3000, 42195]
events = [RunningEvent(length) for length in lengths]
# Print out all events
print(events)
# Pick one from the list and give it a name
marathon = events[-1] # the last item in the list
marathon.name = "Marathon"
# Print out everything again, including the new name
print(events)[100 m. (no name), 200 m. (no name), 1500 m. (no name), 3000 m. (no name), 42195 m. (no name)] [100 m. (no name), 200 m. (no name), 1500 m. (no name), 3000 m. (no name), 42195 m. (Marathon)]
Теперь выясним, что делает последовательность элементов пригодной для генератора. В предыдущей части мы узнали, как сделать собственные классы итерируемыми. Именно эта же возможность позволяет использовать и генераторы списков. Если ваш собственный класс итерируемый, его можно использовать как основу для выражения генератора списка. Следующие определения классов скопированы напрямую из части 10:
class Book:
def __init__(self, name: str, author: str, page_count: int):
self.name = name
self.author = author
self.page_count = page_count
class Bookshelf:
def __init__(self):
self._books = []
def add_book(self, book: Book):
self._books.append(book)
# This is the iterator initialization method
# The iteration variable(s) should be initialized here
def __iter__(self):
self.n = 0
# the method returns a reference to the object itself as
# the iterator is implemented within the same class definition
return self
# This method returns the next item within the object
# If all items have been traversed, the StopIteration event is raised
def __next__(self):
if self.n < len(self._books):
# Select the current item from the list within the object
book = self._books[self.n]
# increase the counter (i.e. iteration variable) by one
self.n += 1
# return the current item
return book
else:
# All books have been traversed
raise StopIteration
# Test your classes
if __name__ == "__main__":
b1 = Book("The Life of Python", "Montague Python", 123)
b2 = Book("The Old Man and the C", "Ernest Hemingjavay", 204)
b3 = Book("A Good Cup of Java", "Caffee Coder", 997)
shelf = Bookshelf()
shelf.add_book(b1)
shelf.add_book(b2)
shelf.add_book(b3)
# Create a list containing the names of all books
book_names = [book.name for book in shelf]
print(book_names)
Генераторы и словари
В генераторах нет ничего внутренне "списочного". Результат является списком потому, что выражение генератора заключено в квадратные скобки, которые обозначают список Python. Генераторы так же хорошо работают со словарями Python, если использовать фигурные скобки. Помните, однако, что словарям нужны пары ключ-значение. При создании словаря, в том числе с генераторами, нужно указать и ключ, и значение.
Основой генератора может быть любая итерируемая последовательность: список, строка, кортеж, словарь, любой ваш собственный итерируемый класс и так далее.
В следующем примере мы используем строку как основу словаря. Словарь содержит все уникальные символы строки вместе с количеством их появлений:
sentence = "hello there"
char_counts = {character : sentence.count(character) for character in sentence}
print(char_counts){'h': 2, 'e': 3, 'l': 2, 'o': 1, ' ': 1, 't': 1, 'r': 1}
Принцип выражения генератора точно такой же, как со списками, но вместо одного значения выражение теперь состоит из ключа и значения. Общий синтаксис выглядит так:
{<key expression> : <value expression> for <item> in <series>}
В завершение этого раздела снова посмотрим на факториалы. На этот раз мы сохраняем результаты в словаре. Само число является ключом, а значение - результат факториала из нашей функции:
def factorial(n: int):
""" The function calculates the factorial n! for integers above zero """
k = 1
while n >= 2:
k *= n
n -= 1
return k
if __name__ == "__main__":
numbers = [-2, 3, 2, 1, 4, -10, 5, 1, 6]
factorials = {number : factorial(number) for number in numbers if number > 0}
print(factorials){3: 6, 2: 2, 1: 1, 4: 24, 5: 120, 6: 720}
Текущие баллы можно посмотреть в синем индикаторе в правом нижнем углу страницы.