company_banner

Подборка @pythonetc, июнь 2019


    Это одиннадцатая подборка советов про Python и программирование из моего авторского канала @pythonetc.

    Предыдущие подборки


    Символ \ в обычной строке имеет особое значение. \t — это символ табуляции, \r — разрыв строки, и так далее.

    Чтобы отключить такое поведение, вы можете использовать raw-строки. Тогда r'\t' превратится всего лишь в обратный слэш и t.

    Очевидно, что нельзя использовать ' внутри r'...'. И хотя это ограничение можно обойти с помощью \, однако в строке \ всё равно останется:

    >>> print(r'It\'s insane!')
    It\'s insane!


    Генераторы списков могут содержать больше одной пары выражений for и if:

    In : [(x, y) for x in range(3) for y in range(3)]
    Out: [
        (0, 0), (0, 1), (0, 2),
        (1, 0), (1, 1), (1, 2),
        (2, 0), (2, 1), (2, 2)
    ]
    In : [
        (x, y)
        for x in range(3)
        for y in range(3)
        if x != 0
        if y != 0
    ]
    Out: [(1, 1), (1, 2), (2, 1), (2, 2)]

    Кроме того, любое выражение внутри for и if может использовать все ранее определённые переменные:

    In : [
        (x, y)
        for x in range(3)
        for y in range(x + 2)
        if x != y
    ]
    Out: [
        (0, 1),
        (1, 0), (1, 2),
        (2, 0), (2, 1), (2, 3)
    ]

    Вы можете смешивать if и for по своему усмотрению:

    In : [
        (x, y)
        for x in range(5)
        if x % 2
        for y in range(x + 2)
        if x != y
    ]
    Out: [
        (1, 0), (1, 2),
        (3, 0), (3, 1), (3, 2), (3, 4)
    ]


    Функция sorted позволяет задавать пользовательские способы сортировки. Это делается с помощью аргумента key, который описывает, как нужно преобразовать исходные значения для последующего сравнения:

    >>> x = [dict(name='Vadim', age=29), dict(name='Alex', age=4)]
    >>> sorted(x, key=lambda v: v['age'])
    [{'age': 4, 'name': 'Alex'}, {'age': 29, 'name': 'Vadim'}]

    Увы, не все библиотеки, работающие со сравнением, поддерживают аргумент key. Из тех, что на слуху, можно упомянуть heapq (частичная поддержка) и bisect (нет поддержки).

    В этой ситуации можно пойти двумя путями. Можно использовать пользовательские объекты, которые поддерживают правильное сравнение:

    >>> class User:
    ...     def __init__(self, name, age):
    ...         self.name = name
    ...         self.age = age
    ...     def __lt__(self, other):
    ...         return self.age < other.age
    ...
    >>> x = [User('Vadim', 29), User('Alex', 4)]
    >>> [x.name for x in sorted(x)]
    ['Alex', 'Vadim']

    Однако, вам может понадобиться создать несколько версий подобных классов, потому что сравнивать объекты можно по-разному. Это может быть неудобно, поэтому есть и второй способ.

    Вместо создания пользовательских объектов вы можете использовать кортежи (a, b), в которых a — значение для сравнения (приоритет), а b — исходное значение:

    >>> users = [dict(name='Vadim', age=29), dict(name='Alex', age=4)]
    >>> to_sort = [(u['age'], u) for u in users]
    >>> [x[1]['name'] for x in sorted(to_sort)]
    ['Alex', 'Vadim']


    Разница между определением и генератором функции заключается в наличии ключевого слова yield в теле функции:

    In : def f():
    ...:     pass
    ...:
    
    In : def g():
    ...:     yield
    ...:
    
    In : type(f())
    Out: NoneType
    
    In : type(g())
    Out: generator

    Это означает, что для создания пустого генератора вам нужно сделать так:

    In : def g():
    ...:     if False:
    ...:             yield
    ...:
    
    In : list(g())
    Out: []

    Но поскольку yield from поддерживает простые итераторы, то есть более приятная версия:

    def g():
        yield from []


    В Python можно создавать цепочки операторов сравнения:

    >>> 0 < 1 < 2
    True
    >>> 0 < 1 < 0
    False

    Такие цепочки не обязаны быть математически корректными, вы можете смешивать > и <:

    >>> 0 < 1 > 2
    False
    >>> 0 < 1 < 2 > 1 > 0
    True

    Также поддерживаются операторы ==. is и in:

    >>> [] is not 3 in [1, 2, 3]
    True

    Каждый оператор применяется к двум соседним операндам. a OP1 b OP2 c строго эквивалентно (a OP1 b) AND (b OP2 c). Сравнение a и c не выполняется:

    class Spy:
        def __init__(self, x):
                self.x = x
    
        def __eq__(self, other):
                print(f'{self.x} == {other.x}')
                return self.x == other.x
    
        def __ne__(self, other):
                print(f'{self.x} != {other.x}')
                return self.x != other.x
    
        def __lt__(self, other):
                print(f'{self.x} < {other.x}')
                return self.x < other.x
    
        def __le__(self, other):
                print(f'{self.x} <= {other.x}')
                return self.x <= other.x
    
        def __gt__(self, other):
                print(f'{self.x} > {other.x}')
                return self.x > other.x
    
        def __ge__(self, other):
                print(f'{self.x} >= {other.x}')
                return self.x >= other.x
    
    
    s1 = Spy(1)
    s2 = Spy(2)
    s3 = Spy(3)
    
    print(s1 is s1 < s2 <= s3 == s3)

    Результат:

    1 < 2
    2 <= 3
    3 == 3
    True
    
    Mail.ru Group
    1 021,33
    Строим Интернет
    Поделиться публикацией

    Похожие публикации

    Комментарии 4

      0
      Вместо создания пользовательских объектов вы можете использовать кортежи (a, b), в которых a — значение для сравнения (приоритет), а b — исходное значение

      Это не будет работать, если значения a совпадают. При равенстве a, сравниваться будут значения b, которые могут не поддерживать сравнения. Ещё и могут возникнуть чудеса:


      sorted([(1, {}), (1, {}), (-1, {'x': 0})])
      sorted([(1, {'x': 0}), (1, {})]) // TypeError

      [x[1]['name'] for x in sorted(to_sort)]

      Лучше выглядит так [x['name'] for _, x in sorted(to_sort)], не правда? :)


      В этой ситуации можно пойти двумя путями. Можно использовать пользовательские объекты, которые поддерживают правильное сравнение

      В прошлой подборке был совет использовать в этом случае functools.total_ordering. Если это неудобно, то возможно стоит сделать обёртку над коллекцией, поддерживающую сравнение по ключу.

        0

        Добавлю: кроме r'...' можно использовать R'...', во многих IDE подсветка для r идёт как regexp, а для R как для обычной строки.

          0
          во многих IDE подсветка для r идёт как regexp

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

          0
          a OP1 b OP2 c строго эквивалентно (a OP1 b) AND (b OP2 c)


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

          >>> def f(x):
          ...     print(x)
          ...     return x
          ... 
          >>> f(1) < f(2) == f(3) < f(4)
          1
          2
          3
          False
          

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

          Самое читаемое