Skip to content
|K07e7su edited this page Jun 13, 2018 · 42 revisions

Install virtualenv on python2

python2 -m pip install virtualenv
virtualenv --python=/usr/bin/python2.6 venv

Activate venv

source venv/bin/activate

Install requirements

pip install -r requirements.txt

Recall that you can check the packages used by the current environment with

pip freeze

Установка Устанавливаем последнюю версию pip в систему, можно делать разными способами, рекомендую также использовать virtualenv:

apt-get install python-pip

pip install -U pip

Для последних версий MySQL-python требуется версия distribute >= 0.6.28, поэтому обновляем его тоже:

pip install -U distribute>=0.6.28

Затем, устанавливаем требуемые dev-пакеты:

apt-get install python-dev libmysqlclient-dev

Устанавливаем MySQL-python:

pip install MySQL-python

Какой web-сервер обслуживает url?

Название web-сервера можно узнать из заголовка Server HTTP ответа.

>>> from requests import get
>>> get('https://stepic.org/favicon.ico').headers['Server']
'nginx/1.10.3'

wget -r -k -l 7 -p -E -nc https://stepik.org/explore

Run autotests

python -m pytest -v tests/test_generator.py

Exception hierarchy

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |         +-- UnicodeError
      |              +-- UnicodeDecodeError
      |              +-- UnicodeEncodeError
      |              +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
	   +-- ImportWarning
	   +-- UnicodeWarning
	   +-- BytesWarning

Dict to string

''.join('{}{}'.format(key, val) for key, val in adict.items())

Magic to transform string representation of a Dictionary to a dictionary

string = ast.literal_eval(string)
assert type(new_dict) is dict

Read multiple parameters from console

def main():
    a, b = map(int, input().split())
    res = a + b
    print(res)


if __name__ == "__main__":
    main()

Which BLAS we use in out numpy and spark

import numpy as np
np.show_config()

Classes

class Song:
    def __init__(self, artist, song):
        self.artist = artist
        self.song = song
        self.tags = []


    def add_tags(self, *args):
        self.tags.extend(args)


song1 = Song("artist_name1", "song_name2")
song1.add_tags("tag1", "tag2")

song2 = Song("artist_name3", "song_name4")
song2.add_tags("tag3", "tag4")

song2.tags
class MoneyBox:
    def __init__(self, capacity):
        self.capacity = capacity
        self.value = 0

    def can_add(self, v):
        if (v <= (self.capacity - self.value)):
            return True
        else:
            return False

    def add(self, v):
        self.value = self.value + v

Check instance

isinstance(x,y)

Check class

type()

Unit-test

import unittest

class TestStringMethods(unittest.TestCase):

  def test_upper(self):
      self.assertEqual('foo'.upper(), 'FOO')

  def test_isupper(self):
      self.assertTrue('FOO'.isupper())
      self.assertFalse('Foo'.isupper())

  def test_split(self):
      s = 'hello world'
      self.assertEqual(s.split(), ['hello', 'world'])
      # Проверим, что s.split не работает, если разделитель - не строка
      with self.assertRaises(TypeError):
          s.split(2)

if __name__ == '__main__':
    unittest.main()

Функции

Синтаксис объявления функций

# return использовать не обязательно, функция вернёт None

# Для документации функции используются строковые литералы
>>> def foo():
...     """I return 42."""
...     return 42
...

# После объявлении функции документация доступна через определённый атрибут
>>> foo.__doc__
'I return 42.'

# В интерпретаторе удобнее пользоваться встроенной функцией
>>> help(foo)  # или foo? в IPython.

Улучшение функции min()

>>> def min(x, y):                #    __o
...     return x if x < y else y  #  _`\<,_
...                               # (_)/ (_)

# 1. Находить минимум произвольного количества аргументов
>>> min(-5, 12, 13)
-5

# 2. Использовать функцию min для кортежей, списков, множеств и других последовательностей 
>>> xs = {-5, 12, 13}
>>> min(???)
-5

# 3. Ограничить минимум произвольным отрезком [lo, hi]
>>> bounded_min(-5, 12, 13, lo=0, hi=255)
12

# 4. По заданным lo и hi строить функцию bounded_min (фабрика функций)
>>> bounded_min = make_min(lo=0, hi=255)
>>> bounded_min(-5, 12, 13)
12

Упаковка агрументов

# 1.a
def min(*args):  # type(args) == tuple
    res = float("inf")  # инициализируем бесконечностью
    for arg in args:
        if arg < res:
            res = arg
        return res

# >>> min(-5, 12, 13)
# -5

# >>> min()
# inf
1.b
def min(first, *args):
    res = first
    # ...

# min()
# TypeError: min() missing 1 required [...] argument: 'first'

Распаковка аргументов

# 2.a
# Синтаксис будет работать с любым объектом, поддерживающим протокол итератора
# Итератор - что-то, что можно обойти

>> xs = {-5, 12, 13}  # но вообще set лучше не юзать
>>> min(*xs)  # здесь он не критичен, но вообще, распаковывать set - странная идея
-5

>>> min(*[-5, 12, 13])
-5

>>> min(*(-5, 12, 13))
-5
...

Ключевые аргументы: аргументы по умолчанию

# 3.a
def bounded_min(first, *args, lo=float("-inf"),
                hi=float("inf")):
    res = hi
    # итерируемся по первому элементу + всё остальное
    for arg in (first, ) + args:  # args - это кортеж, 
                                  # соответственно fisrt тоже надо превратить к кортеж :)
        if arg < res and lo < arg < hi:
            res = arg
    return max(res, lo)

# >>> bounded_min(-5, 12, 13, lo=0, hi=255)
# 12

Ключевые элементы: подводные камни

# Нужно было написать функцию, которая принимает что-то, 
# по чему можно итерироваться и возвращает список уникальных элементов
def unique(iterable, seen=set()):
    acc = []
    for item in iterable:
        if item not in seen:
            seen.add(item)
            acc.append(item)
    return acc

# >>> xs = [1, 1, 2, 3]
# >>> unique(xs)
# [1, 2, 3]

# >>> unique(xs)
# []
# Так происходит, потому что значение по умолчанию инициализируется ровно один раз -
#                                                   - в момент компиляции в байт-код
# Т.е. все вызовы unique() будут различать значения атрибута seen
# Вывод: никогда не использовать изменяемые значения в качестве значений по умолчанию
# (никакие списки, никакие словари)

# Атрибут __defaults__ - кортеж с инициализированными значениями элементов по умолчанию
# >>> unique.__defaults__
# ({1, 2, 3},)

Ключевые элементы: правильная инициализация

def unique(iterable, seen=None):  # None не изменяемый, потому что он Singleton
    seen = set(seen or [])  # None --- falsy. 
    # Поэтому если аргумент=None, то он станет пустым списком
    # OR подразумевает операции над булевыми переменными,
    #                        поэтому, если они не булевы, 
    #                        то он попытается их привести к таковым
    # А если первая штука truesy, то мы берём её значение
    
    acc = []
    for item in iterable:
        if item not in seen:
            seen.add(item)
            acc.append(item)
    return acc


# >>> xs = [1, 1, 2, 3]
# >>> unique(xs)
# [1, 2, 3]

# >>> unique(xs)
# [1, 2, 3]

Falsy:

None
0
0,00
""
[] () {}
set()

Ключевые аргуенты: и только ключевые

# Если функция имеет фиксированную арность, 
# то ключевые агрументы можно передавать без явного указания имени:
>>> def flatten(xs, depth=None):
...     pass
...
>>> flatten([1, [2], 3], depth=1)
>>> flatten([1, [2], 3], 1)

# Можно явно потребовать, чтобы часть аргументов всегда передавалась как ключевые:
>>> def flatten(xs, *, depth=None):  # звёздочка всё кушает и не связывает это с именем
# Всё, что идёт после звёздочки - ключевые аргументы
...     pass
...
>>> flatten([1, [2], 3], 2)
TypeError: flatten() takes 1 positional argument [...]

Ключевые аргументы: упаковка и распаковка

def runner(cmd, **kwargs):
    if kwargs.get("verbose", True):
        print("Logging enabled")

>>> runner("mysqld", limit=42)
Logging enabled
>>> runner("mysqld", **{"verbose": False})
>>> options = {"verbose": False}
>>> runner("mysqld", **options)

Упаковка и распаковка

# Присваивание
acc = []
seen = set()
(acc, seen) = ([], set())

# В качестве правого объекта можно использовать любой объект,
# поддерживающий протокол итератора
x, y, z = [1, 2, 3]
x, y, z = {1, 2, 3}  # unordered
x, y, z = "xyz"
x, y = y, x  # по сути справа - tuple (y, x)

# Скобки обычно опускают, но иногда они бывают полезны
rectangle = (0, 0), (4, 4)
(x1, y1), (x2, y2) = rectangle

Расширенный синтаксис распаковки

# В Python 3.0 можно юзать * (что-то мы распакуем, а что-то обратно запакуем)
# rest всегда приводится к списку и копирует то, что справа
>>> first, *rest = range(1, 5)
>>> first, rest
(1, [2, 3, 4])

# * можно использовать в любом месте приложения
>>> first, *rest, last = range(1, 5)
>>> last
4

>>> first, *rest, last = [42]
ValueError: need more than 1 values to unpack

# Работает рекурсивно тоже
>>> *_, (first, *rest) = [range(1, 5)] * 5
>>> fisrt
1

https://www.python.org/dev/peps/pep-3132/

Распаковка и цикл for

# Синтаксис распаковки работает и в цикле for, например:
for a, *b in [range(4), range(2)]:
    print(b)

# [1, 2, 3]
# [1]

Распаковка и байткод

>>> import dis
>>> dis.dis("first, *rest, last = ('a', 'b', 'c')")
     0 LOAD_CONST               4 (('a', 'b', 'c'))
     3 UNPACK_EX              257
     6 STORE_NAME               0 (first)
     9 STORE_NAME               1 (rest)
    12 STORE_NAME               2 (last)
    15 LOAD_CONST               3 (None)
    18 RETURN_VALUE
# Мораль: присваивание в Python работает слева-направо
>>> x, (x, y) = 1, (2, 3)  # не надо так писать :)
>>> x
2
>>> dis.dis("first, *rest, last = ['a', 'b', 'c']")
     0 LOAD_CONST               0 (1)
     3 LOAD_CONST               1 (2)
     6 LOAD_CONST               2 (3)
     9 BUILD_LIST               3
    12 UNPACK_EX              257
    15 STORE_NAME               0 (first)
    18 STORE_NAME               1 (rest)
    21 STORE_NAME               2 (last)
    24 LOAD_CONST               3 (None)
    27 RETURN_VALUE
# Мораль: Синтаксически схожие конструкции могут 
#         иметь различную семантику времени исполнения

Упаковка и распаковка: резюме

# Функции в Python могут принимать произвольное количество
# позиционных и ключевых аргументов

# Для объявления таких функций используется синтаксис упаковки,
# а для вызова синтаксис распаковки
>>> def f(*args, **kwargs):
...     pass
...
>>> f(1, 2, 3, **{"foo":42})

# Синтаксис распаковки также можно использовать при присваивании
# нескольких аргументов в цикле for:
>>> first, *rest = range(4)
>>> for first, *rest in [range(4), range(2)]:
...     pass
...

Дополниетльные расширения синтаксиса распаковки

# В Python 3.5 возможности распаковки были в очередной раз расширены
# Теперь можем распаковать произвольное количество объектов
# https://www.python.org/dev/peps/pep-0448/

# Изменения затронули распаковку при вызове функции:
>>> def f(*args, **kwargs):
...     print(args, kwargs)
...
>>> f(1, 2, *[3, 4], *[5],
...   foo="bar", **{"baz": 42}, boo=24)
# (1, 2, 3, 4, 5) {'baz': 42, 'boo': 24, 'foo': 'bar'}

# и при инициализации контейнеров
>>> defaults = {"host": "0.0.0.0", "port": 8080}
>>> {**defaults, "port": 80}  # замещает значения по ключу
{'host': '0.0.0.0', 'port': 80}
>>> [*range(5), 6]  # аналогично для множества и кортежа
[0, 1, 2, 3, 4, 5, 6]

Области видимости aka scopes

Функции внутри функций внутри функций

  • В отличие от Java (< 8), C/C++ (< 11) в Python функции - объекты первого класса. То есть с ними можно делать то же самое, что и с другими значениями. Можно функцию передать в другую функцию, можно функцию вернуть из функции.
  • Например, можно объявлять функции внутри других функций
>>> def wrapper():
...     def identity(x):
...         return x
...     return identity
...
>>> f = wrapper()
>>> f(42)
42

Фабрика функций

def make_min(*, lo, hi):
    def inner(first, *args):
       res = hi
       for arg in (first,) + args:
           if arg < res and lo < arg < hi:
               res = arg
       return max(res, lo)
    return inner

# >>> bounded_min = make_min(lo=0, hi=255)
# >>> bounded_min(-5, 12, 13)
# 0 

Области видимости: LEGB

>>> min              # builtin
<built-in function min>
>>> min = 42         # global
>>> def f(*args):
...     min = 2
...     def g():     # enclosing
...         min = 4  # local
...         print(min)
...

# Правило LEGB
# Поиск имени ведётся не более чем в четырёх областях видимости:
# локальной, затем в объемлющей функции (если такая имеется), затем
# в глобальной и, наконец, во встроенной.

Области видимости: интроспекция

>>> min = 42     # = globals()['min'] = 42
>>> globals()    # изменяемый словарь глобальных переменных
{..., 'min': 42}
>>> def f():
...     min = 2  # = locals()['min'] = 2
...     print(locals())
...
>>> f()
{'min': 2}

Области видимости: замыкания

  • Функции в Python могут использовать переменные, определённые во внешних областях видимости
  • Важно помнить, что поиск переменных осуществляется во время исполнения функции, а не во время объявления
>>> def f():
...     print(i)
...
>>> for i in range(4):
...     f()
...
0
1
2
3

Области видимости: присваивание

  • Для присваивания правило LEGB не работает. Присваивание абсолютно всегда создаёт имя в локальной области видимости
>>> min = 42
>>> def f():
...     min += 1
...     return min
...
>>> f()
UnboundLocalError: local variable 'min' referenced [...]
  • По умолчанию операция присваивания создаёт локальную переменную
  • Изменить это поведение можно с помощью операторов local и nonlocal

Оператор global

  • Позволяет модифицировать значение переменной из глобальной области видимости
>>> min = 42
>>> def f():
...     global min
...     min += 1
...     return min
...
>>> f()
43
>>> f()
44
  • Использование global порочно, почти всегда лучше заменить global на thread-local объект

Оператор nonlocal

  • Позволяет модифицировать значение переменной из объемлющей области видимости
# Мы хотим сделать "изменяемую ячейку памяти" value,
# при этом мы хотим инкапсулировать её значение (никаким
# другим способом пользователь не сможет получить её значение, кроме вызвав get(),
# потому что переменная value локальная

# Переменная get просто использует value (мы пойдём наверх и 
# найдём в объемлющей области видимости value)

# С set другая история, там нужен nonlocal чтобы мы не создали локальную переменную, 
# а изменили внешнюю
def cell(value=None):
    def get():
        return value
    def set(update):
        nonlocal value
        value = update
    return get, set

# >>> get, set = cell()
# >>> set(42)
# >>> get()
# 42

Почитать мысли разработчиков на эту тему (PEP-3104)

Области видимости: резюме

  • В Python четыре области видимости: встроенная, глобальная, объемлющая и локальная
  • Правило LEGB: поиск имени осуществляется от локальной к встроенной
  • При использовании операции присваивания имя считается локальным. Это поведение можно изменить с помощью операторов global и nonlocal.

Функциональное программирование

Функциональное программирование: анонимные функции

  • Python не функциональный язык, но в нём есть элементу функционального программирования
  • Анонимные функции имеют вид
>>> lambda arguments: expression

и эквивалентны по поведению

>>> def <lambda>(arguments):
...     return expression
  • Всё, сказанное про аргументы именованных функций, справедливо и для анонимных
>>> lambda foo, *args, bar=None, **kwargs: 42

Функциональное программирование: map

// Функции высшего порядка - функции, которые принимают другие функции

  • Применяет функцию к каждому элементу последовательности (iterable, т.е. объекту, поддерживающему протокол итератора)
# Первый аргумент - это функция, которую мы применим к последовательности из итераторов
# Map ленивый, ничего не вернёт и считать не будет, если не указать
>>> map(indentity, range(4))
<map object>
>>> list(map(identity, range(4)))
[0, 1, 2, 3]
>>> set(map(lambda x: x % 7, [1, 9, 16, -1, 2, 5]))
{1, 2, 5, 6}
>>> map(lambda s: s.strip(), open("./HBA1.txt"))
<map object>
  • или последовательностей, количество элементов в результате определяется длиной наименьшей из последовательностей
>>> list(map(lambda x, n: x ** n,
...          [2, 3], range(1, 8)))
[2, 9]

Функциональное программирование: filter

Принимает предикат (т.е. какую-то функцию, возвращающую truthy value) и принимает коллекцию

  • Убирает из последовательности элементы, не удовлетворяющие предикату
>>> filter(lambda x: x % 2 != 0, range(10))
<filter object>
>>> list(filter(lambda x: x % 2 != 0, range(10)))
[1, 3, 5, 7, 9]
  • Вместо предиката можно передать None, в этом случае в последовательности останутся только truthy значения
>>> xs = [0, None, [], {}, set(), "", 42]
>>> list(filter(None, xs))
[42]

Функциональное программирование: zip

Сшивает несколько последовательностей

  • Строит последовательность кортежей из элементов нескольких последовательностей
>>> list(zip("abc", range(3), [42j, 42j, 42j]))
[('a', 0, 42j), ('b', 1, 42j), ('c', 2, 42j)]
  • Поведение в случае последовательностей различной длины аналогично map
>>> list(zip("abc", range(10)))
[('a', 0), ('b', 1), ('c', 2)]
  • Выражение zip через map
map(lambda *args, argf)

Функциональное программирование: генераторы списков

Выглядят как плоский цикл for

  • Пришли в Python из языка ABC, который позаимствовал их из языка SETL
>>> [x ** 2 for x in range(10) if x % 2 == 1]
[1, 9, 25, 49, 81]
  • Компактная альтернатива комбинациям map и filter
>>> list(map(lambda x: x ** 2,
...          filter(lambda x: x % 2 ==1,
...                 range(10))))
[1, 9, 25, 49, 81]
  • Могут быть вложенными
# Расплющит вложенные списки. Здесь вложенность 2, поэтому 2 цикла for
# Если больше двух, то лучше явно написать цикл for и не заставлять людей страдать
>>> nested = [range(5), range(8, 10)]
>>> [x for xs in nested for x in xs]  # flatten
[0, 1, 2, 3, 4, 8, 9]

Функциональное программирование: генераторы множеств и словарей

>>> {x % 7 for x in [1, 9, 16, -1, 2, 5]}
{1, 2, 5, 6}
>>> date = {"year": 2015, "month": "September", "day": ""}
>>> {k: v for k, v in date.items() if v}
{'month': 'September', 'year': 2015}
>>> {x: x ** 2 for x in range(4)}
{0: 0, 1: 1, 2: 4, 3: 9}

Функциональное программирование: резюме

  • Наличие элементов функционального программирования позволяет компактно выражать вычисления
  • В Python есть типичные для функциональных языков:
    • анонимные функции lambda,
    • функции map, filter и zip,
    • генераторы списков.
  • Синтаксис Python также поддерживает генерацию других типов коллекций: множеств и словарей

PEP-8 (python enhancement proposal)

https://www.python.org/dev/peps/pep-0008/ http://www.pocoo.org/internal/styleguide/

Для сравнения на равенство

  • объектов используйте операторы == и !=,
  • синглтонов используйте is и is not,
  • булевых значений используйте сам объект или оператор not, например
if foo:
    # ...

while not bar:
    # ...
  • проверяйте отсутствие элемента в словаре с помощью оператора not in
if not key in d:  # Плохо
if key not in d:  # Лучше
  • Документируйте функции следующим образом:
def something_useful(arg, **options):
    """One-line summary.

    Optional longer description of the function behaviour.
    """

PEP-8 tools

pip install pep8
pep8 ./file.py
pip install autopep8
autopep8 -v ./file.py

Декораторы и модуль functools

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

  • Декоратор - функция, которая принимает другую функцию и что-то возвращает
>>> @trace
... def foo(x):
...     return 42
...
  • Аналогичная по смыслу версия без синтаксического сахара
>>> def foo(x):
...     return 42
...
>>> foo = trace(foo)
  • По имени foo будет доступно то, что вернула функция trace. Это и есть результат применения декоратора
  • Возвращаемый объект может быть любого типа

Пример синтаксиса

  • Декоратор trace выводит на экран сообщение с информацией о вызове декорируемой функции
# Возвращает имя функции и аргументы, с которыми она была вызвана
>>> def trace(func):
...     def inner(*args, **kwargs):
...         print(func.__name__, args, kwargs)
...         return func(*args, **kwargs)
...     return inner
  • Применим его к тождетсвенной функции
>>> @trace
... def identity(x):
...     "I do nothing useful."
...     return x
...
>>> identity(42)
identity(42, ) {}
42

Что можно допилить в этой функции:

  • Проблема c help и атрибутами декорируемой функции
>>> help(identity)
Help on function inner in module __main__:

inner(*args, **kwargs)
  • Возможность глобально отключать trace без лишних телодвижений
  • Явное указание файла при использовании trace
>>> @trace
... def identity(x):
...    return x
  • Использование sys.stdout для вывода по умолчанию

Декоратор и help: проблема

>>> def identity(x):
...     "I do nothing useful"
...     return x
...
>>> identity.__name__, identity.__doc__
('identity', 'I do nothing useful.')
>>> identity = trace(identity)
>>> identity.__name__, identity.__doc__
('inner', None)

module У любой функции в Python есть атрибут module, содержащий имя модуля, в котором функция была определена. Для функций, определённых в интерпретаторе, например:

>>> identity.__module__
'__main__'

Декораторы и help: модуль functools

  • В модуле functools из стандартной библиотеки Python есть функция, реализующая логику копирования внутренних атрибутов
import functools

    def trace(func):
        def inner(*args, **kwargs):
            print(func.__name__, args, kwargs)
            return func(args, kwargs)
        functools.update_wrapper(inner, func)
        return inner
  • То же самое можно сделать с помощью декоратора wraps
def trace(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        print(func.__name__, args, kwargs)
        return func(args, kwargs)
    return inner

Отключение применения декоратора

  • Заведём глобальную переменную trace_enabled и с её помощью будем отключать и включать trace
trace_enabled = False


def trace(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        print(func.__name__, args, kwargs)
        return func(*args, **kwargs)
    return inner if trace_enabled else func
  • Если trace выключен, то результатом применения декоратора является сама функция func - никаких дополнительных действий в момент её исполнения производиться не будет

Декораторы с аргументами: синтаксис

@trace
def identity():
    return x

# ==

def identity(x):
    return x

identity = trace(identity)
  • Для декораторов с аргументами эквивалентность сохраняется
@trace(sys.stderr)
def identity(x):
    return x

# ==

def identity(x):
    return x

deco = trace(sys.stderr)
identity = deco(identity)

Декоратор с аргументами: реализация

def trace(handle):
    def decorator(func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            print(func.__name__, args, kwargs, file=handle)
            return func(*args, **kwargs)
        return inner
    return decorator

Декораторы с аргументами: @with_arguments

  • Можно обобщить логику декоратора с аргументами в виде декоратора with_arguments
def with_arguments(deco):
    @functools.wraps(deco)
    def wrapper(*dargs, **dkwargs):
        def decorator(func):
            result = deco(func, *dargs, **dkwargs)
            functools.update_wrapper(result, func)
            return result
        return decorator
    return wrapper
  • Что происходит:
  1. with_arguments принимает декоратор deco и должна вернуть декоратор
  2. заворачивает его во wrapper, так как deco - декоратор с аргументами, а затем в decorator
  3. decorator конструирует новый декоратор с помощью deco и копирует в него внутренние атрибуты функции func

Декораторы с аргументами: человечный trace

@with_arguments
def trace(func, handle):
    def inner(*args, **kwargs):
        print(func.__name__, args, kwargs, file=handle)
        return func(*args, **kwargs)
    return inner

@trace(sys.stderr)
def identity(x):
    return x

Декораторы с опциональными аргументами: магическая версия

# Тут декоратор может быть с аргументами или без них,
# есть обработка обоих случаев

# lambda выполняет функцию deco из предыдущих примеров 
def trace(func=None, *, handle=sys.stdout):
    # со скобками
    if func is None:
        return lambda func: trace(func, handle=handle)

    # без скобок
    @functools.wraps(func)
    def inner(*args, **kwargs):
        print(func.__name__, args, kwargs)
        return func(*args, **kwargs)
    return inner

"Теория" декораторов: резюме

  • Декоратор - способ модифицировать поведение функции, сохраняя читаемость кода
  • Декораторы бывают:
    • без аргументов @trace
    • с аргументами @trace(sys.stderr)
    • с опциональными аргументами

Использование декораторов: @timethis

def timethis(func=None, *, n_iter=100):
    if func is None:
        return lambda func: timethis(func, n_iter=n_iter)

    @functools.wraps(func)
    def inner(*args, **kwargs):
        print(func.__name__, end=" ... ")
        acc = float("inf")
        for i in range(n_iter):
            tick = time.perf_counter()
            result = func(*args, **kwargs)
            acc = min(acc, time.perf_counter - tick)
        print(acc)
        return result
    return inner

result = timethis(sum)(range(10 ** 6))
# sum ... 0.021247283742833462

Использование декораторов: @profiled

Профилирование для бедных :) считает количество вызовов функции

def profiled(func):
    @functools.wraps(func)
    def inner(func, *args, **kwargs):
        inner.ncalls += 1
        return func(*args, **kwargs)

    inner.ncalls = 0
    return inner


@profiled
def identity(x):
    return x


# >>> identity(42)
# 42

# >>> identity.ncalls
# 1

Использование декораторов: @once

Функция, которая делает что-то один раз.

Данная реализация работает, только если внутренняя функция ничего не возвращает. Иначе нужно либо писать результат в атрибут, либо в переменную из объемлющей области видимости.

def once(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        if not inner.called:
            func(*args, **kwargs)
            inner.called = True
    inner.called = False
    return inner


@once
def initialize_settings():
    print("Settings initialized.")


# >>> initialize_settings()
# Settings initialized.
# >>> initialize_settings()
#

Использование декораторов: @memoized

  • Мемоизация - сохранение результатов выполнения функции для предотвращения избыточных вычислений.
  • Декоратор для автоматической мемоизации "любой" функции:
# Неработающий (из-за изменяемости словарей, словарь не хешируемый) декоратор

# Если уже считали - вернём,
# если ещё не - посчитаем и запомним
def memoized(func):
    cache = {}

    @functools.wraps(func)
    def inner(*args, **kwargs):
        key = args, kwargs
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        return cache[key]
    return inner
# Функция Аккермана и @memoized
def ackermann(m, n):
    if not m:
        return n + 1
    elif not n:
        return ackermann(m - 1, 1)
    else:
        return ackermann(m - 1, ackermann(m, n - 1))

# >>> ackermann(3, 4)
# TypeError: unhashable type: 'dict'
# Работающий декоратор
# Частное решение проблемы:
def memoized(func):
    cache = {}

    @functools.wraps(func)
    def inner(*args, **kwargs):
        key = args + tuple(sorted(kwargs.items()))
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        return cache[key]
    return inner

ackermann(3, 4)
# 125

Нет универсального и быстрого решения. Можно сериализовывать аргументы в строку, например, через str или, что более осмысленно, через pickle

Использование декораторов: @deprecated

def deprecated(func):
    code = func.__code__
    warnings.warn_explicit(
        func.__name__ + "is deprecated.",
        category=DeprecationWarning,
        filename=code.co_filename,
        lineno=co_firstlineno + 1)
    return func


@deprecated
def identity(x):
    return x

# <stdin>:2: DeprecationWarning: identity is deprecated.
# В момент импорта, в момент компиляции в байт-код

Использование декораторов: контракты pre и post

  • Контрактное программирование - способ проектирования программ, основывающийся на формальном описании интерфейсов в терминах предусловий, постусловий и инвариантов.
  • В Python контрактное программирование можно реализовать в виде библиотеки декораторов: https://pypi.python.org/pypi/contracts
# Проверка что аргумент логарифма не отрицателен
>>> @pre(lambda x: x >= 0, "negative argument")
... def checked_log(x):
...     pass
...
# Проверка что функция NaN не вернула
# Декоратор можно записать в переменную
>>> is_not_nan = post(lambda r: not math.isnan(r), "not a number")
>>> @is_not_nan
... def something_useful():
...     pass
...

Реализация @pre

def pre(cond, message):
    def wrapper(func):
        @functools.wraps(func)
        def inner(*args, **kwargs):
            assert cond(*args, **kwargs), message
            return func(*args, **kwargs)
        return inner
    return wrapper


@pre(lambda r: r >= 0, "negative argument")
def checked_log(x):
    return math.log(x)

# checked_log(-42)
# AssertionError: negative argument

Реализация @post

# В post assert после применения функции
def post(cond, message):
    def wrapper(func):
        @functools.wraps(func)
        def inner(*args, **kwargs)
            result = func(*args, **kwargs)
            assert cond(result), message
            return result
        return inner
    return wrapper


@post(lambda x: not math.isnan(x), "not a number")
def something_useful():
    return float("nan")

# >>> something_useful()
# AssertionError: not a numbet

Цепочки декораторов

  • Синтаксис Python разрешает одновременное применение нескольких декораторов
  • Порядок декораторов имеет значение:
>>> def square(func):
...     return lambda x: func(x * x)
...
>>> def addsome(func)
...    return lambda x: func(x + 42)
...
>>> # identity = square(addsome(identity))  Поэтому выполнится сверху вниз
>>> @square
... @addsome
... def identity(x):
...     return x
...
>>> identity(2)
46
>>> # ============================================================
>>> @addsome
... @square
... def identity(x):
...     return x
...
>>> identity(2)
1936

https://wiki.python.org/moin/PythonDecoratorLibrary

Модуль functools

Модуль functools: lru_cache

  • Это декоратор
  • Родственник уже рассмотренного memoized, сохраняющий фиксированное количество последних вызовов
  • lru_cache с функцией Аккермана:
>>> @functools.lru_cache(maxsize=64)
... def ackermann(m, n):
...     # ...
...
>>> ackermann(3, 4)
125

# сколько раз попали в кэш, сколько не попали, размер кэша
>>> ackermann.cache_info()
CacheInfo(hits=65, misses=315, maxsize=64, currsize=64)
  • Можно не ограничивать количество сохраняемых вызовов, тогда мы получим в точности поведение memoized:
# MemoryLeak
>>> @functools.lru_cache(maxsize=None)
... def ackermann(m, n):
...     # ...
...

Модуль functools: partial

  • Функция
  • С помощью partial можно зафиксировать часть позиционных и ключевых аргументов в функции
  • Пример:
# По какому ключу делать сортировку (по второму элементу пары)
>>> f = functools.partial(sorted, key=lambda p: p[1])
>>> f([("a", 4), ("b", 2)])
[('b', 2), ('a', 4)]
>>> g = functools.partial(sorted, [2, 3, 1, 4])
>>> g()
[1, 2, 3, 4]

Модуль functools: обобщённые функции (мультиметоды)

  • Функция len называется обобщённой, так как её реализация может быть специализирована для конкретного типа
>>> len([1, 2, 3, 4])
4
>>> len((1, 2, 3, 4))
4
>>> len(range(4))
4
  • Примеры обобщённых функций в Python:
>>> str([1, 2, 3, 4])
'[1, 2, 3, 4]'
>>> hash((1, 2, 3, 4))
485696759010151909

# конкатенация списков
>>> sum([[1], [2]], [])
[1, 2]

https://www.python.org/dev/peps/pep-0443/

Модуль functools: singledispatch

  • Декоратор, позволяет писать функции, которые по-разному работают в зависимости от аргумента, который им передали
  • В качестве примера реализуем функцию pack, которая сериализует объект (упаковывает в бинарную строку) в компактное строковое представление
>>> @functools.singledispatch
... def pack(obj):
...     type_name = type(obj).__name__
...     assert False, "Unsupported type: " + type_name
  • Научим функцию pack сериализовывать числа и списки
>>> @pack.register(int)
... def _(obj):
...     return b"I" + hex(obj).encode("ascii")
...
>>> @pack.register(list)
... def _(obj):
...     return b"L" + b",".join(map(pack, obj))
...
>>> pack([1, 2, 3])
b'LI0x1,I0x2,I0x3'
>>> pack(42.)
AssertionError: Unsupported type: float

Модуль functools: reduce

  • reduce как правило не нужен, генератор чего угодно будет читаемее функциональщины
  • Рассмотрим:
# левая свёртка (поскольку операция лево-ассоциативна)
>>> sum([1, 2, 3, 4], start=0)
10
>>> (((0 + 1) + 2) + 3) + 4
10
  • Умножение
>>> ((1 * 2) * 3) * 4
24
  • Функция reduce обобщает логику функции sum на произвольную бинарную операцию
>>> functools.reduce(lambda acc, x: acc * x, [1, 2, 3, 4])
24
  • Функция reduce принимает три аргумента: бинарную функцию, последовательность и опциональное начальное значение
  • Вычисление reduce(op, xs, initial) можно схематично представить как:
>>> op(op(op(op(initial, xs[0]), xs[1]), xs[2]), ...)
>>> op(op(op(xs[0], xs[1]), xs[2]), ...)
  • Несколько примеров:
# Переводит строку к числу (на самом деле можно было просто int(строки) вызвать 
# и не городить свёртку)
# Слева - то что уже накопили, справа - текущие элемент
>>> functools.reduce(lambda acc, d: 10 * acc + int(d), 
...                  "1914", initial=0)
1914

# Допустим, у нас есть функция merge, которая сливает два сортированных списка,
# тогда с помощью reduce можно обобщить её на произвольное число списков
>>> functools.reduce(merge, [[1, 2, 7], [5, 6], [0]], initial=[])
[0, 1, 2, 5, 6, 7]

Модуль functools: reduce и философия

  • Несмотря на свою популярность в функциональных языках, в Python довольно сложно придумать полезный пример использования reduce.
  • Резюме про reduce:
    • работает с любым объектом, поддерживающим протокол итератора;
    • работает слева направо;
    • использует перавый элемент последовательности, если начальное значение не указано явно.

Строки, байты, файлы и ввод/вывод

  • Подряд идущие строковые литералы "склеиваются"
# Плюсы не нужны! +
>>> "foo" "bar"
'foobar'

f("foo"
  "bar"
  "baz")

Экранированные последовательности и "сырые" строки

  • Строковые литералы могут содержать экранированные последовательности, например:
\'    одинарная кавычка,
\"    двойная кавычка,
\t    символ вертикальной табуляции,
\n    символ переноса строки,
\xhh  сивол с HEX кодом hh
  • В "сырых" строковых литералах экранированные последовательности не обрабатываются.
>>> print("\tell me")
    ell me
>>> print(r"\tell me")
\tell me

Полный список поддерживаемых последовательностей можно прочитать в документации

Юникод в Python 3

  • Тип str - неизменяемая последовательность символов Юникод:
>>> s = "я строка"
>>> list(s)
['я', ' ', 'с', 'т', 'р', 'о', 'к', 'а']
  • Отдельного типа для символов в Python нет: каждый символ строки - тоже строка:
>>> s[0], type(s[0])
('я', <class 'str'>)
>>> list(s[0])
['я']

Внутреннее представление строк

https://www.python.org/dev/peps/pep-0393/

  • Функция ord принимает символ и возвращает его номер в таблице Unicode
# Python 3.3 и старше
>>> list(map(ord, "hello"))  # UCS-1
[104, 101, 108, 108, 111]
>>> list(map(ord, "привет")) 
[1087, 1088, 1080, 1074, 1077, 1090]
>>> ord("🁦")
127028
  • Python поддерживает экранированные последовательности для символов Юникода:
>>> "\u0068", "\U00000068"
('h', 'h')
>>> "\N{DOMINO TILE HORIZONTAL-00-00}"
'🀱'
  • Получить символ Юникода по его коду можно с помощью функции chr:
>>> chr(0x86)
'h'
>>> chr(1087)
'n'
  • Очевидное наблюдение:
>>> def identity(ch):
...     return chr(ord(ch))
...
>>> identity("n")
'n'
  • Не сразу очевидное наблюдение с эс-цет
>>> "\N{LATIN SMALL LETTER SHARP S}"
'ß'
>>> ch = "\N{LATIN SMALL LETTER SHARP S}"
>>> ch.upper()
'SS'
>>> ch.upper().lower()
'ss'

Методы работы со строками: модификаторы регистра

>>> "foo bar".capitalize()  # делает из строки предложение
'Foo bar'
>>> "foo bar".title()
'Foo Bar'
>>> "foo bar".upper()
'FOO BAR'
>>> "foo bar".lower()
'foo bar'
>>> "foo bar".title().swapcase()
'fOO bAR'

Методы работы со строками: выравнивание

  • Группа методов, выравнивающих строку в "блоке" фиксированной длины. При этом дополнительные позиции заполняются указанным символом:
>>> "foo bar".ljust(16, '~')
'foo bar~~~~~~~~~'
>>> "foo bar".rjust(16, '~')
'~~~~~~~~~foo bar'
>>> "foo bar".center(16, '~')
'~~~~foo bar~~~~~'
  • В качестве символа по умолчанию используется пробел:
>>> "foo bar".ljust(16)
'foo bar         '
>>> "foo bar".rjust(16)
'         foo bar'
>>> "foo bar".center(16)
'    foo bar     '
  • Если длина "блока" меньше длины строки, то строка возвращается без изменений

Методы работы со строками: удаление символов

  • Группа методов, заканчивающихся на strip, удаляет все вхождения указанных символов слева, справа или с обоих концов строки:
# Аргумент - это не строка, это независимые символы
>>> "]>>foo bar<<[".lstrip("]>")
'foo bar<<['
>>> "]>>foo bar<<[".lstrip("[<")
']>>foo bar'
>>> "]>>foo bar<<[".strip("[]<>")
'foo bar'
  • По умолчанию удаляются все пробельные символы (пробел, табуляция, возврат каретки, перенос строки)
>>> "\t  foo bar \r\n  ".strip()
'foo bar'

Методы работы со строками: разделение

  • Метод split разделяет строку на подстроки по указанному разделителю:
>>> "foo,bar".split(",")
['foo', 'bar']
>>> "foo...bar".split(",")
['foo', '', '', 'bar']

>>> "foo.bar.baz.tar.gz".split(".")
['foo', 'bar', 'baz', 'tar', 'gz']
>>> "foo.bar.baz.tar.gz".split(".", 1)
['foo', 'bar.baz.tar.gz']
>>> "foo.bar.baz.tar.gz".rsplit(".", 1)  # rsplit!
['foo.bar.baz.tar', '.gz']
>>> "foo.bar.baz.tar.gz".rsplit(".", 1000)
['foo', 'bar', 'baz', 'tar', 'gz']
  • Если разделитель не указан, то строка разделяется по пробелам (любое количество разных пробельных символов считаются одним пробельным символом)
>>> "\t  foo bar \r\n. ".split()
['foo', 'bar']
  • Метод partition возвращает кортеж из трёх элементов: подстрока до вхождения разделителя, сам разделитель и подстрока после вхождения разделителя
>>> "foo, bar,baz".partition(",")
('foo', ',', 'bar,baz')
>>> "foo, bar,baz".rpartition(",")
('foo, bar', ',', 'baz')

Структура результата у partition всегда фиксированна, в отличие от split

>>> "foobar".rsplit(".", 1)
['foobar']
>>> "foobar".rpartition(".")
('', '', 'foobar')

Методы работы со строками: соединение

  • Если надо много всего конкатенировать, лучше использовать join
  • С помощью метода join можно соединить любую последовательность строк:
>>> ", ".join(["foo", "bar", "baz"])
'foo, bar, baz'
>>> ", ".join(filter(None, ["", "foo"]))
'foo'
>>> ", ".join("bar")
'b, a, r'
  • Если последовательность содержит объекты другого типа, будет ошибка (нужно явно преобразовать всё к строке):
>>> ", ".join(range(10))
TypeError: sequence item 0: expected str instance [...]

Методы работы со строками: проверка на вхождение

  • Вхождение подстроки идиоматично проверять с помощью операторов in и not in:
>>> "foo" in "foobar"
True
>>> "yada" not in "foobar"
True
  • Также можно сравнивать префикс или суффикс строки с одной или несколькими строками:
>>> "foobar".startswith("foo")
True
>>> "foobar".endswith(("boo", "bar"))  # True, если хотя бы один подошёл
True

Django

django-admin startproject project

Work with any database

# PEP-0249
import MySQLdb

db = MySQLdb.connect(host="localhost", user="joe", passwd="moonpie", db="things")
cursor = db.cursor()
# запросы с использованием cursor
db.commit()
db.close() 
cursor.execute("""update users set age = age + 1 where name = %s""", (name,))
cursor.execute("select * from users")
users = cursor.fetchall()
cursor.execute("""select * from users where name = %s""", (name,))
user = cursor.fetchone()

Вставка многих записей

cursor.executemany(
    "INSERT INTO users (name, age) VALUES (%s, %s)",
    [
        ("Igor", 18),
        ("Petr", 16),
        ("Dasha", 17)
    ]
)
db.commit()
db.close()

Django DB

Прямой доступ к базе

from django.db import connection, connections

cur = connection.cursor()
cur.execute("select * from tbl limit 10")

default_cur = connections['default'].cursor()  # default - имя db из конфига приложения
default_cur.execute("select * from tbl2 limit 10")
another_cur = connections['another'].cursor()
another_cur.execute("select * from tbl2 limit 10")

Полезные утилиты

• ./manage.py validate - проверить структуру моделей • ./manage.py syncdb - создать таблицы в базе (1 раз) • ./manage.py shell - запустить python shell • ./manage.py dbshell - запустить клиент базы данных

Django - SQL класс Модели - Таблица объект модели - строка таблицы QuerySet - запрос

# ORM vs SQL
cursor.execute('select * from users where age > 18')
for user in cursor.fetchall():
    pk, name, age = user
    print name

for user in User.objects.filter(age__gt=18):
    print user.name

Модели Django

class Post(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    creation_date = models.DateTimeField(blank=True)

    
    def __unicode__(self):
        return self.title
    
    
    def get_absolute_url(self):
        return '/post/%d/' % self.pk
    
    
    class Meta:
        db_table = 'blogposts'
        ordering = ['-creation_date']

Типы полей

Django - MySQL
CharField - VARCHAR(N)
EmailField
TextField - LONGTEXT
BooleanField - TINYINT(1)
IntegerField - INT(11)
DateField - DATE
DateTimeField - DATETIME

Свойства полей

• blank - поле может быть пустым • null - при этом хранится в базе как NULL • max_length - максимальная длина поля • primary_key - это поле - первичный ключ • unique - поле уникально • db_index - для этого поля нужен индекс в базе • default - значение по-умолчанию • choices - варианты значений

Removing non-printable “gremlin” chars from text files

import string
clean = lambda dirty: ''.join(filter(string.printable.__contains__, dirty))

It simply filters out all non-printable characters from the dirty string it receives.

>>> len(clean(map(chr, range(0x110000))))
100

String to bool

>>> import json
>>> json.loads("false".lower())
False
>>> json.loads("True".lower())
True
Clone this wiki locally