Python——编程笔记-各种易忘点总结

Python编程笔记,各种易忘点总结
持续更新


快速排序和归并排序参数不可使用 list 子列表

  • 注意使用子列表时是一个新对象,操作子列表与原始 list 无关
  • 在快速排序和归并排序中不可将子列表传入, 以期待可以从函数中修改原始列表的值

list 初始化

  • list 初始化的多种方式
    1
    2
    l = [0] * 10
    l = [0 for _ in range(10)]

list.count 函数的应用

  • list.count 可以统计某个元素出现的次数
    1
    2
    3
    4
    l = [1,  3,  2,  3,  3,  3]
    print l.count(3)
    # output:
    4

str 是一个不可变对象

  • Python 中的 str 是不可变对象
    1
    2
    3
    4
    5
    6
    s = "12345"
    # OK
    print s[1]
    print s[2:4]
    # Error
    s[1] = 10

Random 的应用

  • 输出一个 [start, end] 之间(包括 start 和 end)的随机数
    1
    2
    import random
    print random.randint(start, end)

sorted 函数不修改原始数组

  • sorted 函数不修改原始数组
  • a.sort() 会修改原始数组
    1
    2
    3
    4
    5
    6
    7
    8
    l = [1, 3, 4, 2]
    l1 = sorted(l)
    print l, l1
    l.sort()
    print l
    # output
    [1, 3, 4, 2] [1, 2, 3, 4]
    [1, 2, 3, 4]

sorted 参数 cmp 和 key 比较

  • key是个单参数函数, 返回值为一个可用于比较的值即可
  • cmp是个双参数函数, 返回值为-1, 1, 0, 分别表示小于, 大于, 等于
    • 特别注意不是返回 True 和 False
  • 二者均可作为排序的比较函数

Python 数值类型自动转换

  • 强制类型转换: int(a)
  • 隐式转换
    • boolean 型转 int 型: True =1 False =0
    • 自动类型提升: int 型转 float
    • 注意: 两个 int 型的除法不会保留小数 ,这点与 C++ 一致

关于 bool

  • if 判断语句中,实际上时调用 bool(object)
  • bool(object) 调用的时 object.__bool__()
  • 如果一个对象没有实现 __bool__ 方法,那么会尝试调用 __len__ 方法
    • 返回为 0 时表示 False
    • 否则返回 True

对象 ID

  • Python 中对象的 ID 类似于其他语言中对象的地址
  • 调用方法为
    1
    2
    id(object)
    # Output: 4332312578

运算符号的内部实现

  • + 和 +=
    • + : __add__()
    • += : __iadd__()
      • 当没有 __iadd__() 时 Python 解释器会调用 __add__()
  • * 和 *=
    • * : __mul__()
    • *= : __imul__()
      • 当没有 __imul__() 时 Python 解释器会调用和 __mul__()
  • 不可变变量,比如 tuple也 可以调用 *=+=,表现也是一样的,只是对象id会改变,等价于调用了 __add__() 然后又赋值给当前变量
    1
    2
    3
    4
    tu1 = (1, 2, 3)
    tu2 = (2, 3, 4)
    tu1 += tu2 # <==> tu1 = tu1 + tu2, id of tu1 will change
    # Output: (1, 2, 3, 2, 3, 4)

尽量避免使用最小整数

  • 需要初始化一个最小值, 然后方便求得某个序列的最大值, 此时可以初始化为某个可能的值, 从而避免寻找最小整数的尴尬, 可能会找错, 初始化错的话很容易造成后面结果都错

参数”key

  • 一些需要比较功能的函数都会有此参数
  • key参数是一个函数,这个函数接受一个唯一的对象,然后返回用于比较的值,外层函数比较时会使用key函数返回值进行比较
    • 比如可用与字符窜长度 key = len 排序,忽略大小写排序 key = str.lower 比较等功能
  • 可用于 list.sort(), sorted(), min(), max() 等函数
  • 另外一些其他标准库也会接受这个参数,用法相似

Foreach 局部变量

  • Python for 循环语句中的“局部”变量与Java中的不同

    1
    2
    3
    # Python
    for i in range(0, 10):
    pass
    1
    2
    // Java
    for(int i=1; i < 10; i++)
  • 上面的代码执行完之后i的值为多少?

    • Java 中 i 是局部变量,所以在代码执行完成后变量 i 是不能访问的
    • Python 中 i 是全局变量,所以i的值为最后一次迭代的值 9
    • Java 中要实现与 Python 相同的效果,可以使用全局变量(将 i 的定义放到 for 循环外面即可)

函数内部定义函数时注意

  • 注意内部函数是否访问到 Inner 外的变量
  • 如果某个函数 Otter 只被访问一次且另一个函数 Inner 只被 Otter 访问,那么 Inner 一般定义在 Otter 内部比较合适

正则表达式匹配完整字符串

  • 必须使用 ^$, 否则部分匹配也会返回结果
    1
    2
    3
    4
    5
    import re
    def totally_match(pattern, string):
    if re.match(pattern, string) is not None:
    return True
    totally_match(r"^cat$", "cat")

Python 可以函数定义后再定义全局变量

  • Python 支持先定义函数,再定义全局变量

    1
    2
    3
    4
    def visit():
    print(global_variable)
    global_variable = "testing"
    visit()
    • 因为 Python 是解释器

Python 无穷大的数

常用的是无穷大的实数:

  • 正无穷: float('inf')
  • 负无穷: float('-inf')

运算:

  • Python 里面的无穷大与 C++ 不同, C++ 里面是定义一个最大的整数实现, Python 里面可以视为一个无穷大的对象

  • 和数学分析里面一样, 我们可以和无穷大做计算, 加上无穷大还是无穷大

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    x = float('inf')
    print x
    print x - 1
    print x + 1
    print x + x
    print x - x

    # output
    inf
    inf
    inf
    inf
    nan
    • 无穷大减去无穷大为一个未知结果 nan
    • 判断一个数是否为 nan, nan == nan 返回 False

复制一个普通列表时不要用 copy 模块

  • copy.deepcopy 支持对可变对象的深度复制, 直到解析到不可变对象为止

    1
    2
    3
    import copy
    list1 = [1, 3, 4, [5, 6]]
    list2 = copy.deepcopy(list1)
    • 如果list对象元素都是不可变对象, 那么可以有简便实现
      1
      2
      list1 = [1, 3, 4, 5, 6]
      list2 = list1[:]
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import copy
    list1 = [1, 3, 4, [5, 6]]
    l1 = list1[:]
    l2 = copy.deepcopy(list1)
    l3 = copy.copy(list1)
    l4 = list1
    list1.append(10)
    list1[3].append(7)
    print "l[:]", l1
    print "deepcopy", l2
    print "copy", l3
    print "l", l4

    # output
    l[:] [1, 3, 4, [5, 6, 7]]
    deepcopy [1, 3, 4, [5, 6]]
    copy [1, 3, 4, [5, 6, 7]]
    l [1, 3, 4, [5, 6, 7], 10]
  • 一次其他同学排查很久的 Bug:

    • 在多线程并发时,将同一个 List[Dict] 对象直接分别放到不同任务中做不同的处理(并发处理),任务 B 会朝对象中添加字符串,然后有趣的事情发生了
      • 任务 B 总是对的,任务 A 偶尔出现错误,且不可复现
      • 离线两个任务分别测试,都是对的(即关闭任务 B 后,任务 A 总是对的)
    • 这位同学排查了很久,束手无策,最后其他同学 Review 代码找到是编程习惯不好(没有对可变对象做深拷贝)造成的,因为两边用的是用同一个对象,而这位同学不知道

not " " 返回的是 False

  • 在编程时容易错误的以为空白就是没有, 所以容易认为 not " "True
    • " " 不是什么都没有, 而是有个 space 字符
  • 实际上只有空字符串, 空列表和 None 等是空的, not None, not [], not ''等均为True
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    print not " "
    print not ""
    print not []
    print not None
    print not 0
    print not -1

    # output:
    False
    True
    True
    True
    True
    False

Python中32位最小和最大整形数

1
2
3
4
# max
max_int = 0x7FFFFFFF
# min
min_int = -0x80000000

使用abs(n)求n的绝对值

1
2
3
print abs(-11)
# output
11

使用整除符号//

1
2
3
4
print 3.5 // 2
print 3.5 / 2
print 3 // 2
print 3 / 2
  • //是整除符号,只保留整数部分,但是结果的类型可能为整数,也可能为浮点数,具体取决于除法两边是否含有浮点数

list中的子列表

1
2
3
4
5
6
l = [1, 2, 3, 4, 5]
print l[2:-1]
print l[-1]
# output
[3, 4]
5
  • list l 中使用 -1 可以理解为 len(l)-1, 不管是字列表还是元素的索引操作

sorted的返回值总是list

  • 即使传入的是一个 string, 返回值也是 list, 需要牢记
    1
    2
    3
    print sorted("1523")
    # output:
    ['1', '2', '3', '5']

Python 的 for 循环语句结束时 i 的值与 Java 不同

  • Python

    1
    2
    3
    4
    5
    6
    for i in range(0,  5):
    print i
    print "final:", i
    # output
    0 1 2 3 4
    final: 4
  • Java/C++

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int i;
    for(i = 0; i < 5; i++){
    System.out.print(i);
    }
    System.out.print("final:")
    System.out.print(i)
    # output
    0 1 2 3 4
    final: 4
  • 如果想得到 Java/C++ 的效果, 可以使用 while 语句

    1
    2
    3
    4
    5
    6
    7
    8
    i = 0
    while i < 5:
    print i
    i+= 1
    print "final:", i
    # output
    0 1 2 3 4
    final: 4

join 函数调用的条件

  • 特别注意: join 函数的参数只能是字符串, 不能是数字

    1
    2
    3
    4
    5
    l = [1, 2, 3, 4]
    print ''.join(l)

    # output
    TypeError: sequence item 0: expected string, int found
    • 使用非string元素的列表时抛出TypeError的错误

dict.get()

  • 原始定义

    1
    dict.get(key[, default=None])
    • default参数可以指定默认值, 当key值不存在时可以返回默认值, 如果不指定, 则默认key值不存在时返回None
  • dict[key]对比

    • 使用dict[key]时要确保keydict中, 否则会报异常

一行太长的代码需要分多行

  • 必须在每个子行行尾部使用\

  • 子行内部不用对齐, 因为解析时Python解释器会将所有子行合并成一行

  • 示例:

    1
    2
    if 9 < 10 and 11 < 12 and 13 < 14:
    print "works"
    • 等价于

      1
      2
      3
      4
      if 9 < 10 and \
      11 < 12 and \
      13 < 14:
      print "works"
      • 等价于
        1
        2
        3
        4
        if 9 < 10 and \
        11 < 12 and \
        13 < 14:
        print "works"

全局变量只要在函数调用前声明就行了

  • 我们定义函数时, 函数里面的变量可以没有定义
  • 调用函数的时候, 默认这个函数中没定义过的变量都是全局变量, 函数会主动寻找相关的全局变量, 找不在再报错
  • 核心: 定义函数时函数中没定义的变量被使用了(如 x=10 这样的赋值算是变量的定义, 不是使用), 那么默认函数认为他是全局变量, 当函数被调用的时候, 才会寻找全局变量是否在当前 Python 运行环境中
  • 所以, 可以先定义函数, 再初始化(定义全局变量), 最后调用函数
    • 只要初始化全局变量在调用函数之前即可
    • 但是需要注意函数中不能给全局变量赋值, 被赋值的变量将被函数认为是局部变量
      1
      2
      3
      4
      5
      6
      7
      8
      def sum_x(y):
      return x+y

      x = 100
      print sum_x(10)

      # output
      110

使用列表切片修改列表

  • 测试代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    a = [1, 2, 3, 4, 5, 6]
    b = [1, 2, 3, 4, 5, 6]
    c = [0, 0, 0, 0, 0, 0]

    a[:2] = c[:2]
    print(a)
    d = b[:2]
    d[:] = c[:2]
    print(d)
    print(b)
    b[:2] = [0]
    print(b)

    # Output:
    [0, 0, 3, 4, 5, 6]
    [0, 0]
    [1, 2, 3, 4, 5, 6]
    [0, 3, 4, 5, 6]
    • 列表切片在左边时, 可以修改数组内部数据, 甚至是长度都可以修改(最后两行代码)
    • 列表切片在右边时, 表现为复制一份列表返回给变量d, 所以修改d的值将不影响原始的列表b

使用del删除列表或字典中的元素

  • 删除列表或字典中的元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    l = [1,  2,  3]
    d = {"m": 10, "x": 11}
    print(l)
    print(d)
    del l[0]
    print(l)
    del l[0]
    print(l)
    del d["m"]
    print(d)

    # Output:
    [1, 2, 3]
    {'m': 10, 'x': 11}
    [2, 3]
    [3]
    {'x': 11}
  • 注意不能删除元组中的元素

    1
    2
    3
    4
    5
    6
    7
    8
    t = (1, 2, 3)
    del t[1]

    # Output:
    Traceback (most recent call last):
    File "/home/jiahong/JupyterWorkspace/test2.py", line 13, in <module>
    del t[1]
    TypeError: 'tuple' object doesn't support item deletion

dict.keys()返回的是列表吗?

  • 代码示例

    1
    2
    3
    d = {"m": 10,  "x": 11}
    print(d.keys())
    print(type(d.keys()))
  • Python 2.7中输出

    1
    2
    ['x',  'm']
    <type 'list'>
  • Python 3.6中输出

    1
    2
    dict_keys(['m',  'x'])
    <class 'dict_keys'>
  • 总结

    • Python2.7中输出是列表, 丢失了set信息, 占用空间小, 但是会造成使用x in d.keys()时变成线性搜索时间 O(n)
    • Python3.6中输出是dict_keys类型的对象, 保留了set信息, 占用空间也大了, 便于使用x in d.keys()时变成常数搜索时间 O(1)

列表切片的详细说明 [::-1]

  • 切片完整用法

    1
    li[start:end:step]
    • start: 开始索引, 包含li[start], 默认为0
    • end: 结束索引, 不包含li[end], 默认为len(li)
    • step: 跳着取元素, step为间隔, 默认为1
      • step可以设置为负, 此时若start > end则能得到, 从[start, end]结束的序列, 包含li[start], 不包含li[end], 由于此时start > end, 所以得到的是逆序列
      • 注意: 若step参数省略的话第二个:也能省略
  • 代码示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    A = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    print(A)
    print(A[::-1])
    print(A[::-2])
    print(A[::1])
    print(A[::2])
    print(A[1:5:])
    print(A[1:5:])
    print(A[5:1:-1])

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

vars函数的使用

  • 定义

    1
    2
    3
    4
    5
    6
    7
    8
    def vars(p_object=None): # real signature unknown; restored from __doc__
    """
    vars([object]) -> dictionary

    Without arguments, equivalent to locals().
    With an argument, equivalent to object.__dict__.
    """
    return {}
    • vars(object)返回对象的字典
  • 代码示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class A:
    def __init__(self):
    self.a = 10
    self.b = "abc"

    def getA(self):
    self.c = "100"


    a = A()
    print(A.__dict__)
    print(vars(A))

    # Output:
    {'__module__': '__main__', '__init__': <function A.__init__ at 0x7f87b2be01e0>, 'getA': <function A.getA at 0x7f87994c2e18>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
    {'__module__': '__main__', '__init__': <function A.__init__ at 0x7f87b2be01e0>, 'getA': <function A.getA at 0x7f87994c2e18>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
  • 注意: object.__dict__一般在序列化的时候访问, 平时不会访问


整数除法(负整数)

  • C++ 和 Java中整数除法是向0取整
  • Python中整数除法是向下取整(向负无穷取整)
  • 正整数除法他们的商和余数相同
  • 负整数除法商和余数不同 ,需要注意, 不要用错

如何获取一个正数的小数部分?

  • 方法1

    1
    2
    a = 10.234
    decimal = a - int(a)
  • 方法2

    1
    2
    a = 10.234
    decimal = a % 1

for循环中使用lambda的坑

  • 问题代码

    1
    2
    3
    4
    5
    6
    funs = []
    for i in range(3):
    funs.append(lambda: i)
    print funs[0](), funs[1](), funs[2]()

    ## output: 2 2 2
    • 问题原因:由于lambda引用对象不会被lambda定义时复制,lambda定义后x还可以在外面被修改,最终结果是所有函数都持有相同的i作为引用对象
  • 解决方案:

    1
    2
    3
    4
    5
    6
    funs = []
    for i in range(3):
    funs.append(lambda x=i: x)
    print funs[0](), funs[1](), funs[2]()

    ## output: 0 1 2
    • 核心思想:在定义lambda时将i的值复制给默认参数,这一步实现了值的复制

对象当做函数调用

  • 在类定义中加入__call__函数定义,则可以将对象当做函数来调用
  • 代码示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class SimpleCallable:
    def __init__(self, multiplier):
    self.multiplier = multiplier

    def __call__(self, value):
    # 当实例被当作函数调用时执行的逻辑
    return value * self.multiplier

    # 创建 SimpleCallable 的一个实例
    callable_instance = SimpleCallable(multiplier=2)

    # 将实例作为函数调用
    result = callable_instance(5) # 这里相当于调用了 __call__ 方法
    print(result) # 输出应该是 10

import 包可使用括号

  • 在 Python 中,导入包时可以使用括号,也可以不使用括号

  • 这两种导入方式在功能上没有任何区别 ,它们的作用完全相同

    • from x import x, y 是紧凑的单行写法,用逗号分隔导入的对象
    • from x import (x, y) 是将导入的对象放在括号中,这种写法在导入对象较多、需要换行时更常用
  • 括号导入的示例如下:

    1
    2
    3
    4
    5
    6
    from module import (
    object1,
    object2,
    object3,
    object4
    )
  • 使用括号的形式可以让代码结构更清晰,避免因换行导致的语法问题(Python 中通常用反斜杠处理换行,但用括号更优雅)


assert 语句的使用

  • 使用示例:

    1
    assert a == b,  c
    • 含义:若 a == b 返回 True,则 c 不执行;否则 返回 cc 一般包含错误信息)

PYTHONUNBUFFERED 环境变量的使用

  • 默认情况下,Python 会将输出内容,如 print() 语句的输出,暂存在内存缓冲区中,直到缓冲区填满或程序结束才一次性写入终端或日志文件
  • 而设置 PYTHONUNBUFFERED=1 后,所有输出会立即写入,不再等待缓冲区
    • 这在需要实时查看输出的场景,如调试、监控、日志流等情况下非常有用,可以避免日志延迟,确保信息即时可见
  • 注:设置 PYTHONUNBUFFERED=1 相当于在运行 Python 程序时使用 -u 命令行选项,二者效果是等效的
  • 更准确的描述:PYTHONUNBUFFERED=1 的作用是禁用 Python 标准输出(stdout)和标准错误(stderr)的缓冲机制,使得输出内容能够实时显示,而非等待缓冲区填满后才一次性写入

Python 不支持 函数重载(overload)

  • Python 本身不支持传统意义上的函数重载(即同名函数根据参数个数 / 类型自动匹配调用)
    • 因为 Python 是动态类型语言,函数定义时不指定参数类型,且同名函数会直接覆盖前一个定义
    • Python 不允许同名函数并存(后定义的会覆盖前一个)
  • 如果要 “模拟重载”,则其本质是:在同一个函数中,通过判断参数个数(args/*kwargs) 或参数类型,分支执行不同逻辑
  • Python 中可使用 @overload 装饰器的类型提示重载,但是并不是真的生效,仍然需要自己手动判断调用类型