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

    defcall(*args, **kwargs):defdecorator(func):return func(*args, **kwargs)
        return decorator
    @call(15)defsqr_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
    classBinaryOperation(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
            returnf'{klass}({left}, {right})'    @abstractmethoddefdo(self):pass    @classmethoddefmake(cls, do_function):return type(
                do_function.__name__,
                (BinaryOperation,),
                dict(do=do_function),
            )
    classAddition(BinaryOperation):defdo(self):return self._left + self._right
    @BinaryOperation.makedefSubtraction(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.

    >>> defg():...     print(1)
    ... yield1...     print(2)
    ... yield2...     print(3)
    ... yield3
    ...
    >>> 2in g()
    12True

    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**20in 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.

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

    >>> classA:...     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:

    >>> classFunctionHolder:... def__init__(self, f):...         self._f = f
    ... def__get__(self, obj, objtype):... return self._f
    ...
    >>> classA:...     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.

    classA:    @classmethoddef_get_callback(cls):returnlambda x: x ** x

    Also popular now: