Начало
"Smooth Python" — книга, подходящая для продвинутого python. Основные введения в ней — это продвинутое использование python. Для начинающих python, вероятно, достаточно основ, но они часто забывают об этом, потому что этого достаточно. Углубленно, чтобы освоить. Мы надеемся получить всестороннее представление о границах возможностей этого языка. Возможно, некоторые расширенные функции невозможно освоить и использовать сразу. Поэтому эта книга предназначена для людей, у которых есть свободные возможности, чтобы прочитать ее после работы. Я буду использовать ее здесь. Полезно , тонкий расширенный контент разобрался.
Эта книга состоит из 21 главы, и организация также основана на этих главах.
Глава 1: модель данных Python
В этой части в основном представлены магические методы Python, которые часто называются двумя символами подчеркивания (например,__init__
, __lt__
, __len__
).Эти специальные методы предназначены для вызова интерпретатором python, и эти методы будут зарегистрированы в коллекции методов их типов, что эквивалентно предоставлению ярлыка для cpython.Эти методы также быстрее, чем обычные методы, конечно , по-своему Когда вы знаете цель этих магических методов, не добавляйте их по своему желанию.
Есть две формы представления строк,__str__
и__repr__
. встроенные функции Pythonrepr
через__repr__
Этот специальный метод для получения строкового представления объекта. Обычно используется в интерактивном режиме, если не реализован.__repr__
, когда консоль выводит объект часто<A object at 0x000>
. и__str__
являетсяstr()
функцию или вprint
Функция вызывается только при печати объекта, что удобно для конечного пользователя.
Между ними есть еще одно различие: при форматировании строк"%s"
соответствует__str__
. и"%r"
соответствует__repr__
. __str__
и__repr__
При использовании рекомендуется, чтобы первый был для конечных пользователей, а второй нам удобнее для отладки и записи журналов.
Более специальные методы:docs.Python.org/3/reference…
Глава 2: Массивы последовательностей
Эта часть в основном знакомит с последовательностями, фокусируясь на некоторых продвинутых способах использования массивов и кортежей.
Последовательности можно разделить на:
-
容器序列
: list, tuple и collections.deque. Эти последовательности могут содержать разные типы данных. -
扁平序列
: str, bytes, bytearray, memoryview и array.array, такие последовательности могут содержать только один тип.
В зависимости от того, можно ли его модифицировать, его можно разделить на:
-
可变序列
: list, bytearray, array.array, collections.deque и memoryview -
不可变序列
: кортеж, строка и байты
понимание списка
Понимание списков — это ярлыки для создания списков, которые более удобочитаемы и эффективны.
Например, пример преобразования строки в список кодовых точек юникода, как правило:
symbols = '$¢£¥€¤'
codes = []
for symbol in symbols:
codes.append(ord(symbol))
Используйте понимание списка:
symbols = '$¢£¥€¤'
codes = [ord(symbol) for symbol in symbols]
Включение списков можно использовать для создания списка, использовать вставки, когда это возможно, и делать их короткими.
Декартово произведение и генераторные выражения
Выражения-генераторы могут создавать элементы один за другим, экономя память, например:
>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes):
... print(tshirt)
В примере мало элементов списка, если его заменить двумя списками по 1000 элементов в каждом, то очевидно, что декартово произведение этой комбинации будет списком из 1 миллиона элементов, который будет занимать много памяти. используйте выражение генератора. Это может помочь сократить накладные расходы на циклы for.
именованный кортеж
Кортежи часто используются как不可变列表
Представление , Часто для получения элемента требуется только числовой индекс, но он также может дать элементу имя:
>>> from collections import namedtuple
>>> City = namedtuple('City', 'name country population coordinates')
>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
>>> tokyo
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722,
139.691667))
>>> tokyo.population
36.933
>>> tokyo.coordinates
(35.689722, 139.691667)
>>> tokyo[1]
'JP'
кусочек
Индекс первого элемента в списке равен 0, и срез может извлечь определенный сегмент в соответствии с индексом.
использоватьs[a:b:c]
образуют паруs
существуетa
иb
междуc
значение интервала.c
Значение также может быть отрицательным, отрицательное значение означает обратное значение.
>>> s = 'bicycle'
>>> s[::3]
'bye'
>>> s[::-1]
'elcycib'
>>> s[::-2]
'eccb'
Глава 3: Словари и коллекции
dict
Типы не только широко используются в различных программах, но иPython
Краеугольный камень языка Именно потому, чтоdict
Тип важен,Python
Его реализация была сильно оптимизирована.Самая важная причина заключается в том, что набор (коллекция) «хеш-таблицы» за ним такой же, как и dict, и его основа реализации также зависит от хеш-таблицы.
Хеш-таблицу также называют хеш-таблицей. Для типа dict ее ключ должен быть хешируемым типом данных. Что такое хешируемый тип данных? Его официальное объяснение:
Если объект можно хешировать, его хеш-значение не меняется в течение всего времени существования объекта.
, и этот объект должен реализовать __hash__()
метод. Кроме того, хешируемые объекты также имеют__qe__()
метод, чтобы его можно было сравнить с другими ключами. Если два хэшируемых объекта равны, то их хэш-значение должно быть одинаковым...
str
, bytes
, frozenset
и数值
Оба являются хешируемыми типами.
понимание словаря
DIAL_CODE = [
(86, 'China'),
(91, 'India'),
(7, 'Russia'),
(81, 'Japan'),
]
### 利用字典推导快速生成字典
country_code = {country: code for code, country in DIAL_CODE}
print(country_code)
'''
OUT:
{'China': 86, 'India': 91, 'Russia': 7, 'Japan': 81}
'''
defaultdict: опция обработки ключей не найдена
Мы также хотим получить значение по умолчанию, когда ключ отсутствует на карте.defaultdict
, этоdict
Подкласс , и реализует__missing__
метод.
import collections
index = collections.defaultdict(list)
for item in nums:
key = item % 2
index[key].append(item)
Вариант словаря
Стандартный карриcollections
модуль, кромеdefaultdict
Различные типы сопоставления, кроме:
- OrderDict: Этот тип сохраняет порядок при добавлении ключей, поэтому порядок итерации ключей всегда согласован.
- ChainMap: Этот тип может содержать несколько разных объектов сопоставления.При поиске ключа эти объекты будут искаться один за другим как единое целое, пока ключ не будет найден
pylookup = ChainMap(locals(), globals())
- Counter: этот тип карты подготавливает целочисленный счетчик для ключа и увеличивает счетчик каждый раз при обновлении ключа, поэтому этот тип можно использовать для подсчета объектов хеш-таблицы или в качестве мультимножества.
import collections
ct = collections.Counter('abracadabra')
print(ct) # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
ct.update('aaaaazzz')
print(ct) # Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
print(ct.most_common(2)) # [('a', 10), ('z', 3)]
- UserDict: этот класс фактически снова реализует стандартный dict в чистом Python.
import collections
class StrKeyDict(collections.UserDict):
def __missing__(self, key):
if isinstance(key, str):
raise KeyError(key)
return self[str(key)]
def __contains__(self, key):
return str(key) in self.data
def __setitem__(self, key, item):
self.data[str(key)] = item
Неизменяемые типы карт
Когда дело доходит до неизменяемости, первое, что приходит на ум, это кортежи, но для словарей, чтобы сделать соответствие между ключом и значением неизменяемым,types
модульныйMappingProxyType
сможет сделать:
from types import MappingProxyType
d = {1:'A'}
d_proxy = MappingProxyType(d)
d_proxy[1]='B' # TypeError: 'mappingproxy' object does not support item assignment
d[2] = 'B'
print(d_proxy) # mappingproxy({1: 'A', 2: 'B'})
d_proxy
динамичный, то естьd
Любые внесенные изменения будут возвращены в него.
теория множеств
Суть набора состоит в агрегации множества уникальных объектов, поэтому набор можно использовать для дедупликации, элементы в наборе должны быть хешируемыми, ноset
сам по себе не хэшируется, иfrozenset
сам может быть хеширован.
Наборы уникальны, и в то же время наборы реализуют многие основные инфиксные операторы.Для двух наборов a и b,a | b
возвращение
Это коллекция их,a & b
то, что получается, является пересечением, иa - b
То, что вы получаете, является набором разностей.
Разумное использование этих возможностей может не только сократить объем кода, но и повысить эффективность работы.
# 集合的创建
s = set([1, 2, 2, 3])
# 空集合
s = set()
# 集合字面量
s = {1, 2}
# 集合推导
s = {chr(i) for i in range(23, 45)}
Глава 4: Текстовые и байтовые последовательности
В этой главе обсуждаются текстовые строки и последовательности байтов, а также некоторые преобразования кодировки.str
Относится к python3.
проблема характера
Строки — относительно простая концепция: строка — это последовательность символов."字符"
Определение разнообразно, среди которых,"字符"
Лучшее определениеUnicode 字符
, Поэтому в python3str
Элементы, полученные в объекте, представляют собой символы Юникода.
Процесс преобразования кодовых точек в последовательность байтов编码
, процесс преобразования последовательности байтов в кодовые точки编码
:
>>> s = 'café'
>>> len(s)
4
>>> b = s.encode('utf8')
>>> b
b'caf\xc3\xa9'
>>> len(b)
5
>>> b.decode('utf8') #'café
Кодовые точки можно рассматривать как удобочитаемый текст, в то время как последовательности символов можно рассматривать как более удобные для машин..decode()
и.encode()
Это также очень просто: от последовательности байтов до текста, понятного человеку, — это декодирование, а последовательность байтов, которую человек может понять, в последовательность байтов, непонятную человеку, — кодирование.
Сводка по байтам
python3 имеет две последовательности байтов, неизменяемыеbytes
типы и изменяемыеbytearray
Тип.Каждый элемент в последовательности байтов находится между[0, 255]
целые числа между .
Решение проблем с кодировкой
Python поставляется с более чем 100 кодеками, у каждого кодека есть имя, а у некоторых даже есть псевдонимы, напримерutf_8
имеютutf8
, utf-8
, U8
эти псевдонимы.
Легко выбросить при декодировании или кодировании, если последовательность символов не соответствует ожидаемой.Unicode*Error
Исключение.Эта ошибка вызвана тем, что символ не определен в целевой кодировке (символ, соответствующий кодовой точке, не определен), вот как решить эту проблему.
- С python3 python3 может избежать 95% проблем с символами.
- Попробуйте основную кодировку: latin1, cp1252, cp437, gb2312, utf-8, utf-16le.
- Обратите внимание на заголовок спецификации
b'\xff\xfe'
, последовательности в кодировке UTF-16 также будут иметь эти дополнительные байты в начале. - Чтобы узнать кодировку последовательности, рекомендуется использовать
codecs
модуль
Нормализовать строки юникода
s1 = 'café'
s2 = 'caf\u00e9'
Эти две строки кода полностью эквивалентны. Одной вещи, которую следует избегать, является то, что в стандарте Unicodeé
иe\u0301
Такая последовательность называется"标准等价物"
, В этом случае используется NFC для формирования эквивалентной строки с использованием наименьшего количества кодовых точек:
>>> s1 = 'café'
>>> s2 = 'cafe\u0301'
>>> s1, s2
('café', 'café')
>>> len(s1), len(s2)
(4, 5)
>>> s1 == s2
False
После улучшения:
>>> from unicodedata import normalize
>>> s1 = 'café' # 把"e"和重音符组合在一起
>>> s2 = 'cafe\u0301' # 分解成"e"和重音符
>>> len(s1), len(s2)
(4, 5)
>>> len(normalize('NFC', s1)), len(normalize('NFC', s2))
(4, 4)
>>> len(normalize('NFD', s1)), len(normalize('NFD', s2))
(5, 5)
>>> normalize('NFC', s1) == normalize('NFC', s2)
True
>>> normalize('NFD', s1) == normalize('NFD', s2)
True
сортировка текста юникод
Для строк — кодовая точка сравнения, поэтому для символов, отличных от ascii, результат может быть неудовлетворительным.
Глава 5: Функции первого класса
В Python функции являются объектами первого класса."一等对象"
Определяется как удовлетворяющий следующим условиям:
- Создано во время выполнения
- Может быть назначен переменной или элементу в структуре данных
- Может быть передан как параметр функции
- Может использоваться как возвращаемый результат функции
В Python целые числа, строки, списки и словари являются объектами первого класса.
относиться к функциям как к объектам
Python может быть функциональным программированием, а также объектно-ориентированным программированием.Здесь мы создаем функцию, а затем читаем ее__doc__
свойства и определить, что объект функции на самом делеfunction
Экземпляр класса:
def factorial(n):
'''
return n
'''
return 1 if n < 2 else n * factorial(n-1)
print(factorial.__doc__)
print(type(factorial))
print(factorial(3))
'''
OUT
return n
<class 'function'>
6
'''
Функции высшего порядка
Функция высшего порядка — это функция, которая принимает функцию в качестве параметра, или функция, возвращающая результат.map
, filter
, reduce
Ждать.
например, вызовsorted
когда будетlen
Передано как параметр:
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
sorted(fruits, key=len)
# ['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']
анонимная функция
lambda
Ключевое слово используется для создания анонимных функций. Существуют некоторые ограничения для анонимных функций. Тело определения анонимных функций может использовать только чистые выражения. Другими словами,lambda
Вы не можете присваивать значения внутри функций, и вы не можете использовать такие операторы, как while и try.
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
sorted(fruits, key=lambda word: word[::-1])
# ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
вызываемый объект
За исключением пользовательских функций, оператор вызова()
Его также можно применять к другим объектам.Если вы хотите определить, можно ли вызывать объект, вы можете использовать встроенныйcallable()
Функция для суждения.В Python существует 7 видов моделей данных, которые можно вызвать:
- Пользовательские функции: создаются с помощью операторов def или лямбда-выражений.
- Встроенные функции: такие как len
- Встроенные методы: например, dict.get
- Методы: функции в теле определения класса
- своего рода
- экземпляр класса: если класс определяет
__call__
, то его экземпляр можно вызвать как функцию. - Функция генератора: использовать
yield
Функция или метод ключевого слова.
От позиционных параметров к параметрам только ключевых слов
Это вариативные и ключевые аргументы:
def fun(name, age, *args, **kwargs):
pass
в*args
и**kwargs
Оба являются итерируемыми объектами, которые отображаются в один аргумент после раскрытия: args — это кортеж, а kwargs — словарь.
Глава 6. Реализация шаблонов проектирования с использованием первоклассных функций
Хотя шаблоны проектирования не зависят от языка, это не означает, что каждый шаблон можно использовать на любом языке.Gamma et al.《设计模式:可复用面向对象软件的基础》
в книге есть23
узоры, в том числе16
Это «ушло или упрощено» в динамических языках.
Я не привожу здесь примеры шаблонов проектирования, потому что шаблоны в книге используются редко.
Глава 7: Декораторы функций и замыкания
Декораторы функций используются для «пометки» функции в исходном коде, тем самым улучшая ее поведение. Это мощный
Да, но чтобы освоить его, вы должны понимать замыкания.
Декораторы и замыкания часто обсуждаются вместе, потому что декораторы — это форма замыканий, а также основа для асинхронного программирования в стиле обратного вызова и стилей функционального программирования.
Основы декоратора
Декоратор — это вызываемый объект, аргументом которого является другая функция (декорированная функция).Декоратор может обрабатывать декорированную функцию и возвращать ее или заменять ее другой функцией или вызываемым объектом.
@decorate
def target():
print('running target()')
Это письмо точно эквивалентно следующему письму:
def target():
print('running target()')
target = decorate(target)
Декоратор — это синтаксический сахар, он фактически принимает функции в качестве параметров для обработки другими функциями.Декораторы имеют две характеристики:
- Замените украшенную функцию другой функцией
- Декоратор выполняется сразу после загрузки модуля.
Чтобы понять немедленное выполнение, посмотрите на эквивалентный код,target = decorate(target)
Это предложение вызывает функцию. В общем случае оформленная функция принимает функцию в качестве возвращаемого значения.
правила области видимости переменных
Чтобы понять область действия переменных в декораторе, вы должны понимать замыкания.Я думаю, что лучше изменить порядок замыканий и областей действия в книге.В питоне порядок поиска переменнойLEGB
(L: Локальная локальная среда, E: Охватывающее закрытие, G: Глобальная глобальная, B: Встроенная встроенная).
base = 20
def get_compare():
base = 10
def real_compare(value):
return value > base
return real_compare
compare_10 = get_compare()
print(compare_10(5))
функция закрытияreal_compare
, используемые переменныеbase
Фактическиbase = 10
Да. Поскольку базовая переменная может попасть в замыкание, нет необходимости переходить кglobal
получено в.
Закрытие
Замыкания на самом деле довольно просты для понимания, но когда появляются анонимные функции, это затрудняет понимание этой части.Короткое и простое объяснение замыканий:
Результат связывания пространства имен с функцией называется замыканием.
Это пространство именLEGB
серединаE
Таким образом, замыкания не просто используют функции в качестве возвращаемых значений. Они объединяют пространства имен и функции в качестве возвращаемых значений. Сколько людей забывают это понимать."捆绑"
, я не знаю, где и где переменная окончательно берется Эй.
Декораторы в стандартной библиотеке
В Python есть три встроенные функции для декорирования методов:property
,classmethod
иstaticmethod
.
Они используются для обогащения классов.
class A(object):
@property
def age():
return 12
Глава 8. Ссылки на объекты, изменчивость и сборка мусора
Переменные — это не коробки
Многие люди думают о переменных как о ящиках, просто бросьте в ящик любые данные, которые хотите.
a = [1,2,3]
b = a
a.append(4)
print(b) # [1, 2, 3, 4]
Переменнаяa
иb
Относится к тому же списку, а не к копии этого списка, поэтому операторы присваивания следует понимать как относящиеся к переменным и значениям.
Идентичность, равенство и псевдонимы
Чтобы узнать, являются ли переменные a и b ссылками на одно и то же значение, используйтеis
судить:
>>> a = b = [4,5,6]
>>> c = [4,5,6]
>>> a is b
True
>>> x is c
False
Если обе переменные относятся к одному и тому же объекту, мы обычно говорим, что переменная является другой переменной.别名
.
выбрать между == и есть
оператор==
Он используется для оценки того, равны ли два значения объекта (обратите внимание на значение объекта).is
Он используется, чтобы судить о том, указывают ли две переменные на один и тот же объект, или чтобы судить о том, являются ли переменные псевдонимами двух, не заботясь о значении объекта.==
использовал больше иis
Скорость выполнения выше.
Поверхностное копирование по умолчанию
l1 = [3, [55, 44], (7, 8, 9)]
l2 = list(l1) # 通过构造方法进行复制
l2 = l1[:] #也可以这样想写
>>> l2 == l1
True
>>> l2 is l1
False
Хотя l2 является копией l1, процесс копирования заключается в копировании первым (то есть копируется самый внешний контейнер, а элементы в копии являются ссылками на элементы в исходном контейнере). Поэтому, когда l2[1] при работе l1[1] также изменится соответственно.И если все элементы в списке неизменяемые, то такой проблемы нет, и это может сэкономить память.Однако, если есть изменяемые элементы, это может вызвать непредвиденные проблемы.
В стандартной библиотеке Python есть два инструмента.copy
иdeepcopy
.. используются для мелких и глубоких копий соответственно:
import copy
l1 = [3, [55, 44], (7, 8, 9)]
l2 = copy.copy(l1)
l2 = copy.deepcopy(l1)
При обращении к параметрам функции
Все параметры функции в python используют общие параметры.Общие параметры относятся к каждому формальному параметру функции, чтобы получить копию каждой ссылки в фактическом параметре.То есть формальный параметр внутри функции является псевдонимом фактического параметра.
Эта схема заключается в том, что когда входящий параметр является изменяемым объектом, модификация параметра внутри функции является модификацией внешнего изменяемого объекта, но такой тип параметра недействителен при попытке переназначить его новому объекту, потому что он только Это эквивалентно использованию параметра в качестве ссылки на другую вещь, и исходный объект не изменяется, То есть внутри функции параметр не может заменить один объект другим объектом.
Не используйте изменяемые типы в качестве значений по умолчанию для параметров
Значения параметров по умолчанию — отличная функция. Разработчикам следует избегать изменяемых объектов в качестве значений параметров по умолчанию, потому что, если значение параметра по умолчанию является изменяемым объектом, а его содержимое изменено, это повлияет на последующие вызовы функций.
del и сборка мусора
В python, когда объект теряет свою последнюю ссылку, он обрабатывается как мусор, а затем перерабатывается.del
Оператор используется для удаления переменной, но на самом деле он удаляет только связь между переменной и объектом и не обязательно позволяет повторно использовать объект, потому что могут быть другие ссылки на объект.
В CPython при сборке мусора в основном используется алгоритм подсчета ссылок.Каждый объект будет подсчитывать, сколько ссылок указывает на себя.Когда счетчик ссылок равен нулю, это означает, что объект не используется, и объект будет немедленно уничтожен.
Объект в стиле Python
Благодаря модели данных Python пользовательские типы могут вести себя так же естественно, как и встроенные типы. добиться такого естественного
Поведение не по наследству, а по утиной типизации: мы просто реализуем методы, которые нужны объекту, в соответствии с предопределенным поведением.
представление объекта
Каждый объектно-ориентированный язык имеет по крайней мере один стандартный способ получения строкового представления объекта. Python предоставляет два способа.
-
repr()
: возвращает строковое представление объекта в удобном для понимания разработчиком виде. -
str()
: возвращает строковое представление объекта в удобном для понимания пользователем виде.
метод класса и статический метод
Эти два декоратора являются встроенными декораторами, предоставляемыми Python, и эти два декоратора не упоминаются в общих руководствах по Python.Оба они находятся в классеclass
Обычно используемая в определении функция, определенная в классе, привязана к экземпляру своего класса.Эти два декоратора могут изменить этот вызывающий метод.
Первый взглядclassmethod
, этот декоратор не является методом манипулирования экземпляром и принимает в качестве первого параметра сам класс.staticmethod
Декоратор также меняет способ вызова метода, это просто обычная функция,
classmethod
иstaticmethod
Разница в том,classmethod
Сам класс передается в качестве первого параметра, а все остальное то же самое.
Взгляните на пример:
>>> class Demo:
... @classmethod
... def klassmeth(*args):
... return args
... @staticmethod
... def statmeth(*args):
... return args
...
>>> Demo.klassmeth()
(<class '__main__.Demo'>,)
>>> Demo.klassmeth('spam')
(<class '__main__.Demo'>, 'spam')
>>> Demo.statmeth()
()
>>> Demo.statmeth('spam')
('spam',)
форматированный дисплей
Встроенныйformat()
функция иstr.format()
метод делегирует форматирование каждого типа соответствующему.__format__(format_spec)
метод.format_spec
является спецификатором формата, который имеет вид:
-
format(my_obj, format_spec)
второй параметр -
str.format()
Строка формата метода, часть после двоеточия в поле подстановки в {}
Частные и «защищенные» атрибуты Python
В python нет такой вещи, как переменные экземпляраprivate
Такие модификаторы для создания приватных свойств, в питоне есть простой механизм для работы с приватными свойствами.
class A:
def __init__(self):
self.__x = 1
a = A()
print(a.__x) # AttributeError: 'A' object has no attribute '__x'
print(a.__dict__)
Если свойство начинается с__name
из两个下划线为前缀, 尾部最多一个下划线
Именованные атрибуты экземпляра, python добавит подчеркивание и имя класса перед своим именем, а затем поместит его в__dict__
в, с__name
Например, становится_A__name
.
Изменение имени — это мера безопасности, но она не гарантирует надежной работы, она предотвратит случайный доступ, но не предотвратит преднамеренные плохие вещи.
Пока они знают механизм приватных атрибутов, любой может напрямую читать и переписывать приватные атрибуты, поэтому многие программисты на Python строго оговаривают:遵守使用一个下划线标记对象的私有属性
. Интерпретатор Python не будет специально обрабатывать имена атрибутов, в которых используется одиночное подчеркивание, и программист должен не обращаться к этим атрибутам за пределами класса.Этот метод также рекомендуется, и метод двойного подчеркивания больше не используется. , Цитируя слова бога питона:
Никогда не используйте два начальных символа подчеркивания, это раздражающе эгоистично. Если вы беспокоитесь о конфликтах имен, вы должны указать
Обязательно используйте переписанное имя (например, _MyThing_blahblah). На самом деле это то же самое, что и использование двойных подчеркиваний, но ваши собственные правила легче понять, чем двойные подчеркивания.
Атрибуты, помеченные префиксом подчеркивания, в Python называются «защищенными» атрибутами.
использоватьslotsАтрибуты класса экономят место
По умолчанию Python использует__dict__
В словаре хранятся атрибуты экземпляра.Поэтому атрибуты экземпляра динамически изменяются, а атрибуты могут добавляться произвольно во время выполнения.Словарь представляет собой структуру, которая потребляет много памяти.Поэтому, когда определяется имя атрибута объекта, использовать__slots__
Может сохранить память.
class Vector2d:
__slots__ = ('__x', '__y')
typecode = 'd'
# 下面是各个方法(因排版需要而省略了)
определить в классе __slots__
Назначение атрибутов — сообщить интерпретатору: «Все атрибуты экземпляра в этом классе находятся здесь.
! " Таким образом, Python использует структуру, подобную кортежу, для хранения переменных экземпляра в каждом экземпляре, избегая использования
потребление памяти__dict__
Свойство, Это может сэкономить много памяти, если одновременно активны миллионы экземпляров.
Глава 10: Модификация, хэширование и нарезка последовательностей
Протоколы и утиная типизация
В python тип последовательности не требует использования наследования, ему нужен только метод, который соответствует протоколу последовательности.Протокол здесь является реализацией__len__
и__getitem__
Два метода Любой класс, реализующий эти два метода, удовлетворяет операции последовательности, поскольку ведет себя как последовательность.
Протоколы неформальны и не имеют принудительного исполнения, поэтому вы знаете конкретный вариант использования класса, и обычно вам нужно реализовать только часть протокола.Например, для поддержки итерации просто реализуйте__getitem__
метод, нет необходимости предоставлять__len__
метод, объясняющий鸭子类型
:
Когда вы увидите птицу, которая ходит, как утка, плавает, как утка, и крякает, как утка,
тогда птицу можно назвать уткой
нарезная последовательность
Срез (Slice) используется для получения элементов определенного диапазона последовательности.Операция среза также выполняется__getitem__
завершить:
class Vector:
# 省略了很多行
# ...
def __len__(self):
return len(self._components)
# 省略了很多
def __getitem__(self, index):
cls = type(self) # 获取实例的类型
if isinstance(index, slice): # 如果index参数值是切片的对象
# 调用Vector的构造方法,建立一个新的切片后的Vector类
return cls(self._components[index])
elif isinstance(index, numbers.Integral): # 如果参数是整数类型
return self._components[index] # 我们就对数组进行切片
else: # 否则我们就抛出异常
msg = '{cls.__name__} indices must be integers'
raise TypeError(msg.format(cls=cls))
свойство динамического доступа
Атрибуты получаются путем доступа к имени компонента:
shortcut_names = 'xyzt'
def __getattr__(self, name):
cls = type(self) # 获取类型
if len(name) == 1: # 判断属性名是否在我们定义的names中
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self._components):
return self._components[pos]
msg = '{} objects has no attribute {}'
raise AttributeError(msg.format(cls, name))
test = Vector([3, 4, 5])
print(test.x)
print(test.y)
print(test.z)
print(test.c)
Хеширование и быстрый тест эквивалентности
выполнить__hash__
метод. плюс существующий__eq__
метод, который превращает экземпляр в хешируемый объект.
Когда последовательность многомерна, у нас есть более эффективный метод:
def __eq__(self, other):
if len(self) != len(other): # 首先判断长度是否相等
return False
for a, b in zip(self, other): # 接着逐一判断每个元素是否相等
if a != b:
return False
return True
# 我们也可以写的漂亮点
def __eq__(self, other):
return (len(self) == len(other)) and all(a == b for a, b in zip(self, other))
Глава 11: Интерфейсы: от протоколов к абстрактным базовым классам
Эти протоколы определяются как неформальные интерфейсы и являются способом сделать языки программирования полиморфными.В питоне нетinterface
ключевое слово, и, кроме абстрактного базового класса, каждый класс имеет интерфейс: все классы могут реализовывать себя__getitem__
и__add__
.
Существуют правила написания, которые постепенно обобщаются программистами в процессе разработки, например, одно начальное подчеркивание для именования защищенных атрибутов и некоторые стандарты кодирования.
Протоколы являются интерфейсами, но не формальными, эти положения не являются обязательными, класс может реализовывать только часть интерфейса, которая разрешена.
Так как есть неформальное соглашение, есть ли официальное соглашение?Да, абстрактный базовый класс является обязательным соглашением.
Абстрактный базовый класс требует, чтобы его подклассы реализовывали определенный интерфейс, и абстрактный базовый класс не может быть создан.
Интерфейсы и протоколы в культуре Python
До введения абстрактных базовых классов python был очень успешным, и даже сейчас абстрактные базовые классы используются редко.Благодаря утиной типизации и протоколам мы определяем протоколы как неформальные интерфейсы, именно так python достигает полиморфизма.
С другой стороны, не думайте, что неуместно размещать общедоступные свойства данных в интерфейсе объекта.При необходимости вы всегда можете реализовать методы чтения и установки, чтобы преобразовать свойства данных в свойства.Объекты сами предоставляют методы и позволяют объектам быть в системе, играть определенную роль, поэтому интерфейс представляет собой совокупность методов, реализующих определенную роль.
Протокол последовательности является одним из самых основных протоколов в python, и даже если объект реализует только самые основные части этого протокола, интерпретатор будет обращаться с ним ответственно.
Водоплавающие и абстрактные базовые классы
Утиная типизация полезна во многих ситуациях, но по мере ее развития часто появляются лучшие способы.
В новое время роды и виды в основном классифицируют по фенотипической филогении.Утиные относятся к водоплавающим, а к водоплавающим относятся еще гуси, гуси-лебеди и др. Водоплавающие - это классификация продуктивности определенного класса, и они имеют некоторое унифицированное" описание " части.
Следовательно, согласно эволюции таксономии, должен существовать тип водоплавающих птиц, покаcls
является абстрактным базовым классом, т.е.cls
Метаклассabc.ABCMeta
, ты можешь использоватьisinstance(obj, cls)
судить.
По сравнению с конкретными классами абстрактные базовые классы имеют много теоретических преимуществ.Зарегистрированные классы должны удовлетворять требованиям абстрактных базовых классов в отношении методов и сигнатур и, что более важно, соответствовать лежащему в их основе семантическому контракту.
абстрактные базовые классы в стандартной библиотеке
Большинство абстрактных базовых классов стандартной библиотеки находятся вcollections.abc
Определено в модуле. Некоторые находятся вnumbers
иio
В пакете есть несколько абстрактных базовых классов, в стандартной библиотеке их два.abc
модуль, обсуждаемый только здесьcollections.abc
.
В этом модуле определено 16 абстрактных базовых классов.
Итерируемый, контейнерный и размерный
Коллекции должны наследовать эти три абстрактных базовых класса или, по крайней мере, реализовывать совместимые протоколы.Iterable
пройти через__iter__
Метод поддерживает итерацию, проходы контейнера__contains__
Метод поддерживает в операторе, Sized
пройти через__len__
Метод поддерживает функцию len().
Последовательности, сопоставления и наборы
Эти три типа являются основными неизменяемыми типами коллекций, и каждый из них имеет изменяемые подклассы.
MappingView
В Python 3 метод сопоставления.items()
,.keys()
и.values()
Возвращаемые объекты
Экземпляры ItemsView, KeysView и ValuesView. Первые два класса также наследуют расширенные интерфейсы от класса Set.
рот.
Вызываемый и хэшируемый
Эти два абстрактных базовых класса имеют мало общего с коллекциями просто потому, чтоcollections.abc
есть в стандартной библиотеке
Первый модуль, который определяет абстрактные базовые классы, и они слишком важны, чтобы помещать их вcollections.abc
в модуле. я никогда не виделCallable
илиHashable
подкласс . Основная роль этих двух абстрактных базовых классов заключается в обеспечении внутреннего
установить функциюisinstance
Предоставляет поддержку для определения того, может ли объект вызываться или хэшироваться безопасным способом.
Iterator
Обратите внимание, что это подкласс Iterable.
Глава 12: Преимущества и недостатки наследования
Многие считают, что множественное наследование не стоит потерь, и языкам программирования, не поддерживающим множественное наследование, похоже, терять нечего.
Подклассы встроенных типов громоздки
До python2.2 встроенные типы (такие как list, dict) не могли быть подклассами, они не могли быть унаследованы другими классами, потому что встроенные типы были реализованы на языке C и не вызывали методы, переопределяемые пользовательскими классами. .
Что касается того, будут ли методы, переопределяемые подклассами встроенных типов, вызываться неявно, то CPython официально не установил никаких правил.В принципе, методы встроенных типов не будут вызывать методы, переопределяемые подклассами.Например, подклассы dict переопределяют методы.__getitem__
методы не переопределяют встроенные типыget()
вызов метода.
Множественное наследование и порядок разрешения методов
Любой язык, реализующий множественное наследование, должен иметь дело с потенциальными конфликтами имен, вызванными несвязанными классами-предками, реализующими методы с одинаковыми именами. Этот конфликт называется «алмазной проблемой», как показано на рис.
Python проходит наследование в определенном порядке
рисунок. Этот порядок называется порядком разрешения метода (MRO). Классы имеют имя, называемое
mroатрибут, значением которого является кортеж, перечисляющий каждый суперкласс в порядке разрешения методов, начиная с текущего класса
Вплоть до класса объектов.
Глава 13. Правильная перегрузка операторов
В python большинство операторов можно перегружать, например==
соответствует__eq__
, +
соответствовать__add__
.
Некоторые операторы нельзя перегружать, напримерis, and, or, and
.
Глава 14. Итерируемые объекты, итераторы и генераторы
Итерация является краеугольным камнем обработки данных.При сканировании наборов данных, которые не помещаются в памяти, нам нужно найти способ лениво получать данные, по одному за раз по требованию.迭代器模式
.
у питона естьyield
ключевое слово, используемое для построения生成器(generator)
, который действует так же, как и для итераторов.
Все генераторы являются итераторами, потому что генераторы полностью реализуют интерфейс итератора.
Чтобы проверить, повторяется ли объект x, наиболее точным способом является вызовiter(x)
, если не итерируется, броситьTypeError
исключение. Этот метод болееisinstance(x, abc.Iterable)
точнее, так как он также учитывает наследие__getitem__
метод.
Итерируемые объекты против итераторов
Нам нужно определить итерируемый объект следующим образом:
Используйте встроенную функцию iter, чтобы получить объект итератора. Если объект реализует итератор, который возвращает итератор
iterметод, объект является итерируемым. Последовательности могут повторяться; реализованыgetitemквадратный
метод, а его аргументом является индекс с отсчетом от нуля, такой объект также можно перебирать.
Нам нужно уточнить связь между итерируемыми объектами и итераторами: получить итераторы из итерируемых объектов.
Стандартный интерфейс итератора имеет два метода:
-
__next__
: возвращает следующий доступный элемент или сбрасывает, если элементов больше нетStopIteration
аномальный. -
__iter__
: возвращениеself
, так что итераторы используются там, где должен использоваться итерируемый объект.
типичный итератор
Чтобы четко проиллюстрировать важную разницу между итерируемыми объектами и итераторами, мы разделим их на два класса:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
# 返回一个字符串列表、元素为正则所匹配到的非重叠匹配】
self.words = RE_WORD.findall(text)
def __repr__(self):
# 该函数用于生成大型数据结构的简略字符串的表现形式
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
'''明确表明该类型是可以迭代的'''
return SentenceIterator(self.words) # 创建一个迭代器
class SentenceIterator:
def __init__(self, words):
self.words = words # 该迭代器实例应用单词列表
self.index = 0 # 用于定位下一个元素
def __next__(self):
try:
word = self.words[self.index] # 返回当前的元素
except IndexError:
raise StopIteration()
self.index += 1 # 索引+1
return word # 返回单词
def __iter__(self):
return self # 返回self
Этот пример предназначен главным образом для того, чтобы различать итерируемые объекты и итераторы.В этом случае рабочая нагрузка обычно относительно велика, и программисты неохотно пишут таким образом.
Создание итерируемых объектов и итераторов часто идет не так, как надо, потому что они путают их.__iter__
каждый раз создается новый итератор; итератор должен реализовать__next__
метод, который возвращает один элемент, а также предоставляет__iter__
Метод возвращает сам итератор.
Итерируемый объект не должен быть итератором сам по себе, то есть итерируемый объект должен реализовывать__iter__
метод, но не может реализовать__next__
метод.
Таким образом, итераторы могут выполнять итерации, но итераторы не являются итераторами.
генераторная функция
Для достижения той же функции способ переопределения привычек python заключается в использовании генераторов вместо итераторов.SentenceIterator
, Измените предыдущий пример на способ генератора:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
# 返回一个字符串列表、元素为正则所匹配到的非重叠匹配】
self.words = RE_WORD.findall(text)
def __repr__(self):
# 该函数用于生成大型数据结构的简略字符串的表现形式
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
'''生成器版本'''
for word in self.words: # 迭代实例的words
yield word # 生成单词
return
В этом примере итератор на самом деле является объектом-генератором, и каждый вызов__iter__
создаются автоматически, потому что здесь__iter__
Методы являются генераторными функциями.
Как работают функции генератора
Пока тело определения функции python имеетyield
ключевое слово, функция является функцией генератора. Когда функция генератора вызывается, она возвращает объект генератора. То есть функция генератора является фабрикой генератора.
Единственная разница между обычной функцией и функцией-генератором состоит в том, что функция-генератор содержитyield
ключевые слова.
Функция-генератор создаст объект-генератор, обернув тело определения функции-генератора.next(...)
функция, функция генератора будет двигаться вперед, выполняя следующую функцию в теле функцииyield
оператор, возвращающий выходное значение и останавливающийся в текущей позиции в теле определения функции.
ленивая реализация
Ленивый способ — просто выводить все данные, отличные отnext(...)
Генерирует один элемент за раз.
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
for match in RE_WORD.finditer(self.text):
yield match.group()
выражение генератора
Выражение генератора можно понимать как ленивую версию генератора списков: вместо того, чтобы строить список с нетерпением, он возвращает генератор, который лениво создает элементы по запросу. выражение Формула — это фабрика, производящая генераторы.
def gen_AB():
print('start')
yield 'A'
print('continue')
yield 'B'
print('end.')
res1 = [x*3 for x in gen_AB()]
for i in res1:
print('-->', i)
Как видите, выражения-генераторы производят генераторы, поэтому вы можете использовать выражения-генераторы для сокращения кода:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
return (match.group() for match in RE_WORD.finditer(self.text))
здесь__iter__
Вместо функций генератора для построения генераторов используются выражения генератора, и конечный результат тот же.__iter__
Метод получает объект-генератор.
Выражения-генераторы — это синтаксический сахар, который может полностью заменить функции-генераторы.
Функции генератора в стандартной библиотеке
Стандартная библиотека предоставляет множество генераторов, объекты для итерации по текстовым файлам построчно и отличныеos.walk
Эта функция выдает имя файла при обходе дерева каталогов, поэтому рекурсивный поиск в файловой системе так же прост, как цикл for.
Генераторы в стандартной библиотеке в основном вitertools
иfunctools
, не все в таблице.
функция генератора для фильтрации
модуль | функция | инструкция |
---|---|---|
itertools | compress(it, selector_it) | Параллельно обрабатывать две итерации; если элемент в selector_it равен true, выдать соответствующий элемент в нем |
itertools | dropwhile(predicate, it) | Обработайте его, пропустите элементы, для которых предикат оценивается как истина, и выдайте оставшиеся элементы (без дальнейшей проверки). |
(встроенный) | filter(predicate, it) | Передать каждый элемент в нем в предикат, если предикат (элемент) возвращает истинное значение, то выведите соответствующий элемент; если предикат равен None, то выведите только элемент истинного значения |
функция генератора для отображения
модуль | функция | инструкция |
---|---|---|
itertools | accumulate(it, [func]) | Выведите кумулятивную сумму; если указана функция func, передайте ей первые два элемента, затем передайте результат вычисления и следующий элемент и так далее, и, наконец, выведите результат |
(встроенный) | enumerate(iterable, start=0) | Выведите кортеж, состоящий из двух элементов, структуры (индекс, элемент), где индекс начинает считать с начала, а элемент получается из итерируемого |
(встроенный) | map(func, it1, [it2, ..., itN]) | Передать в ней каждый элемент в func и вывести результат; если передается N итерируемых объектов, то func должна иметь возможность принимать N параметров и параллельно обрабатывать каждый итерируемый объект |
Генераторная функция, которая объединяет несколько итераций
модуль | функция | инструкция |
---|---|---|
itertools | chain(it1, ..., itN) | Сначала создайте все элементы в нем1, затем все элементы в нем2 и так далее, бесшовно соединенные вместе. |
itertools | chain.from_iterable(it) | Производить элементы в каждом итерируемом объекте, сгенерированном им, один за другим, бесшовно соединенными вместе; он должен создавать итерируемые элементы, такие как итерируемый список объектов. |
(встроенный) | zip(it1, ..., itN) | Получать элементы из входных итераций параллельно, создавать кортеж из N элементов и молча останавливаться, как только итерация исчерпана. |
Новый синтаксис: выход из
Если функции-генератору необходимо создать значение, сгенерированное другим генератором, традиционным способом является вложение циклов for, например, мы должны реализовать свой собственныйchain
Строитель:
>>> def chain(*iterables):
... for it in iterables:
... for i in it:
... yield i
...
>>> s = 'ABC'
>>> t = tuple(range(3))
>>> list(chain(s, t))
['A', 'B', 'C', 0, 1, 2]
chain
Генераторная функция передает операции полученному итерируемому объекту по очереди, вместо этого используйтеyield from
Заявление можно упростить:
>>> def chain(*iterables):
... for i in iterables:
... yield from i
...
>>> list(chain(s, t))
['A', 'B', 'C', 0, 1, 2]
Как можно видеть,yield from i
Заменяет цикл for и упрощает чтение кода.
итерируемая функция сокращения
Функции, которые принимают итерации, но возвращают только один результат, называются функциями сокращения.
модуль | функция | инструкция |
---|---|---|
(встроенный) | sum(it, start=0) | Сумма всех элементов в нем, которая добавляется, если предоставлено необязательное начало (функция math.fsum может использоваться для повышения точности при вычислении сложения чисел с плавающей запятой) |
(встроенный) | all(it) | Возвращает True, если все элементы в нем истинны, в противном случае возвращает False; all([]) возвращает True |
(встроенный) | any(it) | Возвращает True, если любой элемент в нем истинен, в противном случае возвращает False; any([]) возвращает False |
(встроенный) | max(it, [key=,] [default=]) | Возвращает элемент с наибольшим значением в нем; *key — функция сортировки, такая же, как и в функции sorted; если итерабельность пуста, возвращает значение по умолчанию |
functools | reduce(func, it, [initial]) | Передайте первые два элемента в func, затем передайте результат вычисления и третий элемент в func и так далее, возвращая окончательный результат; если предоставлено значение initial, передайте его как первый элемент |
Глава 15: Контекстные менеджеры и блоки else
В этой главе обсуждаются функции управления потоком, которые не распространены в других языках и поэтому часто упускаются из виду или недостаточно используются новичками в Python.Функции, обсуждаемые ниже:
- с операторами и менеджерами контекста
- else пункт инструкции for while try
with
Оператор устанавливает временный контекст, передаваемый объекту менеджера контекста, и отвечает за очистку контекста. Это позволяет избежать ошибок и уменьшить размер кода, поэтому API становится более безопасным и простым в использовании. В дополнение к автоматическому закрытию файла , блок with Есть много других применений.
else
Предложение делает это первым и необязательно делает то.
else блокировать вне оператора if
Здесь else используется не в операторе if, а в операторе for while try.
for i in lst:
if i > 10:
break
else:
print("no num bigger than 10")
else
Поведение предложения следующее:
-
for
: только после завершения цикла for (т. е. цикл for не былbreak
прерывание оператора) перед запуском блока else. -
while
: только если цикл while завершается, потому что условие ложно (т. е. цикл while не вызываетсяbreak
прерывание оператора) перед запуском блока else. -
try
: Блок else запускается только в том случае, если в блоке try не выдается исключение.
Во всех случаях, если исключение илиreturn
, break
илиcontinue
Оператор заставляет управление выйти за пределы блока составного оператора,else
пункты также пропускаются.
В этих случаях использование предложения else часто облегчает чтение кода и избавляет вас от необходимости устанавливать переменные, которые управляют тем, что делают флаги, и дополнительные проверки if.
Контекстные менеджеры и с блоками
Целью объекта диспетчера контекста является управлениеwith
Оператор with предназначен для упрощенияtry/finally
Этот режим используется для обеспечения выполнения определенной операции после завершения выполнения фрагмента кода, даже если этот фрагмент кода вызван исключением,return
илиsys.exit()
Вызванный для завершения, также выполнит выполненную операцию.finally
Код в предложении часто используется для освобождения важных ресурсов или восстановления временно измененного состояния.
Протокол диспетчера контекста содержит__enter__
и__exit__
Два метода. Вызывается в диспетчере контекста, когда начинается выполнение оператора with.__enter__
метод и вызовите его после того, как завершится выполнение оператора with__exit__
метод, таким образом действуяfinally
Роль оговорки.
Самый распространенный пример with — убедиться, что файловый объект закрыт.
вызов диспетчера контекста__enter__
без параметров и вызова__exit__
, будут переданы 3 параметра:
-
exc_type
: класс исключения (например, ZeroDivisionError) -
exc_value
: экземпляр исключения. Иногда в конструктор исключений передаются параметры, например сообщение об ошибке, эти параметры можно использоватьexc_value.args
Получать -
traceback
:traceback
объект
Утилиты в модуле contextlib
В стандартной библиотеке ptyhon также есть классы и другие функции в модуле contextlib, которые используются более широко.
-
closing
: если объект предоставляетclose()
метод, но не реализован__enter__/__exit__
протокол, то вы можете использовать эту функцию для создания менеджера контекста. -
suppress
: Создайте менеджер контекста, который временно игнорирует указанное исключение. -
@contextmanager
: этот декоратор превращает простую функцию-генератор в менеджер контекста, так что вам не нужно создавать класс для реализации протокола менеджера. -
ContextDecorator
: это базовый класс, используемый для определения менеджеров контекста на основе классов. Этот диспетчер контекста также можно использовать для оформления функций, запуская всю функцию в управляемом контексте. -
ExitStack
: этот контекстный менеджер может входить в несколько контекстных менеджеров. В конце блока with ExitStack вызывает диспетчеры контекста в стеке в порядке LIFO.__exit__
метод. Используйте этот класс, если вы не знаете заранее, сколько менеджеров контекста войдет в блок with. Например, откройте все файлы в одном списке файлов одновременно.
Очевидно, что из этих утилит наиболее широко используются@contextmanager
декоратор, так что будьте особенно осторожны. Этот декоратор также сбивает с толку, потому что он не имеет ничего общего с итерацией, а использует оператор yield.
Используйте @contextmanager
Декоратор @contextmanager уменьшает объем шаблонного кода для создания менеджера контекста, поскольку нет необходимости определять__enter__
и__exit__
метод, нужно только реализоватьyield
Генератор заявлений.
import sys
import contextlib
@contextlib.contextmanager
def looking_glass():
original_write = sys.stdout.write
def reverse_write(text):
original_write(text[::-1])
sys.stdout.write = reverse_write
yield 'JABBERWOCKY'
sys.stdout.write = original_write
with looking_glass() as f:
print(f) # YKCOWREBBAJ
print("ABCD") # DCBA
yield
Оператор действует как разбиение, и весь код перед оператором yield начинается в начале блока with (т. е. интерпретатор вызывает__enter__
метод) выполняется, а код, следующий за оператором yield, выполняется в конце блока with (т. е. вызов__exit__
метод) выполняется.
Глава 16: Сопрограммы
Чтобы понять концепцию сопрограмм, начните сyield
Сказать.yield item
будет производить значение, предоставляемоеnext(...)
вызывающая сторона; также делает уступку, приостанавливая выполнение генератора, позволяя вызывающей стороне продолжать работу до тех пор, пока не потребуется другое значениеnext(...)
Возобновить выполнение с того места, где оно было остановлено.
С точки зрения синтаксиса предложений сопрограммы похожи на генераторы тем, что ониyield
функции ключевых слов. Однако в сопрограммахyield
Обычно он появляется в правой части выражения (данные = yield) и может возвращать значение или нет (если после yield нет выражения, оно возвращает None).Сопрограмма может получать данные от вызывающей стороны, но вызов Сторона предоставляет данные сопрограмме, используя.send(datum)
метод вместоnext(...)
Обычно вызывающий объект передает значение сопрограмме.
Вызывающая сторона генератора всегда запрашивает данные, а сопрограмма заключается в том, что вызывающая сторона может передавать данные, и сопрограмме не обязательно нужно выводить данные.
Независимо от того, как поток данных,yield
Оба являются инструментом управления потоком, который позволяет создавать многозадачность: сопрограммы могут передавать контроллер центральному планировщику, тем самым активируя другие сопрограммы.
Как генераторы превращаются в сопрограммы
После реализации базового фреймворка сопрограммы был добавлен API генератора.send(value)
Вызывающий генератор может использовать.send(...)
для отправки данных отправленные данные станут в функции генератораyield
Значение выражения, поэтому генераторы можно использовать как сопрограммы..send(...)
метод, также добавленный.throw(...)
и.close()
метод, используемый для того, чтобы вызывающая сторона выдавала исключение и завершала работу генератора.
Базовое поведение генераторов, используемых в качестве сопрограмм
>>> def simple_coroutine():
... print('-> coroutine started')
... x = yield
... print('-> coroutine received:', x)
...
>>> my_coro = simple_coroutine()
>>> my_coro
<generator object simple_coroutine at 0x100c2be10>
>>> next(my_coro)
-> coroutine started
>>> my_coro.send(42)
-> coroutine received: 42
Traceback (most recent call last):
...
StopIteration
существуетyield
В выражении, если сопрограмме нужно только принимать данные от вызывающей стороны, то выходное значение равноNone
Так же, как и при создании генератора, вызов функции получает объект генератора.Сначала должны быть вызваны сопрограммы.next(...)
Функция, так как генератор еще не запущен, предварительно не была в yield, поэтому она не может отправлять данные в начале, если контроллер перетекает в конец тела определения сопрограммы, он будет бросать как итераторStopIteration
аномальный.
Преимущество использования сопрограмм в том, что нет необходимости блокироваться, потому что все сопрограммы работают только в одном потоке, они не вытесняющие, у сопрограмм также есть некоторое состояние, которое можно назватьinspect.getgeneratorstate(...)
Чтобы получить, сопрограмма находится в одном из этих 4 состояний:
-
'GEN_CREATED'
Дождитесь начала выполнения. -
'GEN_RUNNING'
Интерпретатор выполняет. -
'GEN_SUSPENDED'
Сделайте паузу на выражении yield. -
'GEN_CLOSED'
Исполнение заканчивается.
Это состояние видно только в многопоточных приложениях. Кроме того, объект-генератор вызывает сам себяgetgeneratorstate
Функция будет работать, но это будет бесполезно.
Чтобы лучше понять поведение наследования, рассмотрим сопрограмму, которая выдает два значения:
>>> from inspect import getgeneratorstate
>>> def simple_coro2(a):
... print('-> Started: a =', a)
... b = yield a
... print('-> Received: b =', b)
... c = yield a + b
... print('-> Received: c =', c)
...
>>> my_coro2 = simple_coro2(14)
>>> getgeneratorstate(my_coro2) # 协程处于未启动的状态
'GEN_CREATED'
>>> next(my_coro2) # 向前执行到yield表达式, 产出值 a, 暂停并等待 b 赋值
-> Started: a = 14
14
>>> getgeneratorstate(my_coro2) # 协程处于暂停状态
'GEN_SUSPENDED'
>>> my_coro2.send(28) # 数字28发给协程, yield 表达式中 b 得到28, 协程向前执行, 产出 a + b 值
-> Received: b = 28
42
>>> my_coro2.send(99) # 同理, c 得到 99, 但是由于协程终止, 导致生成器对象抛出 StopIteration 异常
-> Received: c = 99
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> getgeneratorstate(my_coro2) # 协程处于终止状态
'GEN_CLOSED'
Ключевым моментом является то, что сопрограммыyield
Приостановить выполнение в месте расположения ключевого слова.b = yield a
Для этой строки кода значение b не будет установлено до тех пор, пока клиентский код снова не активирует сопрограмму.Потребуется некоторое время, чтобы привыкнуть к этому методу.Поняв это, вы сможете понять асинхронное программирование.yield
Роль функции в коде экземпляраsimple_coro2
Процесс исполнения можно разделить на три этапа:
Пример: расчет скользящего среднего с использованием сопрограмм
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total/count
Это код сопрограммы, которая динамически вычисляет среднее значение, этот бесконечный цикл показывает, что он всегда будет получать значение, а затем генерировать результат Только тогда, когда вызывающая сторона вызывает сопрограмму..close()
метод, или если нет ссылки на сопрограмму, сопрограмма завершится.
Преимущество сопрограмм в том, что нет необходимости использовать свойства экземпляра или замыкания, а контекст сохраняется между вызовами.
Декоратор для предварительно возбужденных сопрограмм
если не выполненоnext(...)
, сопрограммы бесполезны.Для упрощения использования сопрограмм иногда используется декоратор prefire.
from functools import wraps
def coroutine(func):
"""装饰器:向前执行到第一个`yield`表达式,预激`func`"""
@wraps(func)
def primer(*args,**kwargs): # 调用 primer 函数时,返回预激后的生成器
gen = func(*args,**kwargs) # 调用被装饰的函数,获取生成器对象。
next(gen) # 预激生成器
return gen # 返回生成器
return primer
Завершение сопрограмм и обработка исключений
Необработанные исключения в сопрограмме всплывут и будут переданыnext()
функция илиsend()
Вызывающий объект . Если это исключение не будет обработано, сопрограмма завершится.
>>> coro_avg.send(40)
40.0
>>> coro_avg.send(50)
45.0
>>> coro_avg.send('spam') # 传入会产生异常的值
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +=: 'float' and 'str'
>>> coro_avg.send(60)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Для этого требуется, чтобы эти исключения обрабатывались внутри сопрограммы.Кроме того, клиентский код также может явно отправлять исключения сопрограмме, выполнивthrow
иclose
:
coro_avg.throw(ZeroDivisionError)
Если исключение не может быть обработано внутри сопрограммы, это приведет к завершению сопрограммы.
иclose
вызывает паузуyield
бросаться на выражениеGeneratorExit
Исключение.Конечно, это исключение разрешено обрабатывать внутри сопрограммы, но оно не должно выдавать значение при получении этого исключения, иначе интерпретатор выкинетRuntimeError
аномальный.
Пусть сопрограмма возвращает значение
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield
if term is None:
break
total += term
count += 1
average = total/count
return (count, average)
coro_avg = averager()
next(coro_avg)
coro_avg.send(10)
coro_avg.send(30)
try:
coro_avg.send(None) # 发送 None 让协程终止
except StopIteration as exc:
result = exc.value
Чтобы вернуть значение, сопрограмма должна завершиться нормально, а нормально завершенная сопрограмма выдастStopIteration
исключение, поэтому вызывающая сторона должна обработать это исключение.
использовать доход от
yield from
является новой грамматической структурой, ее функция болееyield
Намного больше.
>>> def gen():
... for c in 'AB':
... yield c
... for i in range(1, 3):
... yield i
...
>>> list(gen())
['A', 'B', 1, 2]
можно переписать как:
>>> def gen():
... yield from 'AB'
... yield from range(1, 3)
...
>>> list(gen())
['A', 'B', 1, 2]
в генератореgen
используется вyield form subgen()
В это время subgen получит управление и передаст выходное значение вызывающей стороне gen, то есть вызывающая сторона может напрямую вызвать subgen.В это время gen заблокируется, ожидая завершения работы subgen.
yield from x
пара выраженийx
Первое, что делает объект, это вызываетiter(x)
Получает итератор Таким образом, объект x может быть любым итерируемым.
Эта семантика слишком сложна, давайте посмотрим на автораGreg Ewing
объяснение:
«Использование итератора в качестве генератора эквивалентно встраиванию определения подгенератора в выражение yield from
середина. Кроме того, подгенератор может выполнять оператор return, возвращая значение, которое становится значением выражения yieldfrom. "
Вспомогательный генератор отyield from <iterable>
Генератор получен в .Тогда, если вызывающая сторона используетsend()
метода, на самом деле, он также передается непосредственно в подгенератор.None
, то подгенератор будет вызываться__next__()
метод Если нетNone
, то подгенератор будет вызыватьсяsend()
метод.Когда дочерний генератор бросаетStopIteration
исключение, то генератор делегирования возобновляет работу. Любые другие исключения будут всплывать и передаваться генератору делегирования.
генератор вreturn expr
сработает в выраженииStopIteration
аномальный.
Глава 17: Обработка параллелизма с фьючерсами
"期物"
Что такое концепция? Объект относится к объекту, представляющему операцию, выполняемую асинхронно.concurrent.futures
модули иasyncio
Основа пакета.
Пример: три стиля веб-загрузок
Чтобы эффективно обрабатывать сетевой ввод-вывод, необходимо использовать параллелизм, поскольку сеть имеет высокую задержку, чтобы не тратить циклы ЦП на ожидание.
Взяв программу, которая загружает 20 изображений из сети, последовательная загрузка занимает 7,18 с, многопоточная загрузка занимает 1,40 с, а asyncio — 1,35 с, разница между двумя одновременно загружаемыми скриптами незначительна, серийно это намного быстрее. .
Блокировка ввода/вывода и GIL
Интерпретатор CPython не является потокобезопасным, поэтому существует глобальная блокировка интерпретации (GIL), которая позволяет одновременно выполнять байт-код Python только одному потоку, поэтому один процесс Python не может одновременно использовать несколько ядер ЦП.
Программисты на Python не контролируют GIL при написании кода, однако все функции в стандартной библиотеке, выполняющие блокирующие операции ввода-вывода, освобождают GIL, когда промежуточная операционная система возвращает результат. это.
Запустите процесс с помощью модуля concurrent.futures
Существует только один GIL для каждого процесса python.Несколько процессов python могут обойти GIL, поэтому этот метод может использовать все ядра ЦП.concurrent.futures
модуль реализует настоящие параллельные вычисления, поскольку используетProcessPoolExecutor
Передайте работу нескольким процессам Python.
ProcessPoolExecutor
иThreadPoolExecutor
классы реализуют общийExecutor
интерфейс, поэтому используйтеconcurrent.futures
Решение, основанное на потоках, легко преобразовать в решение, основанное на процессах.
def download_many(cc_list):
workers = min(MAX_WORKERS, len(cc_list))
with futures.ThreadPoolExecutor(workers) as executor:
res = executor.map(download_one, sorted(cc_list))
Измените его на:
def download_many(cc_list):
with futures.ProcessPoolExecutor() as executor:
res = executor.map(download_one, sorted(cc_list))
ThreadPoolExecutor.__init__
метод нуждаетсяmax_workers
параметр, указывающий количество потоков в пуле потоков; вProcessPoolExecutor
класс, этот параметр является необязательным.
Глава 18. Обработка параллелизма с помощью пакета asyncio
Параллелизм относится к одновременной обработке нескольких вещей.
Параллелизм означает выполнение нескольких действий одновременно.
Два разных, но связанных.
Один о структуре и один об исполнении.
Параллелизм используется для формулирования решений проблем, которые могут (но не обязательно) быть параллельными. - Роб Пайк, один из создателей языка Go
Параллелизм означает, что два или более события происходят одновременно, а параллелизм означает, что два или более события происходят в один и тот же интервал времени.Действительный параллелизм требует нескольких ядер.Теперь ноутбуки обычно имеют 4 ядра ЦП, но обычно только более 100 процессы, работающие одновременно. Таким образом, на практике большинство процессов обрабатываются одновременно, а не параллельно. В компьютере всегда запущено более 100 процессов, что гарантирует, что у каждого процесса есть шанс расти, но сам процессор это делает. чем четыре вещи.
Введение в эту главуasyncio
Пакет, этот пакет использует сопрограммы, управляемые циклом событий, для достижения параллелизма.Эта библиотека обрабатывается Uncle Turtle.asyncio
интенсивное использованиеyield from
выражение, поэтому оно несовместимо с версиями ниже python3.3.
Поток против сопрограммы
оправданиеthreading
Модули используют потоки, переходasyncio
Пакеты используют реализации сопрограмм для сравнения.
import threading
import itertools
import time
def spin(msg, done): # 这个函数会在单独的线程中运行
for char in itertools.cycle('|/-\\'): # 这其实是个无限循环,因为 itertools.cycle 函数会从指定的序列中反复不断地生成元素
status = char + ' ' + msg
print(status)
if done.wait(.1): # 如果进程被通知等待, 那就退出循环
break
def slow_function(): # 假设这是耗时的计算
# pretend waiting a long time for I/O
time.sleep(3) # 调用 sleep 函数会阻塞主线程,不过一定要这么做,以便释放 GIL,创建从属线程
return 42
def supervisor(): # 这个函数设置从属线程,显示线程对象,运行耗时的计算,最后杀死线程。
done = threading.Event()
spinner = threading.Thread(target=spin,
args=('thinking!', done))
print('spinner object:', spinner) # 显示从属线程对象。输出类似于 <Thread(Thread-1, initial)>
spinner.start() # 启动从属线程
result = slow_function() # 运行 slow_function 函数,阻塞主线程。同时,从属线程以动画形式显示旋转指针
done.set() # 改变 signal 的状态;这会终止 spin 函数中的那个 for 循环
spinner.join() # 等待 spinner 线程结束
return result
if __name__ == '__main__':
result = supervisor()
print('Answer:', result)
Это используетthreading
В случае , пусть дочерний поток будет печатать непрерывно в течение 3 секунд, в python нет API для завершения потока.Если вы хотите закрыть поток, вы должны отправить сообщение в поток.
См. ниже для использования@asyncio.coroutine
Декораторы заменяют сопрограммы и добиваются такого же поведения:
import asyncio
import itertools
@asyncio.coroutine # 交给 asyncio 处理的协程要使用 @asyncio.coroutine 装饰
def spin(msg):
for char in itertools.cycle('|/-\\'):
status = char + ' ' + msg
print(status)
try:
yield from asyncio.sleep(.1) # 使用 yield from asyncio.sleep(.1) 代替 time.sleep(.1),这样的休眠不会阻塞事件循环。
except asyncio.CancelledError: # 如果 spin 函数苏醒后抛出 asyncio.CancelledError 异常,其原因是发出了取消请求,因此退出循环。
break
@asyncio.coroutine
def slow_function(): # slow_function 函数是协程,在用休眠假装进行 I/O 操作时,使用 yield from 继续执行事件循环。
# pretend waiting a long time for I/O
yield from asyncio.sleep(3) # yield from asyncio.sleep(3) 表达式把控制权交给主循环,在休眠结束后恢复这个协程。
return 42
@asyncio.coroutine
def supervisor(): # supervisor 函数也是协程
spinner = asyncio.async(spin('thinking!')) # asyncio.async(...) 函数排定 spin 协程的运行时间,使用一个 Task 对象包装spin 协程,并立即返回。
print('spinner object:', spinner)
result = yield from slow_function() # 驱动 slow_function() 函数。结束后,获取返回值。
# 同时,事件循环继续运行,因为slow_function 函数最后使用 yield from asyncio.sleep(3) 表达式把控制权交回给了主循环。
spinner.cancel() # Task 对象可以取消;取消后会在协程当前暂停的 yield 处抛出 asyncio.CancelledError 异常。协程可以捕获这个异常,也可以延迟取消,甚至拒绝取消。
return result
if __name__ == '__main__':
loop = asyncio.get_event_loop() # 获取事件循环的引用
result = loop.run_until_complete(supervisor()) # 驱动 supervisor 协程,让它运行完毕;这个协程的返回值是这次调用的返回值。
loop.close()
print('Answer:', result)
asyncio
Сопрограмма, используемая пакетом, определена более строго, и сопрограмма, подходящая для asyncio API, должна использоваться в теле определения.yield from
, Вместо того, чтобы использоватьyield
. также,asyncio
Сопрограмма, которой должен управлять вызывающий, например.asyncio.async(...)
, тем самым управляя сопрограммой.@asyncio.coroutine
Декораторы применяются к сопрограммам.
эти двоеsupervisor
Основные различия между реализациями описаны ниже:
-
asyncio.Task
Объект почти такой же, какthreading.Thread
Объектный эквивалент. «Объекты задач похожи на зеленые потоки в библиотеках, которые реализуют совместную многозадачность (например, gevent)». -
Task
Объекты используются для управления сопрограммами,Thread
Объекты используются для вызова вызываемых объектов. -
Task
Объект создается не сам по себе, а путем передачи сопрограммы вasyncio.async(...)
функция илиloop.create_task(...)
способ получения. - приобретенный
Task
Запуск объекта запланирован (например,asyncio.async
планирование функций); экземпляр Thread должен вызвать метод запуска, чтобы явно сообщить ему о запуске. - онлайн-версия
supervisor
в функции,slow_function
Функции — это обычные функции, которые вызываются непосредственно потоками. в асинхронной версииsupervisor
в функции,slow_function
Функции — это сопрограммы, определенныеyield from
водить машину. - Не существует API для внешнего завершения потока, потому что поток может быть прерван в любой момент, оставив систему в недопустимом состоянии. Если вы хотите завершить задачу, вы можете использовать
Task.cancel()
метод экземпляра, брошенный внутри сопрограммыCancelledError
аномальный. Сопрограмму можно приостановитьyield
Перехватите это исключение и обработайте запрос на завершение. -
supervisor
Сопрограмма должна быть вmain
функцияloop.run_until_complete
метод выполняется.
Многопоточное программирование сложнее, потому что планировщик может прервать поток в любой момент и должен помнить о блокировках, чтобы защитить важные части программы от прерываний в процессе многопоточного выполнения.
По умолчанию сопрограммы полностью защищены от прерываний. Мы должны отображать вывод, чтобы разрешить выполнение остальной части программы. Для сопрограмм нет необходимости сохранять блокировки, но для синхронизации операций между несколькими потоками сами сопрограммы будут синхронизированы, потому что в любой момент времени работает только одна сопрограмма.
Вывод из фьючерсов, задач и сопрограмм
существуетasyncio
В пакете фьючерсы и сопрограммы тесно связаны, потому что их можно использовать с помощьюyield from
отasyncio.Future
Результат создается в объекте.foo
является функцией сопрограммы или возвращаетFuture
илиTask
экземпляр обычной функции, то вы можете использоватьres = yield from foo()
.
Чтобы выполнить эту операцию, сопрограмма должна быть запланирована для запуска, а затем использованаasyncio.Task
Объект оборачивает сопрограммы.Task
Существует два основных типа объектов:
-
asyncio.async(coro_or_future, *, loop=None)
: эта функция объединяет сопрограммы и фьючерсы: первый аргумент может быть любым. Если это объект Future или Task, верните его без изменений. Если это сопрограмма, то асинхронная функция вызоветloop.create_task(...)
метод для создания объекта Task. Аргумент ключевого слова цикла является необязательным и передается в цикл событий; если он не передан, асинхронная функция будет передана вызовомasyncio.get_event_loop()
Функция получает объект цикла. -
BaseEventLoop.create_task(coro)
: этот метод планирует время выполнения сопрограммы и возвращаетasyncio.Task
объект. Если в обычаеBaseEventLoop
Вызванный в подклассе возвращаемый объект может быть экземпляром класса во внешней библиотеке (например, Tornado), который совместим с классом Task.
asyncio
В пакете есть несколько функций, которые автоматически (используяasyncio.async
function) оборачивает сопрограмму, указанную параметром вasyncio.Task
в объекте.
Загрузка с использованием пакетов asyncio и aiohttp
asyncio
Пакет поддерживает только TCP и UDP напрямую.Если вы используете HTTP или другие протоколы, вам необходимо использовать сторонние пакеты.Почти все ониaiohttp
В качестве примера возьмем загружаемый образ:
import asyncio
import aiohttp
from flags import BASE_URL, save_flag, show, main
@asyncio.coroutine
def get_flag(cc): # 协程应该使用 @asyncio.coroutine 装饰。
url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
resp = yield from aiohttp.request('GET', url) # 阻塞的操作通过协程实现
image = yield from resp.read() # 读取响应内容是一项单独的异步操作
return image
@asyncio.coroutine
def download_one(cc): # download_one 函数也必须是协程,因为用到了 yield from
image = yield from get_flag(cc)
show(cc)
save_flag(image, cc.lower() + '.gif')
return cc
def download_many(cc_list):
loop = asyncio.get_event_loop() # 获取事件循环底层实现的引用
to_do = [download_one(cc) for cc in sorted(cc_list)] # 调用 download_one 函数获取各个国旗,然后构建一个生成器对象列表
wait_coro = asyncio.wait(to_do) # 虽然函数的名称是 wait,但它不是阻塞型函数。wait 是一个协程,等传给它的所有协程运行完毕后结束
res, _ = loop.run_until_complete(wait_coro) # 执行事件循环,直到 wait_coro 运行结束
loop.close() # 关闭事件循环
return len(res)
if __name__ == '__main__':
main(download_many)
asyncio.wait(...)
Параметр сопрограммы — это итерируемый объект, состоящий из фьючерсов или сопрограмм, и ожидание загрузит каждую сопрограмму вTask
Объекты Конечным результатом является то, что все объекты, обрабатываемые ожиданием, каким-то образом становятсяFuture
Экземпляр класса. Ожидание — это функция сопрограммы, поэтому она возвращает объект сопрограммы или генератора. Чтобы управлять сопрограммой, мы передаем сопрограмму вloop.run_until_complete(...)
метод.
loop.run_until_complete
Параметр метода — это будущее или сопрограмма.Если это сопрограмма,run_until_complete
Метод такой же, как и у функции ожидания, заключающий сопрограмму вTask
объект, потому что сопрограммыyield from
драйв, это точноrun_until_complete
Что делать с объектом wait_coro, возвращаемым ожиданием Возвращает два элемента после запуска,
Первый — это закрытый фьючерс, а второй — незавершенный фьючерс.
Избегайте блокировки звонков
Есть два способа избежать блокирования вызовов от прерывания всего процесса приложения:
- Выполнять каждую операцию блокировки в отдельном потоке
- Преобразуйте каждую блокирующую операцию в неблокирующий асинхронный вызов, используя
Многопоточность возможна, но она будет потреблять большое количество памяти.Чтобы уменьшить потребление памяти, для реализации асинхронных вызовов обычно используются обратные вызовы.Это низкоуровневая концепция, похожая на самый старый и самый примитивный из всех механизмов параллелизма- Аппаратные прерывания. С обратными вызовами вместо ожидания ответа мы регистрируем функцию, которая будет вызываться, когда что-то происходит. Таким образом, все вызовы не блокируются.
Лежащий в основе цикл обработки событий асинхронных приложений может полагаться на прерывания инфраструктуры, потоки, опросы и ожидания фоновых процессов, чтобы гарантировать, что несколько одновременных запросов могут выполняться и в конечном итоге завершаться, чтобы можно было использовать обратные вызовы. назад, чтобы вызвать указанный нами обратный вызов. Если все сделано правильно, основной поток, общий для цикла обработки событий и кода приложения, никогда не будет блокироваться.
Использование генераторов в качестве сопрограмм — еще один способ асинхронного программирования.Для цикла событий вызов обратного вызова аналогичен вызову приостановленной сопрограммы..send()
Эффект примерно такой же.
Используйте объект Executor, чтобы предотвратить блокировку цикла событий
Доступ к локальным файлам блокируется, и базовый CPython освобождает GIL при блокировке вызовов ввода-вывода, поэтому другой поток может продолжить работу.
так какasyncio
События не выполняются через несколько потоков, поэтомуsave_flag
Функция, используемая для сохранения блоков изображения сasyncio
Единственный поток, совместно используемый циклом событий, поэтому все приложение зависает при сохранении файла Решение этой проблемы заключается в использовании объекта цикла событийrun_in_executor
метод.
asyncio
Сопровождающий цикл событий — этоThreadPoolExecutor
объект, мы можем назватьrun_in_executor
метод, отправьте ему вызываемый объект для выполнения:
@asyncio.coroutine
def download_one(cc, base_url, semaphore, verbose):
try:
with (yield from semaphore):
image = yield from get_flag(base_url, cc)
except web.HTTPNotFound:
status = HTTPStatus.not_found
msg = 'not found'
except Exception as exc:
raise FetchError(cc) from exc
else:
loop = asyncio.get_event_loop() # 获取事件循环对象的引用
loop.run_in_executor(None, # run_in_executor 方法的第一个参数是 Executor 实例;如果设为 None,使用事件循环的默认 ThreadPoolExecutor 实例。
save_flag, image, cc.lower() + '.gif') # 余下的参数是可调用的对象,以及可调用对象的位置参数
status = HTTPStatus.ok
msg = 'OK'
if verbose and msg:
print(cc, msg)
return Result(status, cc)
Глава 19: Динамические свойства и свойства
В python как атрибуты данных, так и методы обработки данных могут вызываться как属性
В дополнение к свойствам pythpn предоставляет богатый API для управления доступом к свойствам, а также для реализации динамических свойств, таких какobj.attr
путь и__getattr__
Вычисляемые свойства.
Динамическое создание свойств — это форма метапрограммирования.
Преобразование данных с использованием динамических свойств
Обычно анализируемые данные json должны быть в формеfeed['Schedule']['events'][40]['name']
Доступ к форме, при необходимости мы можем заменить его доступом к атрибутуfeed.Schedule.events[40].name
получить это значение.
from collections import abc
class FrozenJSON:
"""一个只读接口,使用属性表示法访问JSON类对象
"""
def __init__(self, mapping):
self.__data = dict(mapping)
def __getattr__(self, name):
if hasattr(self.__data, name):
return getattr(self.__data, name)
else:
return FrozenJSON.build(self.__data[name]) # 从 self.__data 中获取 name 键对应的元素
@classmethod
def build(cls, obj):
if isinstance(obj, abc.Mapping):
return cls(obj)
elif isinstance(obj, abc.MutableSequence):
return [cls.build(item) for item in obj]
else: # 如果既不是字典也不是列表,那么原封不动地返回元素
return obj
использоватьnewметоды гибкого создания объектов
мы обычно ставим__init__
становится конструктором, термин, заимствованный из других языков Фактически, специальный метод, используемый для создания экземпляра,__new__
: это метод класса, который должен возвращать экземпляр. Возвращенный экземпляр будет использоваться позже.self
перейти к__init__
метод.
Глава 20: Дескрипторы свойств
Дескриптор — это класс, реализующий характерный протокол, который включает__get__
, __set__
и__delete__
Метод: В общем, часть протокола можно реализовать.
Покрывающие и непокрывающие дескрипторы
Способ доступа python к атрибутам неодинаков.Когда атрибут читается через экземпляр, он обычно возвращает атрибут, определенный в экземпляре, но если в экземпляре нет указанного атрибута, он получит атрибут класса.И назначение атрибута в экземпляр, свойства обычно создаются в экземпляре и вообще не влияют на класс.
Это неравное обращение также влияет на дескрипторы.__set__
Методы-дескрипторы можно разделить на две категории: покрывающие дескрипторы и непокрывающие дескрипторы.
выполнить__set__
Дескриптор метода является переопределяющим дескриптором, потому что, хотя дескриптор является атрибутом класса, реализация__set__
При использовании метода операция присваивания свойству экземпляра будет переопределена, поэтому в качестве метода класса__set__
Нужно передать экземплярinstance
, См. пример:
def print_args(*args): # 打印功能
print(args)
class Overriding: # 设置了 __set__ 和 __get__
def __get__(self, instance, owner):
print_args('get', self, instance, owner)
def __set__(self, instance, value):
print_args('set', self, instance, value)
class OverridingNoGet: # 没有 __get__ 方法的覆盖型描述符
def __set__(self, instance, value):
print_args('set', self, instance, value)
class NonOverriding: # 没有 __set__ 方法,所以这是非覆盖型描述符
def __get__(self, instance, owner):
print_args('get', self, instance, owner)
class Managed: # 托管类,使用各个描述符类的一个实例
over = Overriding()
over_no_get = OverridingNoGet()
non_over = NonOverriding()
def spam(self):
print('-> Managed.spam({})'.format(repr(self)))
Дескриптор переопределения
obj = Managed()
obj.over # ('get', <__main__.Overriding object>, <__main__.Managed object>, <class '__main__.Managed'>)
obj.over = 7 # ('set', <__main__.Overriding object>, <__main__.Managed object>, 7)
obj.over # ('get', <__main__.Overriding object>, <__main__.Managed object>, <class '__main__.Managed'>)
названныйover
свойства экземпляра , которые переопределяют чтение и присваиваниеobj.over
поведение.
нет__get__
Переопределение дескрипторов для методов
obj = Managed()
obj.over_no_get
obj.over_no_get = 7 # ('set', <__main__.OverridingNoGet object>, <__main__.Managed object>, 7)
obj.over_no_get
Поведение переопределения возвращается только во время операций присваивания.
методы являются дескрипторами
Функции, определенные в классах Python, являются связанными методами, если пользовательские функции имеют__get__
Метод, прикрепленный таким образом к классу, эквивалентен дескриптору.
obj.spam
иManaged.spam
Получает другой объект.<class method>
последний<class function>
.
Функции не являются переопределяющими дескрипторами.__get__
метод при передаче экземпляра какself
, вы получаете метод, привязанный к этому экземпляру.__get__
Переданный экземплярNone
, то вы получаете саму функцию.Это формальный параметрself
Метод неявной привязки .
Совет по использованию дескриптора
Используйте функции, чтобы упростить задачу
Встроенныйproperty
Классы создают оверлейные дескрипторы,__set__
и__get__
все реализовано.
дескрипторы только для чтения должны иметьsetметод
Если вы хотите реализовать свойство только для чтения,__get__
и__set__
Оба метода должны быть определены, иначе свойство экземпляра с тем же именем переопределит дескриптор.
Дескрипторы, используемые для аутентификации, могут быть толькоsetметод
Какой дескриптор используется для проверки, например, есть атрибут age, но он может быть установлен только в число, тогда вы можете только определить__set__
для проверки правильности значения. В этом случае настройка не требуется.__get__
, потому что свойства экземпляра напрямую выводятся из__dict__
, без срабатывания__get__
метод.
Глава 21: Метапрограммирование
Метапрограммирование классов относится к искусству создания или настройки классов во время выполнения.В python классы являются первоклассными объектами, поэтому в любой момент класс можно создать с помощью функции без использованияclass
Декораторы классов также являются функциями, но могут проверять, изменять и даже заменять декорированный класс другими классами.
Метаклассы — это самый продвинутый инструмент для метапрограммирования.Что такое метакласс?Напримерstr
это класс, который создает строку,int
— это класс, который создает целые числа. Затем метакласс — это класс, который создает класс. Все классы создаются метаклассом.class
Просто оригинальный "экземпляр".
В этой главе обсуждается, как создавать классы во время выполнения.
функция фабрики классов
Примером в стандартной библиотеке является функция фабрики классов с именем tuple (collections.namedtuple
Мы передаем этой функции имя класса и несколько свойств, и она создаетtuple
Подкласс , где элементы получаются по имени.
Предположим, мы создаемrecord_factory
, имеет аналогичную функциональность именованным кортежам:
>>> Dog = record_factory('Dog', 'name weight owner')
>>> rex = Dog('Rex', 30, 'Bob')
>>> rex
Dog(name='Rex', weight=30, owner='Bob')
>>> rex.weight = 32
>>> Dog.__mro__
(<class 'factories.Dog'>, <class 'object'>)
Мы хотим создать функцию фабрики классов во время выполнения:
def record_factory(cls_name, field_names):
try:
field_names = field_names.replace(',', ' ').split() # 属性拆分
except AttributeError: # no .replace or .split
pass # assume it's already a sequence of identifiers
field_names = tuple(field_names) # 使用属性名构建元组,这将成为新建类的 __slots__ 属性
def __init__(self, *args, **kwargs): # 这个函数将成为新建类的 __init__ 方法
attrs = dict(zip(self.__slots__, args))
attrs.update(kwargs)
for name, value in attrs.items():
setattr(self, name, value)
def __iter__(self): # 实现 __iter__ 函数, 变成可迭代对象
for name in self.__slots__:
yield getattr(self, name)
def __repr__(self): # 生成友好的字符串表示形式
values = ', '.join('{}={!r}'.format(*i) for i
in zip(self.__slots__, self))
return '{}({})'.format(self.__class__.__name__, values)
cls_attrs = dict(__slots__ = field_names, # 组建类属性字典
__init__ = __init__,
__iter__ = __iter__,
__repr__ = __repr__)
return type(cls_name, (object,), cls_attrs) # 调用元类 type 构造方法,构建新类,然后将其返回
type
Является метаклассом, последняя строка экземпляра создаст класс, имя классаcls_name
, единственный прямой суперклассobject
.
При метапрограммировании на питоне лучше не использоватьexec
иeval
Эти две функции представляют серьезную угрозу безопасности.
Основы метакласса
Метаклассы — это фабрики, которые создают классы, но не функции, и сами являются классами.Метакласс — это класс, используемый для создания класса.
Чтобы избежать бесконечного возврата,type
является экземпляром самого себя.object
класс иtype
Классовые отношения уникальны,object
даtype
экземпляр, иtype
даobject
подкласс .
специальные методы метаклассовprepare
type
Конструкторы и метаклассы__new__
и__init__
Все методы получают тело класса для оценки в виде карты имен в атрибуты.По умолчанию это отображение является словарем, а порядок атрибутов теряется в теле класса.Решение этой проблемы заключается в использовании специального метода python3, представленного__prepare__
, этот метод полезен только в метаклассах и должен быть объявлен как метод класса (то есть для использования@classmethod
определение декоратора).
Интерпретатор вызывает метакласс__new__
метод будет вызываться перед__prepare__
метод, который создает карту, используя свойства в теле определения класса.
__prepare__
Первый параметр — это метакласс, следующие два параметра — это имя создаваемого класса и принцип базового класса, а возвращаемое значение должно быть картой.
class EntityMeta(type):
"""Metaclass for business entities with validated fields"""
@classmethod
def __prepare__(cls, name, bases):
return collections.OrderedDict() # 返回一个空的 OrderedDict 实例,类属性将存储在里面。
def __init__(cls, name, bases, attr_dict):
super().__init__(name, bases, attr_dict)
cls._field_names = [] # 中创建一个 _field_names 属性
for key, attr in attr_dict.items():
if isinstance(attr, Validated):
type_name = type(attr).__name__
attr.storage_name = '_{}#{}'.format(type_name, key)
cls._field_names.append(key)
class Entity(metaclass=EntityMeta):
"""Business entity with validated fields"""
@classmethod
def field_names(cls): # field_names 类方法的作用简单:按照添加字段的顺序产出字段的名称
for name in cls._field_names:
yield name
Эпилог
Python — это язык, который одновременно прост в изучении и мощен.