Python进阶学习小结-1.md

关于Python进阶

众所周知,Python入门是非常快的。记得我当时就看了一张十分钟入门Python的图,图片地址,再看看别人的代码,就开始尝试写起了Python。无论是普通的Python脚本、爬虫、Python web,以及Python科学计算,我们使用和喜欢使用Python的原因就是因为Python语言简单、清晰,写起来很随意。
但是随着不断深入,我们写Python的时候会不自主的带入一些与整个语言不搭的写法,而Python本身非常优秀的一些用法却被我们忽略。在阅读一些优秀Python开源项目时,往往发现自己写的Python和那些规范的进阶写法的Python差别很大,仿佛不是同一种语言是的。
前几天在公司内部KM上看到一篇Python进阶的翻译文章。单身狗清明节也没啥事情可干,就慢慢阅读,并生成这个笔记,和相关的联系代码(详见我的github)。
全系列文章可能3-4篇,希望大家读完,都可以写出逼格满满的Python代码。
Ps.此处的进阶是相对Python最基本语法而言,如果想要更进一步,利用Python开发类似Openstack这种的话,推荐《Python高手之路》,英文名字《The Hacker‘s Guide To Python》。

本文主要内容

  • *args和**kwargs
  • Debugging部分
  • 生成器部分
  • Map和Filiter部分
  • set部分
  • 装饰器部分

目录基本和那本书的内容一致,但是整合了我对文章阅读的一些理解,配合代码理解,有助于后续代码的学习。

魔法变量:*args和**kwargs

在阅读一些比较大的项目时,会看到很多*args和**kwargs这种魔法变量,如果没有系统学习很容易云里雾里。它们主要将不定量的参数传递给一个函数。*args发送一个非键值对的可变数量的参数列表给一个函数,**kwargs允许你将不定长度的键值对(key,value), 作为参数传递给一个函数。

def test_args_kwargs(arg1, arg2, arg3):
    print "arg1:", arg1
    print "arg2:", arg2
    print "arg3:", arg3


def test_args_1(*argv):
    for arg in argv:
        print "arg from argv", arg


def test_args(f_arg, *argv):
    print "first normal arg:", f_arg
    for arg in argv:
        print "another arg from argv:", arg


def test_kwargs(**kwargs):
    for key, value in kwargs.items():
        print "{0} == {1}".format(key, value)


args = ("two", 3, 5)
test_args_kwargs(*args)
# 输出
# arg1: two
# arg2: 3
# arg3: 5

test_args_1('1','2')
# 输出
# arg from argv 1
# arg from argv 2

test_args('1','2','3')
# 输出
# first normal arg: 1
# another arg from argv: 2
# another arg from argv: 3

kwargs = {"arg3": 3,"arg2": "two"}
test_kwargs(**kwargs)
# 输出
# arg2 == two
# arg3 == 3

如代码所示:*args和 **kwargs是很容易将不定量参数传递给函数的,通过函数等量参数声明或者for循环方式一一读出。此外,如果函数里有 test_args(f_arg, *argv)这种,可以直接额外直接读取出第一个传过去的参数。不过,通过函数等量参数声明的方法感觉不是特别实用,个人感觉也一般场景下应该不会使用。
*args和 **kwargs是下面装饰器的基础,所以需要特别注意。

调试

利用好调试,能大大提高你捕捉代码Bug的。大部分新人忽略了Python debugger(pdb)的重要性。此处建议大家阅读官方文档(其实我自己还没认真阅读完,平时代码量过小,基本靠肉眼debug)。
参考:https://docs.python.org/2/library/pdb.html Or https://docs.python.org/3/library/pdb.html+
一个参考代码

import pdb

def make_break():
    pdb.set_trace()
    return "I don't have time"

print (make_break())

在运行时马上进入debugger模式,接下来就是debugger模式模式下的一系列命令:
c: 继续执行
w: 显示当前正在执行的代码行的上下文信息
a: 打印当前函数的参数列表
s: 执行当前代码行,并停在第一个能停的地方(相当于单步进入)
n: 继续执行到当前函数的下一行,或者当前行直接返回(单步跳过)

由于自己对调试这块用的不多,先跳过不细讲。

生成器 generator

def generator_function():
    for i in range(2):
        yield i

gen = generator_function()
print next(gen)
print next(gen)
# print next(gen)
# yield掉所有value时候,会触发stopIteration

for item in generator_function():
    print item

如上图代码:生成器也是一种迭代,但是你只能迭代一次,它并不会将所有值放入内存中,而是运行时慢慢生成(yield)出来。注意:在不停next的时候,会到达yield所有的值,这时候会触发stopIteration,而采用for循环时不会产生。
这里的例子可能不是很实用,有种多此一举的感觉。其实生成器最佳应用场景:你不想将所有计算出来的大量结果集分配在内存当中,特别是结果集里包含大量循环,因为这样会造成大量的循环。
python2里的一些标准库函数会返回列表,而python3都修改成生成器,因为生成器更节省资源。在生成器对你有意义时候使用它,会非常有效。

map和filter

Map会将一个函数映射到一个输入列表的所有元素上。可以将一个list的参数传到函数里执行,甚至将一个列表的函数一一执行。有种只有想不到没有实现不了的感觉。

# 匿名函数配合map
items = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, items))
print squared

# 一列表的函数。
def multiply(x):
    return (x*x)

def add(x):
    return (x+x)

funcs = [multiply, add]
for i in range(5):
    value = list(map(lambda x: x(i), funcs))
    print value

filter能创建一个列表,其中每个元素都是对一个函数能返回True。

number_list = range(-5, 5)
less_than_zero = list(filter(lambda x: x<0, number_list))
print less_than_zero

集合:set

归纳起来就两点:
- set 集合,与list相似,但是不能包含重复的值
- set 可以比较方便的进行集合运算
利用这个特性,可以实现一些神奇的功能:

set(one_list) #将list中重复的值去掉

duplicates = set([x for x in one_list if one_list.count(x)>1])  #找出list中重复的值

input_set.intersection(valid) #求input_set和valid这个两个set的交集

input_set.difference(valid)  #求input_set和valid的差集

装饰器:decorator

终于到了本片文章最重要,最具Python的部分了。
看过Django和Flask的同学对装饰器肯定不陌生,像在flask中,路由什么的都是通过装饰器来实现的。
其实装饰器自身就是一个函数,它的作用就是改变其他函数的行为。听起来云里雾里,有种嵌入式编程中“中断”的味道。
看如下这个复杂一点的代码(利用生成器实现复杂的日志功能):

from functools import wraps


def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__+" was called"
            print log_string
            with open(logfile,'a') as opened_file:
                opened_file.write(log_string+'\n')
        return wrapped_function
    return logging_decorator

@logit()
def myfunc1():
    pass

myfunc1()

@logit(logfile='out2.log')
def myfunc2():
    pass

myfunc2()

在Python中所有的一切都是对象,这一点在此不细说,通过对象的思维才能理解装饰器。上面代码中,定义logit这个装饰器,设置wraps时的场景,以及修改后的行为。通过在函数上方@logit()标识,可以实现对日志内容区分和日志文件名的区分。
不过上述代码有个小问题,就是如何写日志的同时确保函数内容实现?应该修改后可以实现。
ps.这个问题已经解决,只需要在@wraps(func)前运行func()就能实现相关。

当然,可以把装饰器写成装饰器类的形式,这样形式更简洁,更清晰。


未完待续

你可能感兴趣的:(Python相关)