Python tricks summary - a1k89/Blog GitHub Wiki

Before

  • solid - Single responsibility, open/closed, Liskov substitution, Interface segregation, Dependency inversion (единственная работа для объекта. Открыт для расширения и закрыт для изменения. Подкласс должен соответствовать родительскому классу. Строить тонкие клиенты. Зависимость от абстракций, а не от реализаций)

  • Patterns

  1. Creational: Fabric (abstract), singleton, builder
  2. Structural: adapter, decorator, facade
  3. Behavioral: iterator, mediator, template, strategy

2.1 Assertitions

  • Use assert only for debug
  • assert may simple switch on/off

2.2 Complacent Comma Placement

bad:
names = ['Alex', 'Bob', 'Anna']

good:
names = ['Alex',
         'Bob',
         'Anna',] # comma also important

2.3 Context Manager

Use with instead try...finally. With automatically close the file

with open('file.txt', 'w') as file:
    file.write('hello')

Supporting with in Your Own Objects

class FileManager:
    def __init__(self, filename):
         self.filename = filename

    def __enter__(self)
         self.file = open(self.filename, 'w')
         return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()

And now may use as:

with FileManager("hello.txt") as file:
    file.write("some text")

Also, you may to use:

from contextlib import contextmanager

@contextmanager
def managed_file(name):
    try:
        f = open(name, 'w')
        yield f
    finally:
        f.close()

with managed_file('hello.txt', 'w') as file:
    file.write('some text')

Writing Pretty APIs With Context Managers

You may to use with for any class. Only use interface.

class Simple:
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

2.4 Underscores, Dunders and More

  • _some: only as hint (pep8) that variable internal use. Not use in python interpretator
  • some_: to avoid naming conflict only (pep8(
  • __some: to protect the variable.
class Some:
    def __init__(self):
        self.name = 'hello'
        self._name = 'hello'
        self__name = 'hello'

a = Some()
a.name # hello
a._name # hello
a.__name # error. Now we has: _Some.__name
  • __some__: use for magic method. Please don't name your variable like this
  • _: only convention. You may to use this for unpack:
car = ('red', 'auto', 12)
color, _, _ = car

2.5 A shocking Truth about string formatting

  • 'Hello %s' % name - old style ('Hello %(name)s')
  • (python3) 'Hello {}'.format(name)
  • (python3.6) f'Hello {name}'
from string import Template
t = Template('Hey, $name!')

t.substitute(name='hello') # Hey, hello

2.6 The Zen of python easter egg

import this

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

3 Effective Functions

3.1. Python's functions are first-class

  • Functions in python is a object also (like everything in python)
  • Functions can be stored in Data Structures
  • Functions can be passed as argument (decorators. Higher-order functions)
def bark(name):
    return name.upper()

test_array = ['hello',
              'world',]
some = list(map(bark, test_array)) # return ['HELLO', 'WORLD']
  • Nested functions
def some(name):
    def inner(value):
        return f'hello {value}'
    return inner(name)

s = some('Andrei') # hello Andrei
  • Functions can capture local state
  • Objects can behave like functions (callable)
class Sync:
    def __init__(self, name):
        self.name = name

    def sync(self):
         print('object {} was function'.format(self.name))

    def __call__(self):
        return self.sync()

3.2. Lambda are single-expression functions

  • s = lambda x, y: x + y
  • Lambdas you can use:
tuples = [(1, 'c'), (2, 'b'), (3, 'a')]
sorted_tuples = sorted(tuples, key=lambda x: x[1])
  • Use lambda only with sorting

3.3. The power of decorators

  • Decorators can modify behavior
  • In multiple decorators: bottom-to-top
def strong(func):
    def wrp():
        return "<strong>" + func() + "</strong>"
    return wrp

def em(func):
    def wrp():
        return "<em>" + func() + "</em>"
    return em

@strong
@em
def hello(name):
    return 'Hello {}'.format(name)

s = hello('Andrei') # <strong><em>Hello Andrei</em></strong>
  • Decorators with arguments
def proxy(func):
    def wrp(*args, **kwargs):
        print('make something before')
        return func(*args, **kwargs)

@proxy
def some(name, age):
    return f'hello {name} with {age}'

test = some('Andrei', 32)
1. You pass function (only function)
2. You pass args/kwargs. Wrp got it and then run func with unwrap args/kwargs
  • Use functools
def trace(func):
    @functools.wraps(func) # this save all metadata for func! 
    def wrp(*args, **kwargs):
         return func(*args, **kwargs)
    return wrp

3.4. Fun with *args and **kwargs

  • *args is a tuple
  • **kwargs - is a dictionary
def some(required, *args, **kwargs):
   my_args = args # collect all no positional params and create one tuple
   my_kw = kwargs # collect all positional params and create dictionary

   print(my_args)
   print(my_kw)


some('hello', 1,2,3,4,5, ['hello'], key1='12', key2=14)
# [1,2,3,4,5,['hello']]
# {'key1': 12, 'key2': 14}

Simple: when you see * and ** in params:

  1. You pass no/pos arguments
  2. Function get it and wrapped in tuple and dictionary

3.5. Function argument unpacking

  • use * to unpack tuple (list) and ** to unpack dictionary to params
def show_vector(x, y, z):
    print(x,y,z)

point = (1,10,10)

show_vector(*point)

3.6 Nothing to Return Here

  • Any function return None if not assign another value

4 Classes and OOP

4.1 Object comparisons: is vs ==

a = [1,2,3]
b = a

a == b # True
a is b # True (points to one array)

c = list(a) # Another array
a == c # True
a is c # False (another array, another point)

4.2. String Conversion (repr)

  • Add to each class repr (for more information about class when printed to console)
  • str simple calls repr

4.3. Defining your own exception classes

class NameToShortError(ValueError):
    pass

...raise NameToShortError('hello')

4.4. Cloning objects for fun and profit

  • Create copy for simple:
new_list = list(some_list)
new_dict = dict(some_dict)
new_set = set(some_set)
  • Making Shallow copies
>>> xs = [[1,2,3],[3,4,5]]
>>> ys = list(xs)
>>> xs
[[1, 2, 3], [3, 4, 5]]
>>> ys.append('hei!')
>>> ys
[[1, 2, 3], [3, 4, 5], 'hei!']
>>> xs
[[1, 2, 3], [3, 4, 5]]
>>> xs[0][1] = 'hei!hei!'
>>> xs
[[1, 'hei!hei!', 3], [3, 4, 5]]
>>> ys
[[1, 'hei!hei!', 3], [3, 4, 5], 'hui']
>>> 
  • Making Deep copies
>>> import copy
>>> xs = [[1,2,3],[4,5]]
>>> ys = copy.deepcopy(xs)
>>> xs
[[1, 2, 3], [4, 5]]
>>> ys
[[1, 2, 3], [4, 5]]
>>> ys[0][1]='hello'
>>> ys
[[1, 'hello', 3], [4, 5]]
>>> xs
[[1, 2, 3], [4, 5]]
  • Copying arbitrary objects Please use copy built-in module to copy and deepcopy of object
import copy


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point {self.x!r}, {self.y!r}'


class Rect:
    def __init__(self, topleft, bottomright):
        self.topleft = topleft
        self.bottomright = bottomright

    def __repr__(self):
        return f'React {self.topleft}, {self.bottomright}'
    
    
rect = Rect(Point(0,1), Point(4,4))
srect = copy.copy(rect)

rect.topleft.x = -1000000


drect = copy.deepcopy(srect)
drect.topleft.x = -999999


React Point -999999, 1, Point 4, 4
React Point -1000000, 1, Point 4, 4
React Point -1000000, 1, Point 4, 4

4.5. Abstract base class keep inheritance in check

  • Use ABCMeta and abstractmethod
from abc import ABCMeta, abstractmethod

class Base(metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass

Use ABCMeta instead standard way raise NotImplementedError() is a best practice

4.6. What namedtuples are good for

  • The some of extension of standard tuple
  • Principle: write once - read many
from collections import namedtuple

Car = namedtuple('Car', 'color engine style')

mazda = Car('Black', 'gazoline', 'sedan')

color, engine, type_of = mazda
  • You may extend namedtuple
>>> from collections import namedtuple

>>> Car = namedtuple('Car','color milleage')
>>> class MyCar(Car):
...     def hex(self):
...         if self.color == 'red':
...              return 'hello'
...         else:
...              return 'hai'
... 
>>> c = MyCar('red', 1234)
>>> c.hex()
'hello'
  • Also, namedtuple has properties: _fields, _as_dict(), _replace(), _make() *** so far so good

4.7. Class vs Instance variable pitfalls

class Girl:
    bufer_size = 10

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return f'Girl {self.name} {self.bufer_size} size'


anna = Girl('Anna')
olga = Girl('Olga')
roxana = Girl('Roxana')

print('Girl model', Girl.bufer_size)
print(anna)
anna.bufer_size = 6
print(anna)
print('Girl model', Girl.bufer_size)

Girl model 10
Girl Anna 10 size
Girl Anna 6 size
Girl model 10
  • When you assign variable inside class - this is a class variable and available through Class.variable.
  • But if you want to get this variable inside instance - you create this variable inside instance and it make a copy from class variable.

4.8. Instance, class and static method demystified

  • Instance method: method for each instance (very simple)
  • Class method: link to class, not any instance
  • Static method: no any instance or class
  • If you need unique method - use instance method. If you want to create specific fabric - use class method. Finally, for non-class and non-instance methods - use static method

5. Common data structures in python

  • dict. The most usefull data structure
  • collections:
from collections import OrderedDict

od = OrderedDict()
od['hello'] = 1
od['welcome'] = 2
  • collections.defaultdict - create default key
  • collections.chainMap

5.2 Array Data structure

  • `list' - can add/remove
  • tuple - only create and read
  • array.array:
ar = array('f)
ar.append(23.0)
ar.append(2.0)

ar.append('hello') # error!
  • str - immutable strings (can't insert)

5.2 Records, Structure and data transfer objects

  • dict - simple data objects
  • tuple - immutable groups of objects
  • collections.namedtuple
  • struct.Struct

5.4 Sets and multisets

  • Unordered collection of elements (Mutable)
  • Be careful: when create set:
new_set = {1,2,3,4,'10','20','a',12}
type(new_set) # return set

s = {} # wrong! dict return
s1 = set() # good! now is set
  • frozenset - immutable set
  • collections.Counter - multiset
>>> from collections import Counter
>>> inventory = Counter()

>>> loot = {'sword': 1, 'bread': 3}
>>> inventory.update(loot)
>>> inventory
Counter({'bread': 3, 'sword': 1})

>>> more_loot = {'sword': 1, 'apple': 1}
>>> inventory.update(more_loot)
>>> inventory
Counter({'bread': 3, 'sword': 2, 'apple': 1})”

5.5 Stacks (LIFO)

  • Lifo - last in first out

  • FIFO - first in first out

  • lists - simple, built-in stacks

  • collections.deque - fast stack

  • queue.LifoQueue

5.6 FIFO

6. Looping and iterations

6.1 Writing python loop

  • for i in some_arr...
  • (to get index):
arr = [1,2,3,4]
for index, value in enumerate(arr):
    ...
  • range(start_value, to_end_value, step(optional))

6.2 Comprehending Comprehensions

some_list = [x * x for x in range (1,5) # [1,4,9,16]
some_dict = {x: x * x for x in range (1,5) if x != 3} # {1: 1, 2: 4, 4: 16 }
some_set = {x * x for x in range(1,5} # {1,4,9,16} # unordered set!!!

6.3 List slicing tricks and sushi operator

lst = [1,2,3,4,5,6]
ss = lst[1:4] # [2,3,4,] not included last
ss = lst[1:4:2] # [2,4]
ss = ls[::-1] # [6,5,4,3,2,1] reverse!!!

del lst[::] # [] this delete all items but save cursor to lst

6.4 Beautiful iterators

  • Python iterator protocol
  • Iterator: structure who consist of: __iter__ and __next__
class SomeRepeater:
    def __init__(self, value, limit):
        self.value = value
        self.count = 0
        self.limit = limit

    def __iter__(self)
        return self


    def __next__(self):
        if self.count >= limit:
             raise StopIteration()
        sef.count += 1
        return self.value

6.5 Generators simplifier iterators

  • Magic yield keyword
def generator(value):
    while True:
        yield value
  • Some syntax sugar for iterators
  • Generator Expressions vs List Comprehensions:
lst = [x for x in range(100)] # return list. And list all in memory
gen = (x for x in range(100)) # return generator objects. And values not located in a memory!
  • You may filtering values in generator:
gen = (x for x in range(100) if x % 2 == 0)

6.7 Iterator chains

⚠️ **GitHub.com Fallback** ⚠️