Сводка советов по использованию и производительности Python

Python

1. Запутанная операция

В этом разделе сравниваются некоторые запутанные операции Python.

1.1 Случайная выборка с заменой и случайная выборка без замены

import random
random.choices(seq, k=1)  # 长度为k的list,有放回采样
random.sample(seq, k)     # 长度为k的list,无放回采样

1.2 Параметры лямбда-функций

func = lambda y: x + y          # x的值在函数运行时被绑定
func = lambda y, x=x: x + y     # x的值在函数定义时被绑定

1.3 копирование и глубокое копирование

import copy
y = copy.copy(x)      # 只复制最顶层
y = copy.deepcopy(x)  # 复制所有嵌套部分

Легко запутаться, когда копирование и псевдоним переменных объединяются:

a = [1, 2, [3, 4]]

# Alias.
b_alias = a  
assert b_alias == a and b_alias is a

# Shallow copy.
b_shallow_copy = a[:]  
assert b_shallow_copy == a and b_shallow_copy is not a and b_shallow_copy[2] is a[2]

# Deep copy.
import copy
b_deep_copy = copy.deepcopy(a)  
assert b_deep_copy == a and b_deep_copy is not a and b_deep_copy[2] is not a[2]

Изменение псевдонима повлияет на исходную переменную.Элемент в (поверхностной) копии является псевдонимом элемента в исходном списке, в то время как глубокая копия копируется рекурсивно, и изменение глубокой копии не влияет на оригинал Переменная.

1.4 == и есть

x == y  # 两引用对象是否有相同值
x is y  # 两引用是否指向同一对象

1.5 Тип решения

type(a) == int      # 忽略面向对象设计中的多态特征
isinstance(a, int)  # 考虑了面向对象设计中的多态特征

1.6 Поиск строки

str.find(sub, start=None, end=None); str.rfind(...)     # 如果找不到返回-1
str.index(sub, start=None, end=None); str.rindex(...)   # 如果找不到抛出ValueError异常

1.7 Обратная индексация списка

Это просто вопрос привычки: при прямом индексировании нижний индекс начинается с 0. Если обратный индекс также хочет начинаться с 0, вы можете использовать ~.

print(a[-1], a[-2], a[-3])
print(a[~0], a[~1], a[~2])

2. Руководство пользователя C/C++

Многие пользователи Python в прошлом мигрировали с C/C++.Эти два языка имеют некоторые различия в синтаксисе, стиле кода и т. д. В этом разделе они кратко представлены.

2.1 Большие и маленькие числа

Привычка C/C++ состоит в том, чтобы определять большое число, а в Python есть inf и -inf:

a = float('inf')
b = float('-inf')

2.2 Логические значения

Привычка C/C++ состоит в том, чтобы использовать 0 и ненулевые значения для представления True и False, а Python рекомендует использовать True и False для непосредственного представления логических значений.

a = True
b = False

2.3 Оценка пустоты

Привычка C/C++ оценивать нулевые указатели - это if (a) и if (!a). Решение Python для None:

if x is None:
    pass

Если вы используете if not x, другие объекты (такие как строки нулевой длины, списки, кортежи, словари и т. д.) будут рассматриваться как False.

2.4 Поменять местами значения

Привычка C/C++ состоит в том, чтобы определять временную переменную для обмена значениями. Используя операцию Python Tuple, это можно сделать за один шаг.

a, b = b, a

2.5 Сравнение

Соглашение C/C++ заключается в использовании двух условий. Вы можете сделать все это за один шаг с помощью Python.


if 0 < a < 5:
    pass

2.6 Установка и получение членов класса

Привычка C/C++ состоит в том, чтобы делать члены класса закрытыми и получать доступ к их значениям через ряд функций Set и Get. Хотя в Python также можно задать соответствующие функции Set и Get через @property, @setter, @deleter, следует избегать лишних абстракций, которые будут в 4-5 раз медленнее, чем прямой доступ.

2.7 Входные и выходные параметры функций

Привычка C/C++ состоит в том, чтобы перечислять входные и выходные параметры как параметры функции, изменять значение выходного параметра через указатель, возвращаемое значение функции является состоянием выполнения, а вызывающая функция проверяет возвращаемое значение. значение, чтобы определить, успешно ли выполнено выполнение. В Python вызывающему функцию не нужно проверять возвращаемое значение, и исключение выдается сразу, когда в функции встречается особый случай.

2.8 Чтение файлов

По сравнению с C/C++, Python намного проще для чтения файлов: открытый файл представляет собой итерируемый объект, который возвращает по одной строке за раз.

with open(file_path, 'rt', encoding='utf-8') as f:
   for line in f:
       print(line)       # 末尾的\n会保留

2.9 Объединение путей к файлам

Привычка C / C ++ обычно использует + для прямого объединения путей, что очень подвержено ошибкам. os.path.join в Python автоматически дополняет разделитель / или \ между путями в зависимости от разных операционных систем:

import os
os.path.join('usr', 'lib', 'local')

2.10 Анализ параметров командной строки

Хотя Python также может использовать sys.argv для разбора параметров командной строки напрямую, как C/C++, более удобно и мощнее использовать инструмент ArgumentParser в argparse.

2.11 Вызов внешних команд

Хотя Python также может использовать os.system для прямого вызова внешних команд, таких как C/C++, но использование subprocess.check_output может свободно выбирать, выполнять ли Shell или нет, а также может получать результаты выполнения внешних команд.

import subprocess
# 如果外部命令返回值非0,则抛出subprocess.CalledProcessError异常
result = subprocess.check_output(['cmd', 'arg1', 'arg2']).decode('utf-8')  
# 同时收集标准输出和标准错误
result = subprocess.check_output(['cmd', 'arg1', 'arg2'], stderr=subprocess.STDOUT).decode('utf-8')  
# 执行shell命令(管道、重定向等),可以使用shlex.quote()将参数双引号引起来
result = subprocess.check_output('grep python | wc > out', shell=True).decode('utf-8')

2.12 Не изобретайте велосипед

Не изобретайте велосипед, Python называет батареи включенными, а это означает, что Python предлагает решения для многих распространенных проблем.

3. Общие инструменты

3.1 Чтение и запись файлов CSV

import csv
# 无header的读写
with open(name, 'rt', encoding='utf-8', newline='') as f:  # newline=''让Python不将换行统一处理
    for row in csv.reader(f):
        print(row[0], row[1])  # CSV读到的数据都是str类型
with open(name, mode='wt') as f:
    f_csv = csv.writer(f)
    f_csv.writerow(['symbol', 'change'])

# 有header的读写
with open(name, mode='rt', newline='') as f:
    for row in csv.DictReader(f):
        print(row['symbol'], row['change'])
with open(name, mode='wt') as f:
    header = ['symbol', 'change']
    f_csv = csv.DictWriter(f, header)
    f_csv.writeheader()
    f_csv.writerow({'symbol': xx, 'change': xx})

Обратите внимание, что при слишком большом размере CSV-файла будет сообщено об ошибке: _csv.Error: поле больше предела поля (131072), что можно устранить, изменив верхний предел.

import sys
csv.field_size_limit(sys.maxsize)

csv также может читать данные, разделенные \t

f = csv.reader(f, delimiter='\t')

3.2 Инструменты итератора

Многие инструменты итераторов определены в itertools, например инструмент подпоследовательности:

import itertools
itertools.islice(iterable, start=None, stop, step=None)
# islice('ABCDEF', 2, None) -> C, D, E, F

itertools.filterfalse(predicate, iterable)         # 过滤掉predicate为False的元素
# filterfalse(lambda x: x < 5, [1, 4, 6, 4, 1]) -> 6

itertools.takewhile(predicate, iterable)           # 当predicate为False时停止迭代
# takewhile(lambda x: x < 5, [1, 4, 6, 4, 1]) -> 1, 4

itertools.dropwhile(predicate, iterable)           # 当predicate为False时开始迭代
# dropwhile(lambda x: x < 5, [1, 4, 6, 4, 1]) -> 6, 4, 1

itertools.compress(iterable, selectors)            # 根据selectors每个元素是True或False进行选择
# compress('ABCDEF', [1, 0, 1, 0, 1, 1]) -> A, C, E, F

Сортировка последовательности:

sorted(iterable, key=None, reverse=False)

itertools.groupby(iterable, key=None)              # 按值分组,iterable需要先被排序
# groupby(sorted([1, 4, 6, 4, 1])) -> (1, iter1), (4, iter4), (6, iter6)

itertools.permutations(iterable, r=None)           # 排列,返回值是Tuple
# permutations('ABCD', 2) -> AB, AC, AD, BA, BC, BD, CA, CB, CD, DA, DB, DC

itertools.combinations(iterable, r=None)           # 组合,返回值是Tuple
itertools.combinations_with_replacement(...)
# combinations('ABCD', 2) -> AB, AC, AD, BC, BD, CD

Объединить несколько последовательностей:


itertools.chain(*iterables)                        # 多个序列直接拼接
# chain('ABC', 'DEF') -> A, B, C, D, E, F

import heapq
heapq.merge(*iterables, key=None, reverse=False)   # 多个序列按顺序拼接
# merge('ABF', 'CDE') -> A, B, C, D, E, F

zip(*iterables)                                    # 当最短的序列耗尽时停止,结果只能被消耗一次
itertools.zip_longest(*iterables, fillvalue=None)  # 当最长的序列耗尽时停止,结果只能被消耗一次

3.3 Счетчик

Счетчик подсчитывает количество вхождений каждого элемента в итерируемом объекте.

import collections
# 创建
collections.Counter(iterable)

# 频次
collections.Counter[key]                 # key出现频次
# 返回n个出现频次最高的元素和其对应出现频次,如果n为None,返回所有元素
collections.Counter.most_common(n=None)

# 插入/更新
collections.Counter.update(iterable)
counter1 + counter2; counter1 - counter2  # counter加减

# 检查两个字符串的组成元素是否相同
collections.Counter(list1) == collections.Counter(list2)

3.4 Dict со значениями по умолчанию

При доступе к несуществующему ключу defaultdict установит для него некоторое значение по умолчанию.

import collections
collections.defaultdict(type)  # 当第一次访问dict[key]时,会无参数调用type,给dict[key]提供一个初始值

3.5 Упорядоченный словарь

import collections
collections.OrderedDict(items=None)  # 迭代时保留原始插入顺序

4. Высокопроизводительное программирование и отладка

4.1 Выходные сообщения об ошибках и предупреждения

вывод информации в стандартную ошибку

import sys
sys.stderr.write('')

вывод предупреждающего сообщения

import warnings
warnings.warn(message, category=UserWarning)  
# category的取值有DeprecationWarning, SyntaxWarning, RuntimeWarning, ResourceWarning, FutureWarning

Управление выводом предупреждающих сообщений

$ python -W all     # 输出所有警告,等同于设置warnings.simplefilter('always')
$ python -W ignore  # 忽略所有警告,等同于设置warnings.simplefilter('ignore')
$ python -W error   # 将所有警告转换为异常,等同于设置warnings.simplefilter('error')

4.2 Тестирование в коде

Иногда для отладки мы хотим добавить в код некоторый код, обычно несколько операторов печати, которые можно записать так:

# 在代码中的debug部分
if __debug__:
    pass

После завершения отладки эта часть кода игнорируется путем выполнения параметра -O в командной строке:

$ python -0 main.py

4.3 Проверка стиля кода

Используя pylint, можно выполнить большую проверку стиля кода и синтаксиса, а также найти некоторые ошибки перед запуском.

pylint main.py

4.4 Код, отнимающий много времени

длительный тест

$ python -m cProfile main.py

Требуется много времени для тестирования блока кода

# 代码块耗时定义
from contextlib import contextmanager
from time import perf_counter

@contextmanager
def timeblock(label):
    tic = perf_counter()
    try:
        yield
    finally:
        toc = perf_counter()
        print('%s : %s' % (label, toc - tic))

# 代码块耗时测试
with timeblock('counting'):
    pass

Некоторые принципы трудоемкой оптимизации кода

  • Сосредоточьтесь на оптимизации узких мест в производительности, а не всего кода.
  • Избегайте использования глобальных переменных. Поиск локальных переменных выполняется быстрее, чем глобальных, а код, определяющий глобальные переменные в функции, обычно выполняется на 15–30 % быстрее.
  • Избегайте использования свойств .access. Быстрее использовать имя импорта из модуля, чтобы поместить часто используемую переменную члена класса self.member в локальную переменную.
  • По возможности используйте встроенные структуры данных. str, list, set, dict и т. д. реализованы на C и работают очень быстро.
  • Избегайте создания ненужных промежуточных переменных и функции copy.deepcopy().
  • Конкатенация строк, например, a + ':' + b + ':' + c создаст много бесполезных промежуточных переменных, ':', join([a, b, c]) будет намного эффективнее. Также необходимо учитывать, необходима ли конкатенация строк, например, print(':'.join([a, b, c])) менее эффективна, чем print(a, b, c, sep=':') .

5. Другие советы по Python

5.1 аргмин и аргмакс

items = [2, 1, 3, 4]
argmin = min(range(len(items)), key=items.__getitem__)

То же самое верно и для argmax.

5.2 Транспонирование двумерного списка

A = [['a11', 'a12'], ['a21', 'a22'], ['a31', 'a32']]
A_transpose = list(zip(*A))  # list of tuple
A_transpose = list(list(col) for col in zip(*A))  # list of list

5.3 Развертывание одномерного списка в двумерный список

A = [1, 2, 3, 4, 5, 6]

# Preferred.
list(zip(*[iter(A)] * 2))

Автор: Бэйцзян Айгуо