@Pythonetc November 2018 Compilation


    This is the sixth collection of tips on Python and programming from my author’s @pythonetc channel.

    Previous selections:



    Atypical decorators


    Function decorators are not required to return only new functions, they can return any other value:

    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

    This is useful for creating simple classes with only one override method:

    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__


    The PEP 424 allows generators and other iterable objects that do not have a specific predetermined size to return their approximate length. For example, this generator will certainly return about 50 items:

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

    If you are writing something iterable and want to return an approximate length, then define a method __length_hint__. And if you exactly know the length, then use __len__. If you are using an iterated object and want to know how long it can be, use operator.length_hint.

    in with generator


    The operator incan be used with generators: x in g. In this case, Python will be iterated on guntil it is found xor until it ends g.

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

    range()however, it works somewhat better. It has a magic redefined method __contains__, due to which the computational complexity inbecomes equal to O (1):

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

    Please note that xrange()this will not work with the function from Python 2.

    Operators + = and +


    In Python, there are two different operators: +=and +. Methods are responsible for their behavior __iadd__and, __add__accordingly.

    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)

    If __iadd__not defined, it a += bwill work as a = a + b.

    The semantic difference between +=and +lies in the fact that the first changes the object, and the second creates a new one:

    >>> 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]

    Function as an attribute of a class


    You cannot store a function as a class attribute, because it will be automatically converted to a method if it is accessed through an instance:

    >>> 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

    You can cheat and wrap the function in a trivial descriptor:

    >>> 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>

    You can also get out of the situation by using the class method instead of an attribute.

    class A:
        @classmethod
        def _get_callback(cls):
            return lambda x: x ** x

    Also popular now: