Всем привет.
Сегодня я поделюсь с вами 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