## Python Advanced Topics

A list of Python Advanced Topics:

**Python Generators and Iterator Protocol****Python Meta-programming****Python Descriptors****Python Decorators (class and method based)****Python Buffering Protocol****Python Comprehensions****Python GIL and multiprocessing and multithreading**- Python WSGI protocol
**Python Context Managers**- Python Design Patterns

## Floating Point Arithmetic

Floating-point numbers are represented in computer hardware as base 2 (binary) fractions. For example, the decimal fraction 0.125 has value 1/10 + 2/100 + 5/1000, and in the same way the binary fraction 0.001 has value 0/2 + 0/4 + 1/8. An example is as follows:

Fraction | Decimal | Fractional Approxiamation |
---|---|---|

1/3 | 0.333… | 1/4 + 1/16 + 1/64 . . . |

Unfortunately, most ** decimal fractions cannot be represented exactly as binary fractions**. A consequence is that, in general, the decimal floating-point numbers you enter are only approximated by the binary floating-point numbers actually stored in the machine.

No matter how many base 2 digits you’re willing to use, the decimal value 0.1 cannot be represented exactly as a base 2 fraction. In base 2, 1/10 is the infinitely repeating fraction 0.0001100110011001100110011001100…

Stop at any finite number of bits, and you get an approximation.

On most machines today, floats are approximated using a binary fraction with the numerator using the first 53 bits starting with the most significant bit and with the denominator as a power of two. In the case of 1/10, the binary fraction is 3602879701896397 / 2 ** 55 which is close to but not exactly equal to the true value of 1/10.

Note that this is in the very nature of binary floating-point: this is not a bug in Python, and it is not a bug in your code either. You’ll see the same kind of thing in all languages that support your hardware’s floating-point arithmetic (although some languages may not display the difference by default, or in all output modes).

So we can’t compare calculated floating point numbers directly, waht we can do is using `math.isclose()`

function. For example:

import math math.isclose(0.9999999999999, 1.) >>> True

## New Class & Classic Class

A “New Class” is the recommended way to create a class in modern Python. A “Classic Class” or “old-style class” is a class as it existed in Python 2.1 and before. They have been retained for backwards compatibility.

Python 3 only has new-style classes. No matter if you subclass from object or not, classes are new-style in Python 3. New-style classes inherit from object, or from another new-style class.

class NewStyleClass(object): pass class AnotherNewStyleClass(NewStyleClass): pass

Old-style classes don’t.

class OldStyleClass(): pass

Up to Python 2.1, old-style classes were the only flavour available to the user.

The concept of (old-style) class is unrelated to the concept of type: if `x`

is an instance of an old-style class, then `x.__class__`

designates the class of `x`

, but `type(x)`

is always `<type`

.

'instance'>

This reflects the fact that all old-style instances, independently of their class, are implemented with a single built-in type, called instance.

New-style classes were introduced in Python 2.2 to unify the concepts of class and type. A new-style class is simply a user-defined type, no more, no less.

If `x`

is an instance of a new-style class, then `type(x)`

is typically the same as `x.__class__`

(although this is not guaranteed – a new-style class instance is permitted to override the value returned for `x.__class__`

).

The major motivation for introducing new-style classes is to provide a unified object model with a full meta-model.

Reference: stackoverflow

## Array Bisection Algorithm

Python module `bisect`

provides support for maintaining a list in sorted order without having to sort the list after each insertion. For long lists of items with expensive comparison operations, this can be an improvement over the more common approach.

`bisect.bisect_left(a, x, lo=0, hi=len(a))`

:

Locate the insertion point for x in a to maintain sorted order. The parameters lo and hi may be used to specify a subset of the list which should be considered; by default the entire list is used. If x is already present in a, the insertion point will be before (to the left of) any existing entries. The return value is suitable for use as the first parameter to list.insert() assuming that a is already sorted. The returned insertion point i partitions the array a into two halves so that `all(val < x for val in a[lo:i])`

for the left side and `all(val >= x for val in a[i:hi])`

for the right side.

`bisect.bisect(a, x, lo=0, hi=len(a))`

:

Similar to `bisect_left()`

, but returns an insertion point which comes after (to the right of) any existing entries of x in a.

The returned insertion point i partitions the array a into two halves so that `all(val <= x for val in a[lo:i])`

for the left side and `all(val > x for val in a[i:hi])`

for the right side.

`bisect.insort_left(a, x, lo=0, hi=len(a))`

:

Insert `x`

in `a`

in sorted order. This is equivalent to `a.insert(bisect.bisect_left(a, x, lo, hi), x)`

assuming that `a`

is already sorted. Keep in mind that the `O(log n)`

search is dominated by the slow `O(n)`

insertion step.

Hand Implementaion of Binary Search: https://leetcode.com/problems/search-insert-position/

## Descriptors

https://docs.python.org/3.7/howto/descriptor.html

### Python Comprehensions

Comprehensions are constructs that allow sequences to be built from other sequences.

A list comprehension consists of the following parts:

- An Input Sequence.
- A Variable representing members of the input sequence.
- An Optional Predicate expression.
- An Output Expression producing elements of the output list from members of the Input Sequence that satisfy the predicate.

a_list = [1, ‘4’, 9, ‘a’, 0, 4] squared_ints = [ e**2 for e in a_list if type(e) == types.IntType ] print squared_ints # [ 1, 81, 0, 16 ]

### Duck Typing

**Duck typing** in computer programming is an application of the duck test—”If it walks like a duck and it quacks like a duck, then it must be a duck”—to determine if an object can be used for a particular purpose.

class Duck: def fly(self): print("Duck flying") class Airplane: def fly(self): print("Airplane flying") class Whale: def swim(self): print("Whale swimming") def lift_off(entity): entity.fly() duck = Duck() airplane = Airplane() whale = Whale() lift_off(duck) # prints `Duck flying` lift_off(airplane) # prints `Airplane flying` lift_off(whale) # Throws the error `'Whale' object has no attribute 'fly'`