Заметки по чтению "Свободное владение питоном"

задняя часть Python

Начало

"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.asyncfunction) оборачивает сопрограмму, указанную параметром в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 — это язык, который одновременно прост в изучении и мощен.