5 лет владения Python, в сумме 10 навыков разработки! Нетизен: очень полезно

Python

Всем привет.

Сегодня я поделюсь с вами 10 советами по разработке Python, которые я обычно систематизирую очень практично.Каталог содержимого выглядит следующим образом:

Стоит отметить, что все эти 10 советов включены в «Руководство по черной магии Python», написанное мной.

Вы можете получить красиво отформатированные электронные книги в формате PDF, отправив «Черную магию» в фоновом режиме следующим образом.

1. Как просмотреть исходный код в рабочем состоянии?

Глядя на исходный код функции, мы обычно делаем это с помощью IDE.

Например, в Pycharm вы можете щелкнуть исходный код функции ввода.

А если нет IDE?

Когда мы хотим использовать функцию, как мы узнаем, какие параметры она должна получить?

Когда у нас возникают проблемы с использованием функций, как мы можем устранить проблему, прочитав исходный код?

В настоящее время мы можем использовать проверку вместо IDE, чтобы помочь вам сделать это.

# demo.py
import inspect


def add(x, y):
    return x + y

print("===================")
print(inspect.getsource(add))

Текущий результат выглядит следующим образом

$ python demo.py
===================
def add(x, y):
    return x + y

2. Как отключить автоматический контекст, связанный с исключениями?

Когда вы обрабатываете исключение из-за неправильной обработки или других проблем, когда другое исключение генерируется снова, выброшенное исключение также будет содержать исходную информацию об исключении.

нравится.

try:
    print(1 / 0)
except Exception as exc:
    raise RuntimeError("Something bad happened")

На выходе можно увидеть два сообщения об исключении.

Traceback (most recent call last):
  File "demo.py", line 2, in <module>
    print(1 / 0)
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "demo.py", line 4, in <module>
    raise RuntimeError("Something bad happened")
RuntimeError: Something bad happened

Если исключение генерируется в обработчике исключений или блоке finally, механизм исключений работает неявно по умолчанию, прикрепляя предыдущее исключение в качестве нового исключения.__context__Атрибуты. Это контекст исключения автоматической корреляции, который Python включает по умолчанию.

Если вы хотите самостоятельно контролировать контекст, вы можете добавить ключевое слово from (fromСинтаксис имеет ограничение, заключающееся в том, что второе выражение должно быть другим классом исключения или экземпляром. ), чтобы указать, какое исключение было непосредственно вызвано вашим новым исключением.

try:
    print(1 / 0)
except Exception as exc:
    raise RuntimeError("Something bad happened") from exc

Вывод выглядит следующим образом

Traceback (most recent call last):
  File "demo.py", line 2, in <module>
    print(1 / 0)
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "demo.py", line 4, in <module>
    raise RuntimeError("Something bad happened") from exc
RuntimeError: Something bad happened

Конечно, вы также можетеwith_traceback()метод устанавливает контекст для исключения__context__собственность, это также может бытьtracebackЛучшее отображение информации об исключении.

try:
    print(1 / 0)
except Exception as exc:
    raise RuntimeError("bad thing").with_traceback(exc)

Наконец, что, если я хочу полностью отключить этот механизм автоматической корреляции контекстов исключений? Что еще мы можем сделать?

можно использоватьraise...from None, из следующего примера нет исходного исключения

$ cat demo.py
try:
    print(1 / 0)
except Exception as exc:
    raise RuntimeError("Something bad happened") from None
$
$ python demo.py
Traceback (most recent call last):
  File "demo.py", line 4, in <module>
    raise RuntimeError("Something bad happened") from None
RuntimeError: Something bad happened
(PythonCodingTime)

03. Самый быстрый способ просмотреть путь поиска пакетов

Когда вы используете import для импорта пакета или модуля, Python будет искать в некоторых каталогах, и эти каталоги находятся в порядке приоритета, и обычные люди будут использовать sys.path для просмотра.

>>> import sys
>>> from pprint import pprint   
>>> pprint(sys.path)
['',
 '/usr/local/Python3.7/lib/python37.zip',
 '/usr/local/Python3.7/lib/python3.7',
 '/usr/local/Python3.7/lib/python3.7/lib-dynload',
 '/home/wangbm/.local/lib/python3.7/site-packages',
 '/usr/local/Python3.7/lib/python3.7/site-packages']
>>> 

Есть ли более быстрый способ?

Есть ли способ, которым мне даже не нужно входить в консольный режим?

Вы можете подумать об этом, но по сути это то же самое, что и выше

[wangbm@localhost ~]$ python -c "print('\n'.join(__import__('sys').path))"

/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg
/usr/lib/python2.7/site-packages/redis-3.0.1-py2.7.egg
/usr/lib64/python27.zip
/usr/lib64/python2.7
/usr/lib64/python2.7/plat-linux2
/usr/lib64/python2.7/lib-tk
/usr/lib64/python2.7/lib-old
/usr/lib64/python2.7/lib-dynload
/home/wangbm/.local/lib/python2.7/site-packages
/usr/lib64/python2.7/site-packages
/usr/lib64/python2.7/site-packages/gtk-2.0
/usr/lib/python2.7/site-packages

То, что я хочу представить здесь, это гораздо более удобный метод, чем два вышеуказанных метода, которые можно решить одной строкой команды.

[wangbm@localhost ~]$ python3 -m site
sys.path = [
    '/home/wangbm',
    '/usr/local/Python3.7/lib/python37.zip',
    '/usr/local/Python3.7/lib/python3.7',
    '/usr/local/Python3.7/lib/python3.7/lib-dynload',
    '/home/wangbm/.local/lib/python3.7/site-packages',
    '/usr/local/Python3.7/lib/python3.7/site-packages',
]
USER_BASE: '/home/wangbm/.local' (exists)
USER_SITE: '/home/wangbm/.local/lib/python3.7/site-packages' (exists)
ENABLE_USER_SITE: True

Как видно из вывода, путь этого столбца будет более полным, чем sys.path, который содержит каталог среды пользователя.

4. Пишите вложенные циклы for одной строкой

У нас часто бывает следующий вложенный код цикла for

list1 = range(1,3)
list2 = range(4,6)
list3 = range(7,9)
for item1 in list1:
    for item2 in list2:
      	for item3 in list3:
        	  print(item1+item2+item3)

Здесь всего три цикла for, в реальном кодировании слоев может быть больше.

Такой код очень плохо читается, многие не хотят писать его таким, но лучше его написать не придумаешь.

Вот способ написания, который я обычно использую, используя библиотеку itertools для получения более элегантного и читаемого кода.

from itertools import product
list1 = range(1,3)
list2 = range(4,6)
list3 = range(7,9)
for item1,item2,item3 in product(list1, list2, list3):
    print(item1+item2+item3)

Вывод выглядит следующим образом

$ python demo.py
12
13
13
14
13
14
14
15

5. Как использовать print для вывода логов

Новичкам нравится использовать печать для отладки кода и записи выполнения программы.

Однако print только выводит содержимое на терминал и не может быть сохранено в файле журнала, что не способствует устранению неполадок.

Если вы хотите использовать печать для отладки кода (хотя это не лучшая практика) и записи процесса запуска программы, то описанное ниже использование печати может быть вам полезно.

Как функция печати в Python 3, функция становится более мощной, поскольку она может принимать больше параметров.Указание некоторых параметров может выводить содержимое печати в файл журнала.

код показывает, как показано ниже:

>>> with open('test.log', mode='w') as f:
...     print('hello, python', file=f, flush=True)
>>> exit()

$ cat test.log
hello, python

6. Как быстро рассчитать время работы функции

Чтобы рассчитать время выполнения функции, вы можете сделать что-то вроде

import time

start = time.time()

# run the function

end = time.time()
print(end-start)

Посмотрите, сколько строк кода вы написали для расчета времени выполнения функции.

Есть ли способ более удобно рассчитать это время работы?

имеют.

Есть встроенный модуль timeit.

Используйте его всего с одной строкой кода

import time
import timeit

def run_sleep(second):
    print(second)
    time.sleep(second)

# 只用这一行
print(timeit.timeit(lambda :run_sleep(2), number=5))

Текущий результат выглядит следующим образом

2
2
2
2
2
10.020059824

7. Повысьте эффективность с помощью собственного механизма кэширования

Кэш сохраняется для сохранения количественных данных, чтобы подготовить способ обработки последующих требований к сбору данных, что предназначено для ускорения скорости сбора данных.

Процесс генерации данных может потребовать таких операций, как расчет, регуляризация и удаленный сбор.Если одни и те же данные необходимо использовать несколько раз, каждый раз создавать их заново будет пустой тратой времени. Следовательно, если данные, полученные с помощью таких операций, как расчеты или удаленные запросы, кэшируются, последующие требования к получению данных будут ускорены.

Чтобы выполнить это требование, Python 3.2+ предоставляет нам механизм, который можно легко реализовать, не требуя от вас написания такого логического кода.

Этот механизм реализован в декораторе lru_cache в модуле functool.

@functools.lru_cache(maxsize=None, typed=False)

Интерпретация параметра:

  • maxsize: Максимальное количество результатов вызова этой функции может быть кэшировано. Если None, ограничений нет, при значении степени 2 производительность будет максимальной.
  • Напечатано: если true, звонки с различными типами параметров будут кэшироваться отдельно.

Например

from functools import lru_cache

@lru_cache(None)
def add(x, y):
    print("calculating: %s + %s" % (x, y))
    return x + y

print(add(1, 2))
print(add(1, 2))
print(add(2, 3))

Вывод следующий, вы можете видеть, что второй вызов фактически не выполняет тело функции, а напрямую возвращает результат в кэш

calculating: 1 + 2
3
3
calculating: 2 + 3
5

Ниже приведена классическая последовательность Фибоначчи.Когда вы укажете большее n, будет много повторных вычислений

def fib(n):
    if n < 2:
        return n
    return fib(n - 2) + fib(n - 1)

Время, введенное в шестом пункте, теперь можно использовать для проверки того, насколько можно повысить эффективность.

Время работы 31 секунда без lru_cache

import timeit

def fib(n):
    if n < 2:
        return n
    return fib(n - 2) + fib(n - 1)



print(timeit.timeit(lambda :fib(40), number=1))
# output: 31.2725698948

После использования lru_cache скорость работы слишком высока, поэтому я изменил значение n с 30 до 500, но даже при этом время работы составляет всего 0,0004 секунды. Прирост скорости очень существенный.

import timeit
from functools import lru_cache

@lru_cache(None)
def fib(n):
    if n < 2:
        return n
    return fib(n - 2) + fib(n - 1)

print(timeit.timeit(lambda :fib(500), number=1))
# output: 0.0004921059880871326

8. Трюки для выполнения кода перед выходом из программы

С помощью встроенного модуля atexit можно легко прописать функцию выхода.

Независимо от того, где вы вызываете сбой программы, те функции, которые вы зарегистрировали, будут выполняться.

Примеры следующие

еслиclean()У функции есть параметры, тогда вы можете вызвать ее напрямую без декоратораatexit.register(clean_1, 参数1, 参数2, 参数3='xxx').

Могут быть и другие способы справиться с этим требованием, но это, безусловно, более элегантно и удобно, чем не использовать atexit, и его легко расширить.

Но использование Atexit все еще имеет некоторые ограничения, такие как:

  • Если программа завершается системным сигналом, который вы не обработали, зарегистрированная функция не будет выполняться должным образом.
  • Если произойдет серьезная внутренняя ошибка Python, зарегистрированная вами функция не будет выполняться должным образом.
  • Если вы вызовете вручнуюos._exit(), функция, которую вы зарегистрировали, не может быть выполнена должным образом.

9. Внедрите отложенные вызовы, подобные отложенным

В Golang есть механизм отложенного вызова, ключевое слово defer, как в следующем примере.

import "fmt"

func myfunc() {
    fmt.Println("B")
}

func main() {
    defer myfunc()
    fmt.Println("A")
}

Вывод следующий, вызов myfunc завершится за один шаг до возврата функции, даже если написать вызов myfunc в первой строке функции, это отложенный вызов.

A
B

Так есть ли такой механизм в Python?

Конечно есть, но не так просто, как Голанг.

Доступно на Питонеменеджер контекстадостичь этого эффекта

import contextlib

def callback():
    print('B')

with contextlib.ExitStack() as stack:
    stack.callback(callback)
    print('A')

Вывод выглядит следующим образом

A
B

10. Как стримить и читать несколько гигабайт больших файлов

Используйте with...open... для чтения данных из файла — операция, знакомая всем разработчикам Python.

Но при неправильном использовании он тоже может принести немало неприятностей.

Например, когда вы используете функцию чтения, Python фактически загрузит содержимое файла в память за один раз.Если файл имеет размер 10 ГБ или более, ваш компьютер будет потреблять огромное количество памяти.

# 一次性读取
with open("big_file.txt", "r") as fp:
    content = fp.read()

Для этой проблемы вы можете подумать об использовании readline, чтобы генератор возвращал строку за строкой.

def read_from_file(filename):
    with open(filename, "r") as fp:
        yield fp.readline()

Но если содержимое этого файла всего одна строка, а одна строка — 10 Гб, по сути, вы все равно прочитаете все содержимое за один раз.

Самое элегантное решение — указать, что при использовании метода чтения каждый раз считывается только содержимое фиксированного размера.Например, в следующем коде каждый раз возвращается только 8 КБ.

def read_from_file(filename, block_size = 1024 * 8):
    with open(filename, "r") as fp:
        while True:
            chunk = fp.read(block_size)
            if not chunk:
                break

            yield chunk

В приведенном выше коде нет проблем с функционированием, но он все еще выглядит немного раздутым.

Код можно оптимизировать с помощью частичных функций и итерационных функций.

from functools import partial

def read_from_file(filename, block_size = 1024 * 8):
    with open(filename, "r") as fp:
        for chunk in iter(partial(fp.read, block_size), ""):
            yield chunk