class MyOwnClass(object):
    """
    This is a documentation for my class, so anyone can read it.
    """
    CLASS_CONST = 42
    
    def __init__(self, argument1, default_argument2=1):
        """
        This is a constructor of MyOwnClass. It saves all arguments
        as a 'protected' variables of a class.
        """
        self._argument1 = argument1
        self._argument2 = default_argument2
        super(MyOwnClass, self).__init__()
    
    def get_info(self):
        return ("Class const = %d\nArgument1 = %s\nArgument2 = %d" 
              % (self.CLASS_CONST, self.argument1, self.argument2))
    
    def print_all(self):
        print(self.get_info())
        
    @property
    def argument1(self):
        return self._argument1
    
    @property
    def argument2(self):
        return 2**self._argument2

instance_of_my_own_class_1 = MyOwnClass('string arg #1') 
instance_of_my_own_class_1.print_all()

instance_of_my_own_class_2 = MyOwnClass('string arg #1', 2)
instance_of_my_own_class_2.print_all() 

instance_of_my_own_class_3 = MyOwnClass('string arg #1', default_argument2=4)
instance_of_my_own_class_3.print_all()

instance_of_my_own_class_4 = MyOwnClass(default_argument2=5, argument1=1)
instance_of_my_own_class_4.print_all()

instance_of_my_own_class_5 = MyOwnClass('string arg #1', 'string')
instance_of_my_own_class_5.print_all()

"""
Explanation: Structures
Classes
End of explanation
"""


dir(instance_of_my_own_class_1)

instance_of_my_own_class_1.__dict__

"""
Explanation: Magic methods
There are a bunch of magic methods available in Python. You can overide some of them:

__init__
__unicode__
__str__
__getattr__
__setattr__
__gt__
__gte__
__lt__
__lte__
...
End of explanation
"""


[i*10 for i in range(10)]

(i*10 for i in range(10))

list((i*10 for i in range(10)))

{i / 2 for i in range(10)}

set([1,2,3,1,2,3])

{i: (i - 2, i**2) for i in range(10)}

dict((i, i**i) for i in range(10))

map(lambda x: x*10, range(10))

from functools import reduce

reduce(lambda prev_result, item: prev_result * item, range(1, 5))

{i: i**2 for i in range(10) if i % 3 == 0}

[(a, b) for a in range(4) for b in range(0, -a, -1)]

[1] * 10

[[1] * 5] * 5

matrix = [[1] * 5] * 5
matrix

matrix[3][2] = 2

matrix

matrix = [[1] * 5 for i in range(5)]
matrix

matrix[3][2] = 2

matrix

"""
Explanation: Iterators & Generators
End of explanation
"""


DEFAULT_LOGIN = 'root'

def my_function(arg1, arg2, arg3='str', arg4=None):
    if arg4 is None:
        arg4 = {}
    if 'login' not in arg4:
        arg4['login'] = DEFAULT_LOGIN
    if 'id' not in arg4:
        arg4['id'] = 0
    else:
        arg4['id'] += 1
    print(arg1, arg2, arg3, arg4)

my_function(1, 2)

args = [10, 20]
kwargs = {
    'arg3': 'unicode',
    'arg4': {'login': 'frol', 'password': '$(KE)@D)$!1'},
}

my_function(*args, **kwargs)

my_function(*args, **kwargs)

kwargs

kwargs = {
    'arg1': 11,
    'arg2': 22,
    'arg3': 'unicode',
    'arg4': {'login': 'frol', 'password': '$(KE)@D)$!1'},
}

my_function(**kwargs)

kwargs

def my_function2(*args, **kwargs):
    login = DEFAULT_LOGIN
    if 'arg4' in kwargs and 'login' in kwargs['arg4']:
        login = kwargs['arg4']['login']
    kwargs['arg3'] = login
    my_function(*args, **kwargs)

my_function2(**kwargs)

"""
Explanation: Tricks in functions (class methods)
End of explanation
"""


