company_banner

Подборка @pythonetc, ноябрь 2018


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

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



    Нетипичные декораторы


    Декораторы функций не обязаны возвращать только новые функции, они могут возвращать любое другое значение:

    def call(*args, **kwargs):
        def decorator(func):
            return func(*args, **kwargs)
     
        return decorator
     
    @call(15)
    def sqr_15(x):
        return x * x
     
    assert sqr_15 == 225

    Это бывает полезно для создания простых классов всего лишь с одним переопределяемым методом:

    from abc import ABCMeta, abstractmethod
     
    class BinaryOperation(metaclass=ABCMeta):
        def __init__(self, left, right):
            self._left = left
            self._right = right
     
        def __repr__(self):
            klass = type(self).__name__
            left = self._left
            right = self._right
            return f'{klass}({left}, {right})'
     
        @abstractmethod
        def do(self):
            pass
     
        @classmethod
        def make(cls, do_function):
            return type(
                do_function.__name__,
                (BinaryOperation,),
                dict(do=do_function),
            )
     
    class Addition(BinaryOperation):
        def do(self):
            return self._left + self._right
     
    @BinaryOperation.make
    def Subtraction(self):
        return self._left - self._right


    __length_hint__


    PEP 424 позволяет генераторам и прочим итерируемым объектам, у которых нет конкретного заранее определённого размера, возвращает свою примерную длину. Например, этот генератор наверняка вернёт около 50 элементов:

    (x for x in range(100) if random() > 0.5)

    Если вы пишете что-то итерируемое и хотите возвращать примерную длину, то определяйте метод __length_hint__. А если вам точно известна длина, то используйте __len__. Если же используете итерируемый объект и хотите знать, какой он может быть длины, используйте operator.length_hint.

    in с генератором


    Оператор in можно использовать с генераторами: x in g. В этом случае Python будет итерироваться по g, пока не найдётся x или пока не закончится g.

    >>> def g():
    ...     print(1)
    ...     yield 1
    ...     print(2)
    ...     yield 2
    ...     print(3)
    ...     yield 3
    ...
    >>> 2 in g()
    1
    2
    True

    range(), однако, работает несколько лучше. У него есть волшебный переопределённый метод __contains__, благодаря которому вычислительная сложность in становится равна O(1):

    In [1]: %timeit 10**20 in range(10**30)
    375 ns ± 10.7 ns per loop

    Обратите внимание, что с функцией xrange() из Python 2 это работать не будет.

    Операторы += и +


    В Python есть два разных оператора: += и +. За их поведение отвечают методы __iadd__ и __add__ соответственно.

    class A:
        def __init__(self, x):
            self.x = x
     
        def __iadd__(self, another):
            self.x += another.x
            return self
     
        def __add__(self, another):
            return type(self)(self.x + another.x)

    Если __iadd__ не определён, то a += b будет работать как a = a + b.

    Семантическая разница между += и + заключается в том, что первый изменяет объект, а второй — создаёт новый:

    >>> a = [1, 2, 3]
    >>> b = a
    >>> a += [4]
    >>> a
    [1, 2, 3, 4]
    >>> b
    [1, 2, 3, 4]
    >>> a = a + [5]
    >>> a
    [1, 2, 3, 4, 5]
    >>> b
    [1, 2, 3, 4]

    Функция как атрибут класса


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

    >>> class A:
    ...     CALLBACK = lambda x: x ** x
    ...
    >>> A.CALLBACK
    <function A.<lambda> at 0x7f68b01ab6a8>
    >>> A().CALLBACK
    <bound method A.<lambda> of <__main__.A object at 0x7f68b01aea20>>
    >>> A().CALLBACK(4)
    
    
    Traceback (most recent call last):
    
      File "<stdin>", line 1, in <module>
     
    TypeError: <lambda>() takes 1 positional argument but 2 were given

    Можно схитрить и обернуть функцию в тривиальный дескриптор:

    >>> class FunctionHolder:
    ...     def __init__(self, f):
    ...         self._f = f
    ...     def __get__(self, obj, objtype):
    ...         return self._f
    ...
    >>> class A:
    ...     CALLBACK = FunctionHolder(lambda x: x ** x)
    ...
    >>> A().CALLBACK
    <function A.<lambda> at 0x7f68b01ab950>

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

    class A:
        @classmethod
        def _get_callback(cls):
            return lambda x: x ** x
    • +28
    • 6,3k
    • 1
    Mail.ru Group
    1834,00
    Строим Интернет
    Поделиться публикацией

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

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

      +3
      Функция как атрибут класса

      Зачем?

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

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