Python学习笔记(3)——函数

Python的类内成员函数是method类型,而普通函数是function类型,精确地来说,类成员函数应该被称为方法
通过以下代码,可以看到它们各自的类型:

class TypeTest:
    def method_test(self):
        pass


print(type(TypeTest().method_test))


def function_test():
    pass


print(type(function_test))

输出结果:

<class 'method'>
<class 'function'>

和很多现代高级语言一样(如JS),Python中的函数也都是对象,每一个函数都是callable types的一个实例,method和function均属于callable types,这里先介绍一下函数,类的方法则改日再讨论。

Function以及函数参数

function通常如此定义:

def foo():
    # do something
    pass

跟其它语言一样,function可以输入参数:

def foo(arg):
    # do something
    pass

你甚至可以指定返回类型:

def foo(arg) -> None:
    # do something
    pass

Python允许你定义两个特殊的参数*args,和**kwargs,当然参数名可以自定义:

def foo(arg, *args, **kwargs) -> None:
    # do something
    pass

其中*args是一个tuple,而**kwargs是一个dict,也就是如果如此调用:

def foo(arg, *args, **kwargs) -> None:
    print(arg)
    print(args)
    print(kwargs)


foo(1, 2, 3, 4, 5, test=6)

那么会输出如下结果:

1
(2, 3, 4, 5)
{'test': 6}

*和**符号本身也可以用来展开tuple和dict:

def foo(arg, *args, **kwargs) -> None:
    print(*args)
    print(*kwargs)


foo(1, 2, 3, 4, 5, test=6, test2=5)

这样的输出结果会是:

2 3 4 5
test test2

对于tuple,*将它展开成了各个元素,而对于dict,*将它的keys展开了,至于**符号则将会把dict展开成如下形式:

test=6 test2=5

这使得*args跟**kwargs在不同函数之间传递成为了可能:

def bar(*args, **kwargs):
    print(args)
    print(kwargs)


def foo(*args, **kwargs):
    bar(*args, **kwargs)

注意,此处bar(arg,kwargs)的使用方法将是错误的,这意味着传递了一个tuple跟一个dict作为两个普通参数,这将导致语法错误。
Python的参数可以使用参数名来指定赋值:

def foo(a, b):
    print('{} {}'.format(a, b))


foo(b=1, a=2)

这将输出2 1,这在使用大量默认参数时非常方便。
可以看到,这种形式跟**kwargs的kv对形式是一样的,为了避免歧义,在**kwargs后面不可以再定义参数,而*args则没有这个问题,所以*args后面是可以定义参数的:

def foo(*args, arg, **kwargs): # 合法
    pass


def bar(*args, **kwargs, arg): # 非法
    pass

当然,如果你使用def foo(*args, arg, **kwargs)的写法,那么你在*args后面的所有参数都将需要使用指定参数名的形式赋值,不然都会默认为*args的元素,所以一般来说,*args跟**kwargs总是在最后定义。

Python函数的属性

如前所述,Python的函数本身就是一个callable类型的对象,是对象,就有属性,函数的内置属性如下:

属性 作用 是否可写
__doc__ 函数文档 可写
__name__ 函数名 可写
__qualname__ 函数全名
3.3以后的新特性
可写
__module__ 函数所在的模块名 可写
__defaults__ 一个元组,包含了所有的默认参数值,如果未指定默认参数,则为None 可写
__code__ 编译过的函数体代码(官方说明,打印这个属性得到的是该函数的具体文件位置和行数) 可写
__globals__ 该函数所在的模块中包含的全部全局变量,dict类型 只读
__dict__ 函数的属性集合 可写
__closure__ 闭包,内函数调用了外函数的变量才有的属性 只读
__annotations__ 注解,这个是你写了才有的 可写
__kwdefaults__ keyword-only的参数(在*args后面定义的参数)的元组 可写

大多数属性是可写的,但是如__name__ __qualname__,重写了它们似乎也没有什么用,__doc____annotations__则可以通过以下形式定义:

def foo(arg: str) -> None:
    """

    :rtype: None
    :param arg:
    """
    print(arg)

当然,对每一个函数写上完备的文档和注解是一种良好的编程习惯。
__defaults____kwdefaults__就比较有意思了,虽然在定义函数时已经指定了默认参数,但是由于__defaults____kwdefaults__是可写的,所以你可以运行时修改它们:

def foo(a: str = 'a', *args, b='b') -> None:
    """

    :rtype: None
    :param a:
    :param args:
    :param b:
    """
    print(a)
    print(b)


foo.__defaults__ = ('A',)
foo.__kwdefaults__ = {'b': 'B'}
foo()

以上代码打印的是大写的A和B。
__closure__则是出现闭包才有的属性:

def foo(arg):
    def bar():
        print(arg)

    print(bar.__closure__)


foo('hello')

如此会打印两个地址:<cell at 0x00000271BDCD6430: str object at 0x00000271BDD9D9F0>
至于其它属性就不做多赘述了。

Python函数装饰器

函数装饰器在函数上面用@定义:

def f1(arg):
    def wrapper():
        print('decorated')
        arg()

    return wrapper


@f1
def foo():
    print('foo')


foo()

如上语法严格等价于:

def f1(arg):
    def wrapper():
        print('decorated')
        arg()

    return wrapper


def foo():
    print('foo')


f1(foo)()

装饰器,顾名思义,对函数进行装饰,所以上面的f1函数可以对传进来的arg函数做出各种修改,甚至可以返回一个与原函数完全无关的callable对象:

def f1(arg):
    def wrapper():
        print('decorated')
        print('f1')

    return wrapper


@f1
def foo():
    print('foo')

foo()

这样的输出结果会是:

decorated
f1

此时,任何使用f1进行装饰的函数都将输出一样的结果,与原函数无关。
甚至返回一些别的类型的对象也是可以的:

def f1(arg):
    return 'decorated foo'


@f1
def foo():
    pass


print(foo)

此时,foo将是一个str类型的对象,值为decorated foo
装饰器也可以有多个,还可以含参:

def f1(arg):
    def wrapper(func_arg):
        def inner_wrapper():
            print('f1 decorated')
            func_arg(arg)

        return inner_wrapper

    return wrapper


def f2(func):
    def wrapper(arg):
        print('f2 decorated')
        func(arg)

    return wrapper


# 以下写法严格等价于 foo = f1('foo')(f2(foo))
@f1('foo')
@f2
def foo(arg):
    print(arg)


foo()

学过递归的同学一定很熟悉了,这也是某种形式的套娃,基础薄弱的同学可能会觉得有点烧脑:第一行的装饰器返回一个callable对象并以第二行的装饰器返回的对象作为参数,以此类推。可以看到这里的上层装饰器f1是可以不含参的,而它的返回值必须接收一个至少含一个参数的callable类型,而这个参数既是f2(foo)所返回的值。
那么敏锐的同学一定发现了,这里f2的返回值它的类型不一定必须是callable的,也就是f2(foo)的返回值可以是任何类型:

def f1(arg):
    def wrapper(func_arg):
        def inner_wrapper():
            print('f1 decorated')
            print(func_arg)

        return inner_wrapper

    return wrapper


def f2(func):
    return 'f2 decorator'


# 以下写法严格等价于 foo = f1('foo')(f2(foo))
@f1('foo')
@f2
def foo(arg):
    print(arg)


foo()

以上写法也是合法的,只是没有什么实际价值。
装饰器可以应用在许多场合,可以说是一种高级的多态,它可以将类似的函数中重叠的部分抽取出来做成装饰器,剩下的有区别的部分再分别实现,典型的应用有:插入日志、性能测试、事务处理、缓存、权限校验等场景,这里我尝试写了一个简陋的日志插入装饰器:

INFO = 0
DEBUG = 1

GLOBAL_LOG_LEVEL = DEBUG


def log_level(level):
    def wrapper(logger):
        def inner_wrapper(logs):
            if level <= GLOBAL_LOG_LEVEL:
                print(logs)
                logger()

        return inner_wrapper

    return wrapper


@log_level(level=DEBUG)
def debug_logger():
    print('this is a debug level log')


@log_level(level=INFO)
def info_logger():
    print('this is an info level log')


info_logger('info')
debug_logger('debug')

GLOBAL_LOG_LEVEL设置为INFO,则仅执行info_logger,设置为DEBUG,则info_loggerdebug_logger都会执行。
注意这里的装饰器@log_level(level=INFO)严格等价于log_level(level=INFO)(info_logger),也就是跟以下写法完全等同:

def info_logger():
    print('this is an info level log')


info_logger = log_level(level=INFO)(info_logger)

展开来看的话,是不是更好理解了?log_level(level=INFO)的返回值wrapper是一个callable对象,接收一个info_loggercallable对象作为参数,并返回了一个inner_wrappercallable对象,所以,当你执行info_logger('info')时,事实上是执行了inner_wrapper并且其参数loginfo_logger的参数,而内部变量level则是外部的装饰器log_level的参数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容