#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# 函数的参数
print('函数的参数')
print('python的函数定义非常简单，但灵活度却非常大，'
      '除了正常定义的必选参数外，'
      '还可以使用默认参数、可变参数和关键字参数，'
      '使得函数定义出来的接口，不但能处理复杂的参数'
      '还可以简化调用者的代码')

# 位置参数
print('位置参数')
print('即哪个位置的参数调用的时候就放在哪个位置')

# 默认参数
print('默认参数')
print('即就是在函数的参数位置给参数默认值'
      '在调用时，带有默认值的参数位置可以不写')
print('设置默认参数时，有几点要注意：'
      '1、必选参数在前，默认参数在后，否则pyhon的解释器会报错'
      '2、如何设置默认参数：当函数有多个参数时，把变化大的参数放前面，变化小的参数放后面，变化小的参数就可以作为默认参数'
      '-------------------------------'
      '使用默认参数有什么好处，最大的好处是能降低调用函数的难度')

def add_end(L=[]):
    L.append('END')
    return L
print(add_end([1,2,3]))
print(add_end([1]))
print(add_end(['x']))
print(add_end())
print(add_end())
print(add_end())
print('上面的三次调用同一函数，却一次比一次多打印一个，'
      '是因为参数中的L是可变的，调用一次会‘记住’上次的内容'
      '所以要使用None这个不变对象来实现，调用多少次都不会有改变')

print('为啥要设计str，None这样的不变对象，因为不变对象一旦创建，对象内部的数据就不能修改'
      '这样就减少了由于修改数据导致的错误。'
      '此外，由于对象不变，多任务环境下同时读取对象不需要加锁，'
      '同时读一点问题都没有，我们在编写程序时，如果可以设计一个不变对象'
      '那就尽量设计成不变对象')

# 可变参数
print('在python函数中，可以定义可变参数，就是传入的参数个数是可变的')
print('用发：在参数前面加一个 * 号即为可变参数，'
      '调用函数时，可以传入任意个参数，包括0个；'
      '也可以给list和tuple前面加 * 号，变为可变参数来使用')


# 关键字参数
print('可变参数允许传入0个或任意个参数，这些可变参数在函数条用时自动组装成一个tuple；'
      '而关键字参数也允许传入0个或任意个含参数名的参数，这些关键字参数在函数内部自动组装成一个dict；')

# def person(name, age, **kw):
#       print('name', name, 'age', age, 'other', kw)
print('person(\'bob\', 20, city=\'beijing\'),即city是任意参数')
print('person函数除了必选参数name和age外还接受关键字参数kw。'
      '在调用函数时，可以只传入必选参数')

print('在做用户注册的功能，除了用户名和年龄是必须，其他都是可选项，'
      '利用关键字参数来定义这个函数就可以满足注册的需求')
print('可以和可变参数类似，先组装出一个dict，然后把该dict转换成关键字参数传进去')

# extra = {'city': 'Beijing', 'Job': 'Engineer'}
# person('Jack', 20, city=extra['city'], Job=extra['Job'])
# person('Jack', 20, **extra)         # 与上一行结果一样
print('**extra表示把extra这个dict的所有key-value用关键字参数传入函数的**kw参数中'
      'kw将获得一个dict，注意kw获得的dict是extra的一份拷贝，'
      '对kw的改动不会影响到函数外的extra')


# 命名关键字参数
print('命名关键字参数')
# 命名关键字参数，函数的调用者可以传入任意不受限制的关键字参数
# 至于到底传入哪写，就需要在函数内部通过kw检查
# 还是上面的person的例子，我们希望检查是否有city和job参数
def person(name, age, **kw):
    if 'city' in kw:
        # 有city参数
        pass
    if 'job' in kw:
        # 有job参数
        pass
    print('name：', name, 'age:', age, 'other:', kw)
#     但调用者仍可以传入不受限制的关键字参数
# 想要限制关键字参数的名字，就可以用命名关键字参数，
# 例如：只接收city和job作为关键字参数，定义的函数如下
def person(name, age, *, city, job):
    print(name, age, city, job)
print('和关键字参数**kw不同，命名关键字参数需要一个特殊分隔符*， '
      '* 后面的参数被视为命名关键字参数')
print('调用方式如下：'
      'person(\'jack\', 24, city=\'beijing\', job=\'engnineer\')')
print('命名关键字参数必须传入参数名，这个和位置参数不同，'
      '如果没有传入参数名，调用将报错')
print('也可以给参数传入默认值，在调用的时候，可不传入带默认值的参数；')
print('使用命名关键字参数时，要特别注意，* 不是参数，而是特殊分隔符')
print('如果缺少 * ，python解释器将无法识别位置参数和命名关键字参数')


# 参数组合
print('参数组合')
print('在函数定义中，可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数'
      '这5种参数都可以组合使用，除了可变参数无法和命名关键字参数混合'
      '但是，参数定义的顺序必须是：必选参数、默认参数、可变参数/命名关键字参数和关键字参数')

print('eg:'
      'def f1 (a, b, c=0, *args, **kw):'
      'print(\'a=\',a, \'b=\', b, \'c=\', c, \'args=\', args, \'kw=\', kw)')
print('eg:'
      'def f2 (a, b, c=0, *, d, **kw):'
      'print(\'a = \', a, \'b = \', b, \'c = \', c, \'d = \', d, \'kw = \', kw)')

print('在函数调用的时候，python解释器自动按照参数位置和参数名把对应的参数传进去')
print('调用上面的函数例子：'
      'f1(1, 2)======打印如下：'
      'a = 1 b = 2 c = 0 args = () kw = {}'
      'f1(1,2,3,\'a\', \'b\')======打印如下'
      'a = 1 b = 2 c = 3 args = (\'a\', \'b\') kw = {}')

print('调用f2的函数：'
      'f2(1, 2, d=99, ext=None)'
      'a = 1 b = 2 c = 0 d = 99 kw = {\'ext\': None}')

print('还可以使用 tuple 和 dict 去调用上面的 f1 和 f2 函数========如下'
      'args = (1, 2, 3, 4)'
      'kw = {\'d\': 99, \'x\': \'#\'}'
      'f1(*args, **kw)============打印如下'
      'a = 1 b = 2 c = 3 args = (4,) kw = {\'d\': 99, \'x\': \'#\'}')
print('调用f2的例子如下：'
      'args = (1, 2, 3)'
      'kw = {\'d\': 88, \'x\': \'#\'}'
      'f2(*args, **kw)'
      'a = 1 b = 2 c = 3 d = 88 kw = {\'x\': \'#\'}')

print('对于任意函数，都可以通过类似的func（*args, **kw）的形式调用它，无论它的参数是如何定义的')


# 小结
print('小结：'
      'python的函数具有非常灵活的参数形态，既可以实现简单的调用，又可以传入非常复杂的参数'
      '默认参数一定要用不可变对象，如果是可变对象，程序运行时会有逻辑错误'
      '要注意定义可变参数和关键字参数的语法'
      '*args时可变参数，args接收的时一个tuple'
      '**kw时关键字参数，kw接收的时一个dict'
      '以及调用函数时如何传入可变参数和关键字参数的语法：'
      '可变参数：既可以直接传入func(1 ,2, 3); '
      '          又可以先组装list或tuple，再通过*args传入：func(*(1, 2, 3))'
      '关键字参数： 既可以直接传入 func(a=1, b=2);'
      '          又可以先组装dict，再通过**kw传入：func（**{\'a\': 1,\'b\': 2}）'
      '-----------------------'
      '使用*args  和 **kw 时python的习惯写法，当然也可以用其他参数名，但最好使用习惯用法'
      '命名的关键字参数是为了限制调用者可以传入的参数名，同时可以提供默认值'
      '----定义命名的关键字参数不要忘了写分隔符 *， 否则定义的将是位置参数')
