美文网首页Python回忆录
基础篇: 7. Python基础语句与函数

基础篇: 7. Python基础语句与函数

作者: 后视镜 | 来源:发表于2019-11-04 09:09 被阅读0次

    Python语句关键词都比较简单,但又有很多灵活的使用方式。

    1. 缩进和文件头

    1.1. 缩进

    说语句之前,先谈谈之前一直没有谈过的Python文件的缩进,Python文件不是以花括号{}来分割代码块,而是以缩进(英文是indent)来分割,一般都是推荐4个空格。函数体和语句块都会用缩进4个空格来表示,但除了空格还可以用tab来缩进,但这两种缩进后又无法用肉眼可以看到,但如果出现混用就很容易报错和混乱。在缩进这个问题上,我的建议是以下:

    1. 尽量全部用4个空格
    2. 需要修改别人编写的Python文件,其中缩进是以tab的,建议也遵循原来的tab,或者全部转换成空格。但切记不要混用,在sublime text可以选定这些空白字符,点就是空格,横线就是tab,可以统一转换。
    3. 没说空格就比tab高人一等,也没有说空格就低人一等。我觉得统一是减少争执和问题的有效办法。

    1.2. 文件头

    文件头之前简略提过,现在来详细说说,先把统一的文件头放出来,以后无脑用它就好。

    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    
    • 指定了使用linux环境变量$PATH里某个路径下名为python的解释器来打开.py文件

    •   echo $PATH
      
    • 告诉Python解释器,这个文件是以utf-8的格式保存的。这个有什么意义呢?Python默认是以ASCII来打开文件,但中文字却是不在ASCII字符编码的范围内,这样打开的话会无法解析中文字,如下例:

    •   print('我')
        # 运行后就会报错
        SyntaxError: Non-ASCII character '\xe6' in file noheader.py on line 1,
      

    1.3. 小技巧

    每次创建python文件的时候可以自动生成这些文件头,免得每次都要复制粘贴。而sublime可以使用下面一个snippet:

    <snippet>
        <content><![CDATA[
    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    
    # vim: et sw=4 ts=4
    ]]></content>
        <!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
        <tabTrigger>code</tabTrigger>
        <!-- Optional: Set a scope to limit where the snippet will trigger -->
        <scope>source.python</scope>
    </snippet>
    

    创建文件后输入code然后按tab就会自动转换为文件头具体内容了,记得是py结尾的文件才能触发。上面的例子还可以举一反三,在平时十分常用的代码块可以保存下来,那样每次不用重新手动码字。

    2. 语句

    2.1. if-else语句

    if-else语句是python最主要的条件判断语句,没有switch-case,所以一旦碰到需要条件判断的情况,就上if-else就好。例子如下:

    name = "Tom"
    if "CAT" == name:
        print("name is CAT")
    elif "MARY" == name:
        print("name is MARY")
    elif "TOM" ==  name :
        print("name is TOM")
    else:
        print("default")
    
    • if后面冒号前是测试条件,如果是True,就会执行冒号后面的语句。
    • 建议这种常量测试条件放在前面,这样更方便地看到每个条件的要求。
    • else建议是必备,因为这样避免缩进错误了,导致执行错了代码。有些资深的开发因为没写一个默认的else,常常因为某些异常摸不着头脑。如果不需要任何语句逻辑,可以写pass表示空白省略,也不会导致报错

    这里特别提一提一行的if-else语句,先举例子:

    flag = 1
    name = "Tom" if flag else "MARY"
    

    非常类似其他语言的三元操作符name = flag?"Tome":"MARY",如果flag是真,就赋值"Tom"给name,否则就赋值给"MARY"。这个特别简洁易看,但如果flag过于复杂,还是最好用完整的if语句块,这样看起来更清晰。要记着代码是写给人看的,而不是写给机器看的,能维护易改才是真正的好代码。

    2.2. while语句

    循环语句,格式如下:

    
    i = 1
    while i <= 100:
        if i == 10:
            break
        print("i = {0}".format(i))
        i += 1
    
    • while和冒号之间是测试条件,是真的话会一直执行下面的循环体。

    • 但i=10的时候,会执行break语句,跳出循环。所以用while循环的时候,必须要考虑死循环的问题。

    • 记得要自增i,经常出现没有if条件的判断,只有while语句的判断,但忘记自增i,出现多次执行。

    • 如果while循环里面写了很多重要逻辑,建议可以增加一些调试语句,在生产环境执行的时候先执行一次或者数次,避免一次过完全执行造成大锅。可以先看执行一次的结果是否如你所愿,我就是这样试过避免了很多次内外网不一致导致的问题。

    • 其实while还有个else语句,例子如下:

    • while 0:
          print("0")
      else:
          print("else")
      

      这种用法我很多同事都几乎看不懂,而且非常容易出错。我个人认为一门语言,吸收其最好的80%精华已经很不错了,这种特殊罕见的例子用在生产上会让很多人迷惘,一旦迷惘就会出现生产事故。我觉得可以忽略不用,在规则下面怎么方便就怎么来。

    2.3. for语句

    for循环语句,与while不一样的,for循环比较难出现死循环,因为一般在for循环里面都会定下终止条件和达到终止条件的逻辑操作。同样for也有else语句,我个人认为还是与上面一样,太少用了。先看例子:

    for i in xrange(100):
        print(i)
    

    对比两个语句的差别,for一般用于遍历可迭代对象,最常用的就是list了而while要指定终止条件,然后在循环体里面逐步达到终止条件。对比最明显的就是for循环不容易造成死循环。后面会说到的高深魔法,生成器就一般使用for循环比较多。当然了,还是养成个人编程习惯,习惯用哪个熟悉就多使用,这样编写的代码你会更有信心,有自己的节奏。

    3.函数

    3.1. 基本介绍

    函数是以def 函数名(参数, 默认参数=默认值,*可变参数,**关键词参数):这样的定义,python函数真的非常灵活,因为没有强制类型,所以其灵活性是他一大优势,当然出错率也肯定高,但我觉得出错这个东西只要维持一定习惯,可以把出错尽量减少,就是熟悉就会减少出错。例子:

    def test(name, default=2, *args, **kwargs):
        print(name)
        print(default)
        print(args)
        print(kwargs)
    
    • name是指定参数
    • default是带有默认值的参数,注意这里有个大坑,后面详细说说。
    • *args表示多个可变参数,星号表示解包操作。
    • **args表示多个可变关键词参数,两个星号同样是解包操作。

    3.2. 解包星号

    传入函数的时候,用一个星号的话,表示对后续的参数进行解包操作,列表解包变成一个一个值,我们可以调用看一看

    # 对列表或者元组进行解包
    a= range(10)
    test(*a)
    0
    1
    (2, 3, 4, 5, 6, 7, 8, 9)
    {}
    

    可以看到是按顺序传入对应的参数。

    • name赋值为0
    • default赋值为1
    • args赋值为元组(2, 3, 4, 5, 6, 7, 8, 9
    • kwargs赋值为空字典

    假如我们解包一个字典呢:

    b = {"a":1, "b":2, "c":3, "d":4}
    test(*b)
    a
    c
    ('b', 'd')
    {}
    # = test(*b.keys())
    

    可以看到其实就是把字典的keys()传进去而已。

    双星号按上面规律就知道是用来专门解包字典的,把字典解包成key=value的方式,直接看例子:

    b = {"a":1, "b":2, "c":3, "d":4}
    test("Tom", **b)
    "Tom"
    2
    ()
    {'a': 1, 'c': 3, 'b': 2, 'd': 4}
    # = test("Tom", a=1, b=2, c=3, d=4)
    
    • name赋值为Tom
    • default用默认值2
    • args赋值为()空元组
    • kwargs赋值为{'a': 1, 'c': 3, 'b': 2, 'd': 4}
    • 可以看到最后给出另一个方式,解包可以认为是这样的赋值方式,更直观,用在format里面也很方便。

    3.3. 如何传参

    默认值传参和可变参数混在一起通常比较混乱,首先args是可以传入可变参数,但用上解包和默认值传参的是,要注意args是按位置赋值,例子如下:

    def test1(a, b, c, d=1, *args, **kwargs):
        print a,b,c,d,args,kwargs
    test1(1,*[2,3,4])
    1 2 3 4 () {}
    

    如果先赋值默认值d呢:

    test1(d=10,*[2,3,4])
    2 3 4 10 () {}
    

    如果数量不够就会报错,下面例子只传了两个参数,c是必须要传参的

    test1(*[2,3])
    TypeError: test1() takes at least 3 arguments (2 given)
    

    传参数量多了,明显可以看出,是按照位置赋值的,所以如果默认参数如果赋值了,那就不要再传了

    # 不传默认参数
    test1(*[1,2,3,4,5])
    # 传默认参数,
    test1(d=3, *[1,2,3,4,5])
    TypeError: test1() got multiple values for keyword argument 'd'
    # 这样还是会报错
    test1(1, 2, 3, d=3, *[1,2,3,4,5])
    TypeError: test1() got multiple values for keyword argument 'd'
    # 不明确指出变量名来赋值就正确了
    test1(1, 2, 3, 3, *[1,2,3,4,5])
    1 2 3 3 (1, 2, 3, 4, 5) {}
    # 数量缺少
    test1(d=3, *[1,2,3])
    1 2 3 3 () {}
    

    总结:

    1. 对于必传参数,最好还是明确一个一个按位置传进去。如果确实需要添加可变参数args,那就在args前面不要指明key=value的方式赋值,否则可能会报错。

    2. key=value赋值后,就不要再kwargs再次赋值,否则也会报多次赋值异常

      test1(1,2,3,d=4,**{"d": 4})
      

    3.4. 默认值的坑

    函数默认值传入一个dict和list是很正常的事情,但往往这里会出巨坑,发现下次调用list的时候,list或者dict还保存着上次函数调用的信息,这就非常坑了。从官方文档也可以看到,它特别提醒了,以下是官方文档的一段话:

    Default parameter values are evaluated when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call. This is especially important to understand when a default parameter is a mutable object, such as a list or a dictionary: if the function modifies the object (e.g. by appending an item to a list), the default value is in effect modified. This is generally not what was intended. A way around this is to use None as the default, and explicitly test for it in the body of the function
    

    函数在Python的世界里面也是一个函数,所以它定义的时候就会被生成一个对象,同样其中的的默认参数会被生成。如果参数是一个可变对象,例如list或者dict,如果在函数中被修改,默认值也会被修改。我们看看例子

    #! /usr/bin/env python
    # -*- coding: utf-8 -*-
    def test(val, data = []):
        data.append(val)
        return data
    def main():
        for i in xrange(10):
            print(test(i))
    if __name__ == '__main__':
        main()
    

    想象的输出:

    [0]
    [1]
    [2]
    [3]
    [4]
    [5]
    [6]
    [7]
    [8]
    [9]
    

    实际输出如下:

    [0]
    [0, 1]
    [0, 1, 2]
    [0, 1, 2, 3]
    [0, 1, 2, 3, 4]
    [0, 1, 2, 3, 4, 5]
    [0, 1, 2, 3, 4, 5, 6]
    [0, 1, 2, 3, 4, 5, 6, 7]
    [0, 1, 2, 3, 4, 5, 6, 7, 8]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    可以看得出函数的默认值一直引用都是同一个对象,所以传入参数的时候修改的还是同一个对象。所以要避免在默认值定义可变的对象。那一定要传入有个默认值list呢?官方文档给出了个例子:

    def whats_on_the_telly(penguin=None):
        if penguin is None:
            penguin = []
        penguin.append("property of the zoo")
        return penguin
    

    默认值定义为None,如果是None就初始化,这样就不会导致修改的是同一个对象了。还可以用or来简化,我也是从lua那边看到的,表示如果penguin是真值就直接赋值,否则创建一个list对象。

    penguin = penguin or []
    

    3.5. lambda表达式

    lambda表达式定义一个匿名函数,多用于作为参数传入给函数。先看个例子,数组排序的sorted()

    def main():
        datas = {"Tom":10,  "Mary":3, "Faker": 5, "Doinb": 100}
        sorted_list = sorted(datas.items(), key=lambda v: v[1],  reverse=True)
        for name, vote_num in sorted_list:
            print(name, vote_num)
    
    if __name__ == '__main__':
        main()`
    

    datas是保存着主播对应的票数,现在想对他们进行排序后按降序打印出来。下面对上面代码进行解释:

    • datas.items()把字典转化为一个数组,里面元素是一个二元的tuple,如d = ("Tom", 10), d[0]=>Tom, d[1] => 10
    • key=lambda v: v[1]其中key表示传入一个函数,参数v是传入一个个二元数组,然后函数的返回值是v[1]就是对应的票数,连起来看就是每个二元组经过key对应的匿名函数后,返回了票数作为排序字段。

    lambda表达式一般用在这种情况,函数简单易懂,又不会影响调用者查看代码,如果要复杂了,sorted的调用就很容易出错,还不如另外定义一个函数更直观更保险。

    4. 总结

    写完这篇之后,突然感慨Python还是有很多小东西要注意的,回想起那段刚进前公司的时光,留下了都是奋斗的记忆,也很感谢那时候刚起步的部门和新人的我一起成长,能有更多机会去遇坑填坑。

    后视镜 20191104

    相关文章

      网友评论

        本文标题:基础篇: 7. Python基础语句与函数

        本文链接:https://www.haomeiwen.com/subject/pnsrbctx.html