Python cheatsheet

Tuples

Tuple unpacking

In [2]:
def divmod(x,y):
    return x // y, x % y

args = (20,8)
divmod(20, 8) == divmod(*args)

Using * to grab excess items

In [8]:
a, b, *rest = range(5)
a, b, rest
Out[8]:
(0, 1, [2, 3, 4])
In [9]:
a, b, *rest = range(3)
a, b, rest
Out[9]:
(0, 1, [2])
In [10]:
a, b, *rest = range(2)
a, b, rest
Out[10]:
(0, 1, [])

* prefix can appear in any position

In [12]:
*head, a, b = range(5)
a, b, head
Out[12]:
(3, 4, [0, 1, 2])

Nested Tuple unpacking

In [13]:
metro_areas = [
    ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
    (' Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
    ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
    ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
    ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]
In [16]:
print('{:15} | {:^9} | {:^9}'.format('', 'lat.', 'long.'))
fmt = '{:15} | {:9.4f} | {:9.4f}'
for name, cc, pop, (lat, long) in metro_areas:
    if long <= 0:
        print(fmt.format(name, lat, long))
                |   lat.    |   long.
Mexico City     |   19.4333 |  -99.1333
New York-Newark |   40.8086 |  -74.0204
Sao Paulo       |  -23.5478 |  -46.6358

Named Tuples

In [18]:
from collections import namedtuple
In [19]:
City = namedtuple('City', 'name country population coordinates')
In [20]:
tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 133.5846))
In [21]:
tokyo
Out[21]:
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 133.5846))
In [23]:
tokyo.country
Out[23]:
'JP'
In [24]:
tokyo[1]
Out[24]:
'JP'
In [25]:
City._fields
Out[25]:
('name', 'country', 'population', 'coordinates')
In [26]:
LatLong = namedtuple('LatLong', 'lat long')
In [28]:
delhi_data = ('Delhi NCR', 'IN', 21.935, LatLong(28.354, 77.2))
In [29]:
delhi = City._make(delhi_data);delhi
Out[29]:
City(name='Delhi NCR', country='IN', population=21.935, coordinates=LatLong(lat=28.354, long=77.2))
In [30]:
delhi._asdict()
Out[30]:
OrderedDict([('name', 'Delhi NCR'),
             ('country', 'IN'),
             ('population', 21.935),
             ('coordinates', LatLong(lat=28.354, long=77.2))])
In [32]:
for k, v in delhi._asdict().items():
    print(k + ":", v)
name: Delhi NCR
country: IN
population: 21.935
coordinates: LatLong(lat=28.354, long=77.2)

Slicing

In [33]:
s = 'bicycle'
s[::3]
Out[33]:
'bye'
In [34]:
s[::-1]
Out[34]:
'elcycib'
In [35]:
s[::-3]
Out[35]:
'eyb'
In [38]:
# You can name a slice
first_two = slice(0,2)
s[first_two]
Out[38]:
'bi'
In [42]:
# You can assign to slices
numbers = list(range(10))
numbers[2:5] = [20, 30]; numbers
Out[42]:
[0, 1, 20, 30, 5, 6, 7, 8, 9]
In [43]:
del numbers[5:7]; numbers
Out[43]:
[0, 1, 20, 30, 5, 8, 9]
In [44]:
numbers[3::2] = [11, 22]; numbers
Out[44]:
[0, 1, 20, 11, 5, 22, 9]
In [46]:
numbers[2:5] = [100]; numbers
Out[46]:
[0, 1, 100]

Python bytecode disassembly tool – dis

In [50]:
import dis

dis.dis('s[a] += b')
  1           0 LOAD_NAME                0 (s)
              2 LOAD_NAME                1 (a)
              4 DUP_TOP_TWO
              6 BINARY_SUBSCR
              8 LOAD_NAME                2 (b)
             10 INPLACE_ADD
             12 ROT_THREE
             14 STORE_SUBSCR
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

Key is brilliant

In [51]:
l = [28,14,'28',5,1,'1','23',19]
sorted(l, key=int)
Out[51]:
[1, '1', 5, 14, 19, '23', 28, '28']
In [52]:
sorted(l, key=str)
Out[52]:
[1, '1', 14, 19, '23', 28, '28', 5]

Functions as first-class objects

In [54]:
def factorial(n):
    '''returns n!'''
    return n if n < 2 else factorial(n-1) * n

factorial(10)
Out[54]:
3628800
In [55]:
help(factorial)
Help on function factorial in module __main__:

factorial(n)
    returns n!

In [56]:
[factorial(i) for i in range(10)]
Out[56]:
[0, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
In [61]:
list(map(factorial, range(10)))
Out[61]:
[0, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]

Higher-order functions

In [57]:
def reverse(word):
    return word[::-1]

reverse('seyoung')
Out[57]:
'gnuoyes'
In [59]:
fruits = 'banaani mustikka appelsiini omena'.split()
sorted(fruits, key=reverse)
Out[59]:
['mustikka', 'omena', 'banaani', 'appelsiini']
In [62]:
sorted(fruits, key=lambda word: word[::-1])
Out[62]:
['mustikka', 'omena', 'banaani', 'appelsiini']

User-defined callable types

In [65]:
import random

class BingoCage:

    def __init__(self, items):
        self._items = list(items)
        random.shuffle(self._items)

    def pick(self):
        try:
            return self._items.pop()
        except IndexError:
            raise LookupError('Pick from empty BingoCage')

    def __call__(self):
        return self.pick()
In [69]:
bingo = BingoCage(range(30))
bingo(), bingo.pick()
Out[69]:
(17, 7)

Decorators

In [ ]:
'''
The following two are equivalent
'''
class A:
    @classmethod
    def method(cls):
        pass


class B:
    def method(cls):
        pass
    method classmethod(method)
In [71]:
def deco(func):
    def inner():
        print('running inner()')
        func()
    return inner

@deco
def target():
    print('running target()')

target()
running inner()
running target()

Decorator is just a syntatic sugar as passing another function to a function:

In [74]:
def inner(func):
    print('running inner()')
    func()

def target():
    print('running target()')


inner(target)
running inner()
running target()

Decorator – practical usecase

In [76]:
promos = []

def promotion(promo_func):
    promos.append(promo_func)
    return promo_func

'''
Decorators are run on import time. Thus `promos` will be filled up
as soon as the module is imported.
'''

@promotion
def fidelity(order):
    '''
    5% discount for customers with 1000+ fidelity points
    '''
    return order.total * .05 if order.customer.fidelity >= 1000 else 0

@promotion
def bulk_item(order):
    '''10% discount for each LineItem with 20 or mor units'''
    discount = 0
    for item in order.cart:
        if item.quantity >= 20:
            discount += item.total() * .1
    return discount


def best_promo(order):
    '''Select best discount available'''
    return max(promo(order) for promo in promos)

Closure

A closure is a function within a function which accesses nonglobal variable that is declared outside its body

closure\_variable\_scope

closure_variable_scope

In [77]:
def make_averager():
    series = []

    def avg(new_value):
        series.append(new_value)
        return sum(series) / len(series)

    return avg

avg = make_averager()
avg(10), avg(11), avg(12)
Out[77]:
(10.0, 10.5, 11.0)
In [78]:
avg.__code__.co_varnames
Out[78]:
('new_value',)
In [79]:
avg.__code__.co_freevars
Out[79]:
('series',)
In [80]:
avg.__closure__
Out[80]:
(<cell at 0x1118e90a8: list object at 0x112697d88>,)
In [82]:
avg.__closure__[0].cell_contents
Out[82]:
[10, 11, 12]

Nonlocal declaration

In [83]:
def make_averager():
    count = 0
    total = 0

    def avg(new_value):
        nonlocal count, total
        count += 1
        total += new_value
        return total / count

    return avg

Runtime measure decorator

In [35]:
import time
import functools

def clock(func):
    @functools.wraps(func)
    def clocked(*args, **kwargs):
        t0 = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - t0
        name = func.__name__

        arg_list = []
        if args:
            arg_list.append(', '.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
            arg_list.append(', '.join(pairs))
        arg_str = ', '.join(arg_list)
        print('[{:.8f}s] {:s}({:s}) -> {}'.format(
            elapsed,
            name,
            arg_str,
            result
        ))
        return result
    return clocked
In [53]:
@clock
def snooze(seconds):
    time.sleep(seconds)

@clock
def factorial(n):
    '''returns n!'''
    return n if n < 2 else factorial(n-1) * n

snooze(1)
[1.00002574s] snooze(1) -> None
In [44]:
factorial(10)
Out[44]:
3628800

Unwrapping a decorator

In [54]:
snooze.__wrapped__(2)

Momoization decorator

In [103]:
'''
@functools.lru_cache(maxsize=128, typed=False)
maxsize should be a power of 2 for optimal performance.
'''
@functools.lru_cache(maxsize=128, typed=False)
@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-2) + fibonacci(n-1)

fibonacci(100)
[0.00000047s] fibonacci(0) -> 0
[0.00000062s] fibonacci(1) -> 1
[0.00018753s] fibonacci(2) -> 1
[0.00000126s] fibonacci(3) -> 2
[0.00023754s] fibonacci(4) -> 3
[0.00000094s] fibonacci(5) -> 5
[0.00028544s] fibonacci(6) -> 8
[0.00000091s] fibonacci(7) -> 13
[0.00033311s] fibonacci(8) -> 21
[0.00000088s] fibonacci(9) -> 34
[0.00038029s] fibonacci(10) -> 55
[0.00000106s] fibonacci(11) -> 89
[0.00042941s] fibonacci(12) -> 144
[0.00000100s] fibonacci(13) -> 233
[0.00047744s] fibonacci(14) -> 377
[0.00000095s] fibonacci(15) -> 610
[0.00052555s] fibonacci(16) -> 987
[0.00000098s] fibonacci(17) -> 1597
[0.00057387s] fibonacci(18) -> 2584
[0.00000093s] fibonacci(19) -> 4181
[0.00062187s] fibonacci(20) -> 6765
[0.00000091s] fibonacci(21) -> 10946
[0.00067036s] fibonacci(22) -> 17711
[0.00000106s] fibonacci(23) -> 28657
[0.00071860s] fibonacci(24) -> 46368
[0.00000088s] fibonacci(25) -> 75025
[0.00076595s] fibonacci(26) -> 121393
[0.00000088s] fibonacci(27) -> 196418
[0.00081273s] fibonacci(28) -> 317811
[0.00000088s] fibonacci(29) -> 514229
[0.00085992s] fibonacci(30) -> 832040
[0.00000092s] fibonacci(31) -> 1346269
[0.00090667s] fibonacci(32) -> 2178309
[0.00000092s] fibonacci(33) -> 3524578
[0.00095364s] fibonacci(34) -> 5702887
[0.00000090s] fibonacci(35) -> 9227465
[0.00100009s] fibonacci(36) -> 14930352
[0.00000097s] fibonacci(37) -> 24157817
[0.00104787s] fibonacci(38) -> 39088169
[0.00000089s] fibonacci(39) -> 63245986
[0.00109493s] fibonacci(40) -> 102334155
[0.00000104s] fibonacci(41) -> 165580141
[0.00114280s] fibonacci(42) -> 267914296
[0.00000092s] fibonacci(43) -> 433494437
[0.00119226s] fibonacci(44) -> 701408733
[0.00000107s] fibonacci(45) -> 1134903170
[0.00124033s] fibonacci(46) -> 1836311903
[0.00000107s] fibonacci(47) -> 2971215073
[0.00128768s] fibonacci(48) -> 4807526976
[0.00000105s] fibonacci(49) -> 7778742049
[0.00133551s] fibonacci(50) -> 12586269025
[0.00000108s] fibonacci(51) -> 20365011074
[0.00149767s] fibonacci(52) -> 32951280099
[0.00000117s] fibonacci(53) -> 53316291173
[0.00154963s] fibonacci(54) -> 86267571272
[0.00000100s] fibonacci(55) -> 139583862445
[0.00159663s] fibonacci(56) -> 225851433717
[0.00000101s] fibonacci(57) -> 365435296162
[0.00164442s] fibonacci(58) -> 591286729879
[0.00000104s] fibonacci(59) -> 956722026041
[0.00169188s] fibonacci(60) -> 1548008755920
[0.00000101s] fibonacci(61) -> 2504730781961
[0.00173856s] fibonacci(62) -> 4052739537881
[0.00000097s] fibonacci(63) -> 6557470319842
[0.00178615s] fibonacci(64) -> 10610209857723
[0.00000098s] fibonacci(65) -> 17167680177565
[0.00183278s] fibonacci(66) -> 27777890035288
[0.00000101s] fibonacci(67) -> 44945570212853
[0.00187957s] fibonacci(68) -> 72723460248141
[0.00000108s] fibonacci(69) -> 117669030460994
[0.00192640s] fibonacci(70) -> 190392490709135
[0.00000095s] fibonacci(71) -> 308061521170129
[0.00197353s] fibonacci(72) -> 498454011879264
[0.00000098s] fibonacci(73) -> 806515533049393
[0.00202084s] fibonacci(74) -> 1304969544928657
[0.00000100s] fibonacci(75) -> 2111485077978050
[0.00303772s] fibonacci(76) -> 3416454622906707
[0.00000149s] fibonacci(77) -> 5527939700884757
[0.00316910s] fibonacci(78) -> 8944394323791464
[0.00000113s] fibonacci(79) -> 14472334024676221
[0.00321860s] fibonacci(80) -> 23416728348467685
[0.00000100s] fibonacci(81) -> 37889062373143906
[0.00326648s] fibonacci(82) -> 61305790721611591
[0.00000097s] fibonacci(83) -> 99194853094755497
[0.00331421s] fibonacci(84) -> 160500643816367088
[0.00000098s] fibonacci(85) -> 259695496911122585
[0.00336403s] fibonacci(86) -> 420196140727489673
[0.00000106s] fibonacci(87) -> 679891637638612258
[0.00341073s] fibonacci(88) -> 1100087778366101931
[0.00000110s] fibonacci(89) -> 1779979416004714189
[0.00345798s] fibonacci(90) -> 2880067194370816120
[0.00000103s] fibonacci(91) -> 4660046610375530309
[0.00350508s] fibonacci(92) -> 7540113804746346429
[0.00000098s] fibonacci(93) -> 12200160415121876738
[0.00355214s] fibonacci(94) -> 19740274219868223167
[0.00000095s] fibonacci(95) -> 31940434634990099905
[0.00359954s] fibonacci(96) -> 51680708854858323072
[0.00000101s] fibonacci(97) -> 83621143489848422977
[0.00364663s] fibonacci(98) -> 135301852344706746049
[0.00000094s] fibonacci(99) -> 218922995834555169026
[0.00369384s] fibonacci(100) -> 354224848179261915075
Out[103]:
354224848179261915075

singledispatch for function overloading

In [110]:
from functools import singledispatch
from collections import abc
import numbers
import html

@singledispatch
def example_func(object):
    print("any: ", object)

@example_func.register(str)
def _(text):
    print("str: ", text)

@example_func.register(int)
def _(number):
    print("int: ", number)

@example_func.register(tuple)
@example_func.register(list)
def _(number):
    print("sequence ", number)


example_func(123)
example_func('123')
example_func([123,543])
example_func((123,543))
example_func(23.4)
int:  123
str:  123
sequence  [123, 543]
sequence  (123, 543)
any:  23.4

Parameterized clock decorator

Notice the parenthesis after @clock below.

In [2]:
import time

DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'

def clock(fmt=DEFAULT_FMT):
    def decorate(func):
        def clocked(*_args):
            t0 = time.time()
            _result = func(*_args)
            elapsed = time.time() - t0
            name = func.__name__
            args = ', '.join(repr(arg) for arg in _args)
            result = repr(_result)
            print(fmt.format(**locals()))
            return _result
        return clocked
    return decorate

@clock()
def snooze(seconds):
    time.sleep(seconds)

for _ in range(3):
    snooze(.123)

print("\nWith a custom argument\n")

@clock('{name}: {elapsed}s')
def snooze(seconds):
    time.sleep(seconds)

for _ in range(3):
    snooze(.123)


[0.12447476s] snooze(0.123) -> None
[0.12471581s] snooze(0.123) -> None
[0.12503409s] snooze(0.123) -> None

With a custom argument

snooze: 0.12709403038024902s
snooze: 0.12314391136169434s
snooze: 0.12816691398620605s

Parameterized clock decorator with partial for removing ()

Notice the parenthesis after @clock is GONE below.

In [3]:
import time
from functools import wraps, partial
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'

def clock(func=None, fmt=DEFAULT_FMT):
    if not func:
        return partial(clock, fmt=fmt)

    @wraps(func)
    def clocked(*_args):
        t0 = time.time()
        _result = func(*_args)
        elapsed = time.time() - t0
        name = func.__name__
        args = ', '.join(repr(arg) for arg in _args)
        result = repr(_result)
        print(fmt.format(**locals()))
        return _result
    return clocked

@clock
def snooze(seconds):
    time.sleep(seconds)

for _ in range(3):
    snooze(0.123)

print("\nWith a custom argument\n")

@clock(fmt='{name}: {elapsed}s')
def snooze(seconds):
    time.sleep(seconds)

for _ in range(3):
    snooze(.123)
[0.12613082s] snooze(0.123) -> None
[0.12330794s] snooze(0.123) -> None
[0.12451982s] snooze(0.123) -> None

With a custom argument

snooze: 0.12448596954345703s
snooze: 0.12357401847839355s
snooze: 0.12677001953125s

Logger decorator

In [1]:
from functools import wraps
import logging

logging.basicConfig(
    filename='code/example.log',
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.DEBUG
)

def logged(level, name=None, message=None):
    def decorate(func):
        logname = name if name else func.__module__
        logger = logging.getLogger(logname)
        logmsg = message if message else func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs):
            logger.log(level, logmsg)
            return func(*args, **kwargs)
        return wrapper
    return decorate

@logged(logging.DEBUG)
def add(x, y):
    return x + y

@logged(logging.CRITICAL, 'example')
def mul(x, y):
    return x * y

add(2, 34), mul(5,3)
Out[1]:
(36, 15)

Enforcing type checking on a function using a decorator

In [9]:
from inspect import signature

def typeassert(*ty_args, **ty_kwargs):
    def decorate(func):
        # If in optimized mode, disable type checking
        if not __debug__:
            return func

        # Map function argument names to supplied types
        sig = signature(func)
        bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments

        @wraps(func)
        def wrapper(*args, **kwargs):
            bound_values = sig.bind(*args, **kwargs)
            # Enforce type assertion across supplied arguments
            for name, value in bound_values.arguments.items():
                if name in bound_types:
                    if not isinstance(value, bound_types[name]):
                        raise TypeError(
                            'Argument {} must be {}'.format(
                                name,
                                bound_types[name]
                            )
                        )
            return func(*args, **kwargs)
        return wrapper
    return decorate


@typeassert(int, z=int)
def spam(x,y,z=42):
    print(x,y,z)

spam(1,2,3)
spam('1',2,3)
spam(1,'23',3)
spam(1,'23','23')
1 2 3
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-c3a04188cd07> in <module>()
     37
     38 spam(1,2,3)
---> 39 spam('1',2,3)
     40 spam(1,'23',3)
     41 spam(1,'23','23')

<ipython-input-9-c3a04188cd07> in wrapper(*args, **kwargs)
     21                             'Argument {} must be {}'.format(
     22                                 name,
---> 23                                 bound_types[name]
     24                             )
     25                         )

TypeError: Argument x must be <class 'int'>

Anatomy of a function wrapper with a class v.1

class function_wrapper(object):
    def __init__(self, wrapped):
        self.wrapped = wrapped
    def __call__(self, *args, **kwargs):
        return self.wrapped(*args, **kwargs)

@function_wrapper
def function():
    pass
In [6]:
import functools

class function_wrapper(object):
    def __init__(self, wrapped):
        self.wrapped = wrapped
        functools.update_wrapper(self, wrapped)

    def __call__(self, *args, **kwargs):
        print("wrapper")
        return self.wrapped(*args, **kwargs)

@function_wrapper
def function():
    print("actual func")

function()
wrapper
actual func

Anatomy of a function wrapper with a class v.2

In [18]:
import types
from functools import wraps

class function_wrapper:
    ncalls = 0
    def __init__(self, wrapped):
        wraps(wrapped)(self)
#         self.ncalls = 0

    def __call__(self, *args, **kwargs):
        function_wrapper.ncalls += 1
        print("wrapper")
#         self.ncalls += 1
#         ncalls += 1
        return self.__wrapped__(*args, **kwargs)

    def __get__(self, instance, cls):
        '''
        This method is necessary when using a class decorator
        to an instance method.

        Maybe it is just better to use a closure decorator.
        '''
        if not instance:
            return self
        else:
            return types.MethodType(self, instance)

@function_wrapper
def function():
    print("actual func")

function()
function.ncalls
wrapper
actual func
Out[18]:
1

Applying decorators to Class and

Proper decorators

In [32]:
import functools

'''
Factory for creating decorators
'''
class object_proxy(object):

    def __init__(self, wrapped):
        self.wrapped = wrapped
        try:
            self.__name__= wrapped.__name__
        except AttributeError:
            pass

    @property
    def __class__(self):
        return self.wrapped.__class__

    def __getattr__(self, name):
        return getattr(self.wrapped, name)


class bound_function_wrapper(object_proxy):

    def __init__(self, wrapped, instance, wrapper):
        super(bound_function_wrapper, self).__init__(wrapped)
        self.instance = instance
        self.wrapper = wrapper

    def __call__(self, *args, **kwargs):
        if self.instance is None:
            instance, args = args[0], args[1:]
            wrapped = functools.partial(self.wrapped, instance)
            return self.wrapper(wrapped, instance, args, kwargs)
        return self.wrapper(self.wrapped, self.instance, args, kwargs)


class function_wrapper(object_proxy):

    def __init__(self, wrapped, wrapper):
        super(function_wrapper, self).__init__(wrapped)
        self.wrapper = wrapper

    def __get__(self, instance, owner):
        '''
         If the wrapper is applied to a method of a class,
         the __get__() method is called when the attribute is
         accessed, which returns a new bound wrapper and the
         __call__() method of that is invoked instead when a
         call is made.
        '''
        wrapped = self.wrapped.__get__(instance, owner)
        return bound_function_wrapper(wrapped, instance, self.wrapper)

    def __call__(self, *args, **kwargs):
        return self.wrapper(self.wrapped, None, args, kwargs)


def decorator(wrapper):
    '''
    A decorator for creating decorators
    '''
    @functools.wraps(wrapper)
    def _decorator(wrapped):
        print("_decorator: wrapped: ", wrapped.__name__)
        print("_decorator: wrapper: ", wrapper.__name__)
        return function_wrapper(wrapped, wrapper)
    return _decorator
In [33]:
@decorator
def my_function_wrapper(wrapped, instance, args, kwargs):
    print('INSTANCE', instance)
    print('ARGS', args)
    return wrapped(*args, **kwargs)

@my_function_wrapper
def function(a, b):
    pass

function(3,24)

# class Class(object):
#     @my_function_wrapper
#     def function_im(self, a, b):
#         pass

# c = Class()  # __get__ is called

# c.function_im(1,2)
_decorator: wrapped:  function
_decorator: wrapper:  my_function_wrapper
INSTANCE None
ARGS (3, 24)
In [34]:
class Class(object):
    @my_function_wrapper
    def function_im(self, a, b):
        pass

c = Class()  # __get__ is called
Class.function_im(c, 1, 2)

# c.function_im(1,2)
_decorator: wrapped:  function_im
_decorator: wrapper:  my_function_wrapper
INSTANCE <__main__.Class object at 0x106c92e10>
ARGS (1, 2)
In [35]:
def decorator(wrapper):
    @functools.wraps(wrapper)
    def _decorator(wrapped):
        return function_wrapper(wrapped, wrapper)
    return _decorator

def optional_arguments(wrapped=None, arg=1):
    if wrapped is None:
        return functools.partial(optional_arguments, arg=arg)

    @decorator
    def _wrapper(wrapped, instance, args, kwargs):
        return wrapped(*args, **kwargs)

    return _wrapper(wrapped)

@optional_arguments(arg=2)
def function1():
    pass

@optional_arguments
def function2():
    pass

function1()
function2()
In [36]:
import this
The Zen of Python, by Tim Peters

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!
In [39]:
x = 4
y = 2
if x == 4: print(x, y); x, y = y, x
4 2
In [42]:
def powOfTwo(n: int = 2) -> int:
    return n * n

powOfTwo(5), powOfTwo()
Out[42]:
(25, 4)

Getter and Setter with @property

In [45]:
class Person:
    def __init__(self, age):
        self.age = age

    @property
    def value(self):
        return 80 - self.age

    @value.setter
    def value(self, age):
        self.age = age

jack = Person(10)
print(jack.value)
jack.value = 12
print(jack.value)
70
68
In [21]:
'asdf'.suffix(1)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-21-6e590c9db7d1> in <module>()
----> 1 'asdf'.suffix(1)

AttributeError: 'str' object has no attribute 'suffix'
In [16]:
class TimemachineServerMonitor:

    def __init__(self):
        pass

    @property
    def timestamp(self):
        return format(datetime.now(), '%Y/%B/%d %A %H:%M:%S')

t = TimemachineServerMonitor()
t.timestamp
Out[16]:
'2018/July/06 Friday 12:38:47'

Getter & Setter using Descriptor

In [30]:
'''
Getter & Setter using Descriptor. With WeakKeyDictionary, it's free from memory leak
'''

from weakref import WeakKeyDictionary


class Grade(object):
    def __init__(self):
        self._value = WeakKeyDictionary()

    def __get__(self, instance, instance_type):
        if not instance: return self
        return self._value.get(instance, 0)

    def __set__(self,instance, value):
        if not (0 <= value <= 100):
            raise ValueError
        self._value[instance] = value

class Exam:
    math_grade = Grade()
    writing_grade = Grade()
    science_grade = Grade()

exam = Exam()

exam.writing_grade = 40;
print(exam.writing_grade)

exam2 = Exam()
print(exam2.writing_grade)
40
0
In [31]:
seyoung = Exam()
In [32]:
seyoung.math_grade = 100; seyoung.math_grade
Out[32]:
100
In [33]:
fabio = Exam(); fabio.math_grade
Out[33]:
0
In [34]:
my_grade = Grade()

Datetime

In [11]:
from datetime import datetime
format(datetime.now(), '%Y/%B/%d %A %H:%M')
Out[11]:
'2018/July/06 Friday 12:34'
In [ ]: