0%

Python基础补习

这应该是一篇会长期更新的帖子…是时候补补Python基础语法了

开帖动机

  • 从来没有把Python当一门语言去系统学习
  • 经常被他人写的Python代码的精炼而震撼
  • Python语法比较散,需要一个地方集中整理备忘
  • 其实是课程《Python计算》的学习感悟啦>_<

Wish List

  • The Zen of Python:内存管理、语法规范
  • 基本语法:常用方法、IO、异常处理
  • 函数:type hint、lambda、函数调用、面向对象
  • 字符串:切片、re、格式化字符串
  • 数据结构:推导式、list、dict、tuple、set、链表
  • 标准库与常用第三方库:numpy等
  • 扩充中:装饰器等

期待自己能对Python有更进一层的基本理解

0. The Zen of Python

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better then dense.
Readability counts.

Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.

In the face of ambiguity, refuse the temptation to guess.
There should be one - and preferably only one - obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.

Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let’s do more of those!

内存管理

Python内存管理

  • 在Python中,即使是简单的赋值语句a=1,也只是将变量标识符a引用向对象1
  • 基于值的内存管理:小整数、短字符串等对象都被Python缓存,多个变量值相等时,可能会有相同的id
  • a=b中,建立的是ab的引用。并且Python似乎不支持=的重载
  • 使用a is b来判断a和b是否引用同一对象,a is bid(a)==id(b)的充分非必要条件
  • 使用del(a)主动释放一个变量,但如果对象还被引用不会导致对象被消除
  • 一般情况下,引用关系构成一张DAG图,使用getrefcount(a)得到a的引用计数
  • 形如a.append(a)会造成引用环,对垃圾回收会造成一定困难

Python垃圾回收

  • gc.get_threshold()返回三值元组:启动垃圾回收的阀值;0、1代回收频率比值;1、2代回收频率比值
  • 分代回收:新创建的变量会进入0代,每次回收必备检查。若不被回收则逐渐进入1、2代,减少检查比例

1. 基本语法

常用函数

  • reduce(func, iterable):对一个可迭代对象进行累func操作(两两进行)

  • filter(func, iterable):将符合func筛选条件的对象作为迭代器返回

  • yield x:创建生成器(惰性求值)

    1
    def test():
    2
        a = 0
    3
        while True:
    4
            yield a
    5
            a += 1
    6
    7
    a = test()
    8
    for i in range(5):
    9
        print(a.__next__())
    10
    for i in test():
    11
        if i >= 5:
    12
            break
    13
        print(i)
  • map(func, iterable):对一个可迭代对象的每一个元素做func操作,返回一个map对象

    • 例如list(map(int, '123 456'.split()))返回[123, 456]
    • C++的std::unordered_map对应的是Python的dict数据结构
  • reversed(iterable):对可迭代对象生成一个反转的迭代器

  • sorted(iterable, reversed=False, key=None):对可迭代对象排序,返回一个新生成列表

    • 关键参数key:用于比较关键字,如key=lambda x:x%2
    • 关键参数reverse:用于表示是否反过来排序
  • enumerate(iterable, start=0):返回下标和可迭代对象的关系

    1
    a = ['hello', 'world', 'test', 'python']
    2
    for i, x in enumerate(a, start=1):
    3
        print(f"{i} => {x}")
    4
    5
    # 1 => hello
    6
    # 2 => world
    7
    # 3 => test
    8
    # 4 => python

类的方法与运算符

  • __new__:构造函数
  • __init__:初始化函数
  • __del__:析构函数
  • __add____sub__:加减运算符
  • __str____repr__:转字符串,后者将不可见字符转译
  • __call__:被函数调用
  • __len__:字面意思
  • __bool__:字面意思
  • __lt__ __gt____le____ge____eq____ne__< > <= >= == !=运算符重载
  • __contians__in,返回布尔值
  • __getitem__:下标访问或迭代器访问
  • __setitem__:下标修改或迭代器修改
  • __dict__:使用一个字典对对象中成员的值进行修改,一般不需要重载

2. 函数

Type hint

众所周知,Python属于强类型编程语言,因此每个变量在它的生命周期结束前始终只有一种类型。

然而,Python也是动态类型,一个标识符可能有多段生命周期且不同生命周期有着不同类型。

具体可以参考这里

并且,Python的变量类型是隐式的,这也意味着在函数传参的时候可能需要对变量类型进行一定约定,以避免意外情况发生。

1
def func(a, b):
2
    print(a + b)
3
4
func(1, 2) # 3
5
func('1', '2') # 12
6
func(1, '2') # unsupported operand type(s) for +: 'int' and 'str'

Python提供了Type hint,可以帮助程序员和IDE了解变量的类型,语法如下。

1
def func(a: int, b: int) -> int:
2
    return a + b

可惜的是,Type hint似乎没有强制执行力。(比如上述函数依然可以正常执行func('1', '2')得到'12'

如果要保证数据类型出错就报错,则可能需要用assert断言或isinstance函数。

函数调用

func(*args):在函数定义时将参数当做tuple或在函数调用时将tuple,list等当做参数处理

  • 函数定义时:将参数聚合

    1
    def func(*args):
    2
        for elem in args:
    3
            print(elem, end=' ')
    4
        print()
    5
    6
    func(('a','b','d','c'))  # ('a', 'b', 'd', 'c')
    7
    func('a','b','d','c')    # a b d c
  • 函数调用时:将list,tuple拆开作为参数传递

    1
    def func(x, s, y):
    2
        print(x+1, s, y-1)
    3
    4
    st=[122,'456']
    5
    func(*st, 790)  # 123 456 789
  • 作为切片使用:

    1
    first,*rest=[1,2,3,4]
    2
    print(first, rest) # 1 [2,3,4]

func(**kwargs):在函数定义时将参数当做字典或在函数调用时将字典当做参数处理

  • 函数定义时:将参数聚合

    1
    def func(argc, *args, **kwargs):
    2
        print('argc =', argc) # argc = 5
    3
        print('args =', args) # args = (6, 7)
    4
        print('kwargs =', kwargs) # kwargs = {'a': 1, 'b': 2, 'c': 3}
    5
    6
    func(5, 6, 7, a=1, b=2, c=3)
  • 函数调用时:将字典拆开作为参数传递

    1
    dic={'name':'Lemon','depth':'CS'}
    2
    print("my name is {name}, I am studying in {depth}.".format(**dic))
    3
    # my name is Lemon, I am studying in CS.
    1
    def func(x, y, z):
    2
        print(x, y, z)
    3
    4
    a = {'x':1, 'z':2, 'y':3}
    5
    func(*a)  # x z y
    6
    func(*a.values())  # 1 2 3
    7
    func(**a)  # 1 3 2

    其中前两个操作可能是危险的,Python字典对顺序是否维护与版本有关

Python传参的问题

Python对变量和值的管理有独特之处,所以也造成了Python对传参引用问题的特殊性。事实上,python不允许程序员选择采用传值还是传引用。Python参数传递采用的肯定是“传对象引用”的方式,相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典或者列表)的引用,就可能能修改对象的原始值,相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就一定不可能修改原始对象,相当于通过传值来传递对象。

  • 例1:不可变对象函数传递,不可能改变外部值

    1
    def func1(a: int):
    2
        print(f"a={a}, id(a)={id(a)}")
    3
        a += 1  # a = a + 1,此时原有a的生命周期结束,id改变
    4
        print(f"a={a}, id(a)={id(a)}")
    5
    6
    a = 1
    7
    print(f"a={a}, id(a)={id(a)}")
    8
    func1(a)
    9
    print(f"a={a}, id(a)={id(a)}")
    10
    11
    # a=1, id(a)=140735024623872
    12
    # a=1, id(a)=140735024623872
    13
    # a=2, id(a)=140735024623904
    14
    # a=1, id(a)=140735024623872
  • 例2:可变对象函数传递,可能不改变外部值

    1
    def func2(a: list):
    2
        print(f"a={a}, id(a)={id(a)}")
    3
        a = [3, 2, 1]  # 此时原有a的生命周期结束
    4
        print(f"a={a}, id(a)={id(a)}")
    5
    6
    a = [1, 2, 3]
    7
    print(f"a={a}, id(a)={id(a)}")
    8
    func2(a)
    9
    print(f"a={a}, id(a)={id(a)}")
    10
    11
    # a=[1, 2, 3], id(a)=1804632609352
    12
    # a=[1, 2, 3], id(a)=1804632609352
    13
    # a=[3, 2, 1], id(a)=1804632609864
    14
    # a=[1, 2, 3], id(a)=1804632609352
  • 例3:可变对象函数传递,可能改变外部值

    1
    def func3(a: list):
    2
        print(f"a={a}, id(a)={id(a)}")
    3
        a[0] = 3  # 可变对象修改,id不变
    4
        print(f"a={a}, id(a)={id(a)}")
    5
    6
    a = [1, 2, 3]
    7
    print(f"a={a}, id(a)={id(a)}")
    8
    func3(a)
    9
    print(f"a={a}, id(a)={id(a)}")
    10
    11
    # a=[1, 2, 3], id(a)=1651703566920
    12
    # a=[1, 2, 3], id(a)=1651703566920
    13
    # a=[3, 2, 3], id(a)=1651703566920
    14
    # a=[3, 2, 3], id(a)=1651703566920

lambda表达式

  • 格式:lambda arg1, arg2: expression(arg1,arg2)
  • Python的lambda只允许冒号后有一个表达式,但表达式可以调用函数
1
def func(n):
2
    return lambda x: x * 10 + n
3
4
for i in range(9):
5
    print(func(i)(i + 1), end=' ')
6
# 10 21 32 43 54 65 76 87 98

3. 字符串

在Python中,字符串是不可变对象。在使用方法对字符串进行更新时,往往需要新建对象。

常用方法

  • format:格式化字符串

    • 基本用法:"{}{}abc".format(123,'456') —> 123456abc

    • 使用下标:"{1}{0}{1}".format('o','l') —> lol

    • 使用list或tuple:"{0[1]}{0[0]}{1}".format([1,2],3) —> 213

    • 使用dict:"{a}{b}{c}".format(**{'a':1,'b':234},c='hello') —> 1234hello

    • 格式转换:用于整数和浮点数

      • {:.3f}:浮点数四舍五入保留三位小数
      • {:10}:右对齐宽度10
      • {:<10}:左对齐宽度10
    • {:^10}:居中宽度10

      • {:x>10}:整数补前导x至宽度10
      • {:x<10}:补后缀x至宽度10
      • {:b}二进制{:d}十进制{:o}八进制{:x}十六进制
    • 如果只是想要输出变量,这里有一种非常好用的代替format的输出方式:

      1
      x, y = 3, 6
      2
      print(f"the position is [{x}, {y}]")
  • findrfindindexrindexcountin(str,begin=0,end=len())字符串查找

    • findrfind分别正序和逆序查找第一次匹配的下标,没有返回-1
    • indexrindex分别正序和逆序查找第一次匹配的下标,没有报错
    • count返回出现次数,不允许出现公用部分
    • 当不需要知道下标时,可以用in来代替find
  • splitjoin:字符串的分割和聚合
    • split:参数可以是None或者字符串,按空白字符或字符串分割成list
    • join:list按调用者聚合 ",".join(['1','2']) —> 1,2
  • loweruppercapitalizetitleswapcase:各种大小写转化,字面意思
  • replace:返回替换后的字符串
  • striplstriprstrip:删除(或删除左右侧)的指定字符,默认空白字符

    • 例如:"asasaslasasasa".lstrip('as')—> lasasasa
  • eval:尝试将字符串转表达式求值
  • startswithendwith:返回bool值,是否匹配前后缀
  • isalnumisalphaisdigitisspaceisupperislower:字面意思
  • centerljustrjust(10,'=')中左右对齐10个字符,填充=

切片

  • 格式str[start: end: step]
  • step默认为1,且不能为0
  • step为正时,start默认为0end默认为len(str)
  • step为负时,start默认为-1end默认为-len(str)-1
  • startend的取值范围-len(str)-1<=x<=len(str)
    • x为正表示第x个字符(从零计数)
    • x为负表示倒数第x个字符(从一计数)
  • 切片会对不合法的startend进行保护
    • 死循环返回空串
    • 越界则自动采用左右边界

list和str的切片方式类似

4. 数据结构

列表list

  • extend(list):一次性append多个元素
  • index(x):找到x首次出现的位置,没有抛出异常
  • sort()reverse():原地操作,返回值为None
  • enumerate(['a', 'b', 'c']):转list得到[(0, 'a'), (1, 'b'), (2, 'c')]
  • zip([1,2,3], [4,5,6], [7,8,9]):转list得到[(1,4,7), (2,5,8), (3,6,9)]
  • 列表推导式[x for x in range(5)]:得到[0,1,2,3,4]

元组tuple

  • 元组中的静态对象不可改变,但可变序列依然可以改变

    1
    a = ([1], 2, 3)
    2
    a[0][0] = -1 # ([-1], 2, 3)
    3
    a[0] = [-2] # 'tuple' object does not support item assignment
  • 元组解包:a, b = 1, 2本质上是(a, b) = (1, 2)

  • 用于函数return多个元素return a, b

  • 生成器推导式:(x for x in range(5))得到的生成器需要用.next()访问

  • 序列解包:*(1, 2, 3)*range(5)

  • tuple有自己的运算符,可以用作多关键字排序

集合set

  • set本质是一个无序可变集
  • .add(x):添加元素
  • .remove(x):删除元素
  • 集合运算:
    • .union():并集
    • .intersection():交集
    • .difference():差集
    • .issubset():判断是否是子集
  • list快速去重:list(set(lst)),注意会打乱顺序

字典dict

  • dict本质是一个无序可变集

  • 初始化方法:

    • dict(zip(keys, vals)):按键值对创建
    • dict.fromkeys(keys):按键创建,值为None
  • 添加和修改:

    • dict[key]=value:如key已经存在,替换val
    • dict.update(dic2):如key已经存在,则采用dic2
  • 访问:

    • dict[key]:如键不存在会报错

    • dict.get(key, default):如键不存在则为default值

      1
      dic = dict()
      2
      for x in [1, 3, 1, 2, 0, 100, 3]:
      3
          dic[x] = dic.get(x, 0) + 1
      4
      print(dic) 
      5
      # {1: 2, 3: 2, 2: 1, 0: 1, 100: 1}
  • globals()locals()返回全局变量和局部变量字典

5. 标准库与常用第三方库

random

  • from random import *
  • randint(0, 10):从0~10中随机抽取整数
  • randrange(10):生成0~9的随机数
  • choice(list):从list中随机选择一个

copy

  • from copy import *

  • copy(a):对a进行浅复制

    1
    a = [1,2,3,[1,2,3]]
    2
    b = copy(a)
    3
    a[0], a[3][0] = 2, 2
    4
    print(a, b)
    5
    # a: [2, 2, 3, [2, 2, 3]]
    6
    # b: [1, 2, 3, [2, 2, 3]]
  • deepcopy(a):对a进行深度复制(递归构造)

    1
    a = [1,2,3,[1,2,3]]
    2
    b = deepcopy(a)
    3
    a[0], a[3][0] = 2, 2
    4
    print(a, b)
    5
    # a: [2, 2, 3, [2, 2, 3]]
    6
    # b: [1, 2, 3, [1, 2, 3]]