Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

Python——函数返回值是copy还是引用

Python中有些函数是直接操作当前对象的,有些函数是操作副本的


操作当前对象的

  • list.sort(), 返回空
  • random.shuffle(my_list), 返回空
  • func(inplace=True), 这里Pandas库中的其他方法几乎均适用

返回copy的

  • 除了numpy的reshape()外目前默认都为
  • sorted(), 内置函数,返回新对象,不论接受什么参数返回的都是列表
  • np.ndarray.reshape() 返回新对象,但是新对象除了shape属性外,数据属性是和原始对象共享的
    • np.ndarray存储着数据和一个shape属性
    • 我们可通过修改shape属性而不是创建新对象来修改当前对象的shape object.shape = 3,4
    • 使用reshape时可以理解为创建了一个新对象,但是共享了数据,两个ndarray对象有相同的数据引用
    • numpy包没有array类,只有ndarray类,array是一个函数,用于构造ndarray,也可以用ndarray函数构建,但是不推荐,测试ndarray函数发现用法很奇怪

Python——标准输入input函数

本文简单讲解input和raw_input函数的用法,两者都是内置函数,无需用户自己导入模块


input函数

  • 用法

    1
    input[[prompt]]
  • 实例

    1
    2
    3
    while True:
    a = input("input: ")
    print type(a), a
  • 交互结果

    1
    2
    3
    4
    5
    6
    7
    8
    input: 123
    <type 'int'> 123
    input: "abc"
    <type 'str'> abc
    input: 1.0
    <type 'float'> 1.0
    input: abc
    SyntaxError: unexpected EOF while parsing
  • 总结:

    • 当输入为整数时,识别为int和long类型
    • 当输入为小数时,识别为float等类型
    • 当输入为string(两边需要添加")时,识别为str类型
    • 当输入为未知,像是string但是没被"引用起来时,抛出语法错误异常

raw_input函数

  • 用法与inputx完全相同

  • 把所有的输入都当做字符串,注意,如果输入的字符串带有",那么"会被保留在字符串内部

  • 用法实例

    1
    2
    3
    while True:
    a = raw_input("input: ")
    print type(a), a
  • 交互结果

    1
    2
    3
    4
    5
    6
    7
    8
    input: 123
    <type 'str'> 123
    input: "abc"
    <type 'str'> "abc"
    input: 1.0
    <type 'str'> 1.0
    input: abc
    <type 'str'> abc

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]时要确保key在dict中, 否则会报异常

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

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

  • 子行内部不用对齐, 因为解析时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 不执行;否则 返回 c(c 一般包含错误信息)

PYTHONUNBUFFERED 环境变量的使用

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

Python 不支持 函数重载(overload)

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

列表等可变对象赋值时需要注意准确性

初始化

  • 初始化时,如果是乘法是对列表的列表进行的,则会出现赋制引用的情况
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 正确初始化 m 行 n 列的数组(元素之间隔离)
    l = [[0] * n for range(m)]
    # 错误初始化 m 行 n 列的数组(同一列的不同行元素共享引用)
    l = [[0]* n] * m

    # 正确初始化 1 行 n 列数组(元素之间隔离)
    l = [[0] for range(m)]
    # 错误初始化 1 行 n 列数组(不同行元素共享引用)
    l = [0] * m

多个值同时赋值

  • 错误交换元素的实现:
    1
    numbers[i], numbers[numbers[i]] = numbers[numbers[i]], numbers[i]
两个核心规则:
  • 右侧表达式先全部计算 :赋值符号 = 右侧的所有值会先被计算并暂存,不会受左侧赋值的影响
  • 左侧按顺序赋值 :右侧暂存的值会按顺序赋值给左侧的变量/元素
    • 注意:是按照顺序的,所以第一个值 numbers[i] 赋值后会修改第二个值 numbers[numbers[i]] 的引用
详细拆解
  • 假设:

    1
    2
    3
    4
    numbers = [2, 0, 1]
    i = 0
    # 执行目标代码
    numbers[i], numbers[numbers[i]] = numbers[numbers[i]], numbers[i]
  • 步骤 1:计算右侧所有表达式(暂存值)

    • 先计算 numbers[numbers[i]] 和 numbers[i] 的值,此时 i=0,numbers 还是初始状态 [2,0,1]
    • numbers[i] = numbers[0] = 2
    • numbers[numbers[i]] = numbers[2] = 1
    • 右侧最终暂存的元组是 (1, 2)
  • 步骤 2:按顺序给左侧赋值

    • 将右侧暂存的 (1, 2) 按顺序赋值给左侧的两个目标
    • 第一个赋值:numbers[i] = 1 给 numbers[0] = 1,此时 numbers 变为 [1, 0, 1]
    • 第二个赋值:numbers[numbers[i]] = 2
      • 注意!问题出在这里的 numbers[i] 已经是第一步赋值后的结果(值为 1),因此实际是 numbers[1] = 2,最终 numbers 变为 [1, 2, 1]
推荐正确用法
  • 正确交换元素的实现:
    1
    2
    3
    4
    5
    6
    # 使用缓存
    temp = numbers[i]
    numbers[i], numbers[temp] = numbers[temp], numbers[i]

    # 注意左边的赋值顺序,保证前面的赋值不会影响后面(不推荐)
    numbers[numbers[i]], numbers[i] = numbers[i], numbers[numbers[i]]

Python——编程规范

Python 编程中经验型的一些规范
持续更新


使用li[:]代替copy.copy(li)

  • li[:]等价于copy.copy(li)
  • li[:]不等价于copy.deepcopy(li)
  • li[:]可以简化代码

从后开始访问列表

  • list作为stack用时访问栈顶元素list[-1]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    list1 = [1, 5, 2, 3]
    print list1[-1]
    print list1[-2]
    print list1[1:-1]

    # # output:
    # 3
    # 2
    # [5, 2]

函数内函数

  • Python可以在函数内部定义函数,但是要注意不能与外部变量混淆
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def sum_all(alist):

    def sum_two(a, b):
    return a+b

    result = 0
    for i in alist:
    result = sum_two(result, i)
    return result


    print sum_all([1, 2, 3, 4, 5])
    # # output:
    # 15

不要随意调用特殊方法

(比如:__len__)

  • 特殊方法不应该被开发者调用,而应该被开发者定义新类时实现
  • 一般使用len(object)即可
  • 原因: 内置的方法(比如:len())可能会直接返回对象中的ob_size属性,而不用调用__len__()(这个函数往往会用迭代或者其他比较复杂的方法实现)

多使用列表推导

(list comprehension, 简写listcomps)和生成器表达式(generator expression, 简写genexps)

  • listcomps

    1
    [str(i) for i in range(1,10)]
  • genexps

    1
    (str(i) for i in range(1,10))

考虑使用reduce而不是循环语句

  • reduce
    1
    2
    3
    def add(x, y):
    return x + y
    reduce(add, [1,2,3,4,5])
1
2
from operator import mul
reduce(mul, range(1, 10))

多使用pprint

  • (Pretty Print)而不是print
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import pprint
    l1 = (1, {2: 3}, "first", ("second", 5, 6, [7, 8, 9]), [1, 3, 5], "this is a pprint")
    pprint.pprint(l1)

    # Output:
    (1,
    {2: 3},
    'first',
    ('second', 5, 6, [7, 8, 9]),
    [1, 3, 5],
    'this is a pprint')

多使用三目运算符

  • Python不像Java和C++一样,有x?y:z这样的三目运算符号,但是可以有自己的特殊使用方法,等价于三目运算符且更容易理解

    1
    2
    3
    # same as x?y:z
    result = y if x is True else z
    return y if x is True else z
    • 注意: 在使用return语句中的三目运算时必须有else语句,否则编译不通过,因为返回值可能会缺失

匿名变量的使用

  • 初始化一个列表时
    1
    list1 = [_ for _ in list2 if _.val > 10]

Python异常处理

  • 异常处理的正确姿势, 注意如果不是必要的话不要使用Exception, 可以考虑列出来需要捕获的所有异常, 然后在函数内部判断异常类型

    1
    2
    3
    4
    5
    try:
    read some thing
    except IOError, ValueError, e:
    exception_type = type(e)
    print("%s" % e)
  • 当然, 我们一般为了方便也会直接使用下面的方法

    1
    2
    3
    4
    5
    try:
    read some thing
    except Exception, e:
    exception_type = type(e)
    print("%s" % e)

位运算

  • 在Python中, 位运算与C++中有所不同
  • 参见Python——位运算与逻辑运算和C++有什么不同

函数

  • 内部函数访问全局变量时使用global关键字声明(与外部函数一样)
  • 内部函数访问外部函数的变量使用nonlocal(仅限Python 3)关键字声明
  • 如果没有声明
    • 变量变为只读的
      • 可以写出a.append()这样的语句
      • 但不可以写出a = b这样的语句
  • 函数内部变量与函数外同名时:
    • 若写出赋值操作,则认为当前变量为局部变量
    • 否则认为是函数外的全局变量
    • 若先访问变量(一般访问,不是对变量赋值,此时视为全局变量),然后对变量赋值(此时视为局部变量),则产生矛盾,Python解释器报错

三元表达式的使用

  • 如果在三元表达式使用在加法中,需要加上括号,不然整体意思会变成错误的

  • 比如两个结点的加法操作,带有进位carry,我们可以简单的写一行:

    • 下面是错误示例:

      1
      val = l1.val if l1 else 0 + l2.val if l2 else 0 + carry
    • 上面的表达式可以理解为如果l1不为空,返回l1.val,否则返回0 + l2.val if l2 else 0 + carry

    • 当l1为空时又可以理解为如果l2不为空,返回0+l2.val否则返回0+carry

  • 正确的写法

    1
    val = (l1.val if l1 else 0) + (l2.val if l2 else 0) + carry

迭代dict的键时用dict.iterkeys()

  • 迭代键值时使用iterkeys()而不是keys()

  • iterkeys()返回一个迭代器而不是所有键值列表

  • keys()返回所有键值列表

    1
    2
    3
    4
    # 迭代效率高
    for _ in dict.iterkeys()
    # 迭代效率低
    for _ in dict.keys()
  • 迭代值时也同理

    1
    2
    3
    4
    # 迭代效率高
    for _ in dict.itervalues()
    # 迭代效率低
    for _ in dict.values()

zip实现二维列表的行列变换

  • 只适用于Python3,因为涉及到*操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    a = [[1,2,3],
    [4,5,6]]

    print([e for e in zip(*a)]) # 等价于 print([a for a in zip(a[0],a[1])])
    print(*a) # 等价于 print(a[0],a[1])
    # b = *a # 这行会报错: SyntaxError: can't use starred expression here

    # Output
    # [(1,4), (2,5), (3,6)]
    # [1, 2, 3] [4, 5, 6]
  • Python3中才能使用*作为列表解包符

  • Python3中zip函数返回的是一个生成器而不是列表,所以需要迭代成列表

  • 注意:在代码中,*a 的作用是将列表 a 中的子列表解包(unpack)为独立的参数。具体来说:

    • 当a 是一个列表时,*a可以作为参数传递,不能直接赋值给其他变量
    • 使用 print(a) 时,它会直接打印整个嵌套列表,输出为:[[1, 2, 3], [4, 5, 6]]
    • 使用 zip(*a) 时,*a 会将 a 解包为两个独立的列表 [1, 2, 3] 和 [4, 5, 6],等价于 zip(a[0], a[1])
    • 使用 print(*a) 时,*a 会将 a 解包为两个独立的列表 [1, 2, 3] 和 [4, 5, 6],然后 print 函数会分别打印这两个列表,输出为:[1, 2, 3] [4, 5, 6]

Python中逻辑运算符的巧用

不建议使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
print 0 or 1
print 10 or 1
print None or 1
print 0 and 1
print 1 and 10
print None and 10

# # output:
# 1
# 10
# 1
# 0
# 10
# None
  • 理解

    1
    2
    3
    4
    5
    6
    7
    print a or b 
    <==>
    print a if a else b

    print a and b
    <==>
    print b if a else a
  • 记忆:

    • 把a, b当做逻辑表达式,如果访问到b则返回b,否则返回a

reversed函数的使用

  • 接受参数为可迭代对象,返回一个反向访问迭代对象的迭代器

    1
    2
    3
    4
    5
    for i in reversed(range(n)):

    <==>

    for i in range(n-1, -1, -1):
  • reversed的使用似乎更优雅,也更容易理解

  • 容易遗忘的点,需要注意: reversed的参数必须是可迭代的对象,而不是两个数字


将简单的句子优雅的写到一行

  • 返回值

    1
    2
    3
    4
    if i < 0: return Flase
    <==>
    if i < 0:
    return False
  • 其他简单的执行语句直接合并

    1
    i = 10; j = 10
  • 判断语句之后使用

    1
    if bool: i = 10;
  • 判断语句后使用多条

    1
    if bool: i = 10; j = 20
  • while语句后使用

    1
    while(bool): print 10; print 20
  • 当句子较长时不建议使用

  • 一般为了美观,平时的项目也不建议使用

  • 刷题时为了让代码看起来简便,是可以使用的,但是这样会使得代码不易调试

  • 总之:慎用


用同一个值初始化两个变量

1
x = y = value
  • 在初始化链表头部时最常用
    1
    head, curr = ListNode(None)

使用collections.Counter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import collections

counter = collections.Counter([1, 2, 1, 3, 4, "a", "a", "c"])

print counter
print counter["a"]
print counter[1]
print counter[-1]
print counter.get("a")
print counter.get(-1, 0)

# # Output:
# Counter({'a': 2, 1: 2, 2: 1, 3: 1, 4: 1, 'c': 1})
# 2
# 2
# 0
# 2
# 0

括号用作分组表达式

  • 在 Python 中,括号用于分组表达式,不会改变表达式的值(除非用于元组、函数调用等特殊情况)
    • 所以,(1) 只是 1,不会变成元组或其他类型
  • a = (1) 和 a = 1 等价
  • a = (1,),则是元组类型,和 a = 1 不等价

for 和 while 语句内部作用域

  • Python 和 C++ 不一样

    • C++ 中,通常变量在函数、类、或代码块(如循环、条件语句)内部定义时,只能在该作用域内访问
    • Python 中,在函数、类中定义的是局部变量,但是循环、条件语句内部定义的不是
  • C++ 中,for 和 while 语句中定义的变量仅在语句内可访问(虽然 for 和 while 语句本身并不产生新的作用域,但是 for 和 while 语句需要接 {},这个会产生一个作用域块)

    1
    2
    3
    4
    5
    6
    // C++ 中
    for (int i = 0; i < 3; ++i) {
    int x = i;
    // 可以使用 i 和 x
    }
    // 这里不能访问 i 和 x
  • Python 中,for 和 while 语句不会产生新的定义域,在 for 和 while 语句内部定义的变量,可以直接在外面访问

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    for i in range(3):
    x = i
    print(x) # 输出 2,最后一次循环的值
    print(i) # 输出 2,i 也不会被视作局部变量

    i = 1
    while True:
    y = i
    i += 1
    if y >= 20:
    break
    print(y) # 输出 20
  • 注:当 for 循环迭代没有生效时,外面无法访问

    1
    2
    3
    4
    5
    6
    7
    8
    9
    for x in range(11, 11):
    print(x)

    print("finally, x is", x) # NameError: name 'x' is not defined

    for x in range(0):
    print(x)

    print("finally, x is", x) # NameError: name 'x' is not defined
    • 这是一个常见的 bug,所以建议在循环语句前加一个默认赋值操作,确保变量可访问

list 切片索引可以超过 list 长度而不会报错

  • 示例
    1
    2
    3
    4
    a = [1,2,3,4,5,6]

    print(a[:100]) # [1, 2, 3, 4, 5, 6]
    print(a[99:100]) # []

递归函数中定义函数会影响递归函数的运行效率吗?

  • 在 Python 中,在递归函数内部定义函数(嵌套函数)可能会对运行效率产生一定影响,主要体现在以下几个方面:

  • 函数定义开销:

    • 递归函数每次调用时,内部嵌套的函数都会被重新定义一次(生成新的函数对象),例如:

      1
      2
      3
      4
      5
      6
      7
      def recursive(n):
      # 每次递归调用时,都会重新定义 inner()
      def inner(x):
      return x * 2
      if n == 0:
      return 0
      return inner(n) + recursive(n-1)
    • 每次递归调用 recursive(n) 时,inner 函数都会被重新创建,这会产生额外的内存分配和函数对象初始化开销

    • 如果将 inner 定义在递归函数外部,只会初始化一次,避免了重复定义的成本

  • 作用域查找的开销:

    • 嵌套函数需要访问外部递归函数的变量时,会涉及闭包作用域的查找 ,比全局作用域或局部作用域的查找更耗时
    • 例如,若 inner 引用了 recursive 中的局部变量,Python 解释器需要在闭包环境中逐级查找,增加了每次调用的开销
  • 对解释器优化的限制

    • Python 解释器(如 CPython)对函数的优化(如局部变量缓存)在嵌套函数中可能效果较差
    • 递归本身已经依赖解释器对函数调用栈的处理,嵌套函数可能进一步增加解释器的执行负担
  • 建议:

    • 将嵌套函数移到递归函数外部定义,避免重复初始化
    • 若需要共享变量,可通过参数传递替代闭包引用,减少作用域查找开销

默认参数是在 函数定义时 立即绑定的

  • 核心原则:Python 中函数的默认参数值,是在函数定义时(而非调用时)计算并绑定的

  • 这里影响我们对函数的调用行为,需要特别关注

  • 测试一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 只要 function 定义就会立即调用 a(), 且后续无论如何调用 function(), a()仅会被调用一次
    def a():
    print("a")

    def function(x = a()):
    print(f'function+{x}')
    pass

    function()
    function()
    function()

    # a
    # function+None
    # function+None
    # function+None
  • 测试二:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 文件一内定义:
    import os
    def function(x = os.path.abspath(__file__)):
    print(f'function+{x}')
    pass

    # 文件一内调用:
    function() # 输出是文件一: function+/path_to_file1.py

    # 文件二内调用:
    function() # 输出还是文件一: function+/path_to_file1.py

如何打印对象名称?

  • 在 Python 中,对象本身没有“名字”属性(变量名只是引用对象的“标签”)

    • 一个对象可能被多个变量引用,也可能没有显式变量名(如匿名对象 func() 的返回值)
    • 因此,我们无法直接获取“对象自己的名字”
  • 可以通过 查找引用该对象的变量名 来间接实现打印对象名称,核心原理为:

    • 通过 globals()(全局变量字典)、locals()(局部变量字典)或 inspect 模块遍历变量,反向查找“值等于目标对象”的变量名(即“找到引用该对象的标签”)
  • 方案示例:查找全局变量中的对象名(最常用)

    • 如果对象是全局变量(模块级定义),可遍历 globals() 字典,匹配值等于目标对象的变量名:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      def get_object_name(obj) -> list[str]:
      """
      查找全局变量中引用该对象的所有名字(一个对象可能被多个变量引用)
      :param obj: 目标对象
      :return: 变量名列表(无匹配则返回空列表)
      """
      return [name for name, value in globals().items() if value is obj]

      # 测试示例
      import torch

      # 全局变量(对象)
      a = [1, 2, 3]
      b = a # 同一对象的另一个引用
      tensor1 = torch.randn(2, 3)

      # 打印对象的“名字”(变量名)
      print(get_object_name(a)) # 输出:['a', 'b'](a和b都引用同一个列表对象)
      print(get_object_name(tensor1)) # 输出:['tensor1']
      print(get_object_name([4,5,6])) # 输出:[](匿名对象,无全局变量引用)
  • 方案示例:查找局部变量中的对象名(如函数内)

    • 如果对象是函数内的局部变量,需结合 locals()(当前作用域局部变量)或 inspect 模块获取调用者的局部变量:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      import inspect

      def get_object_name(obj, include_global=True) -> list[str]:
      """
      同时查找局部变量和全局变量中引用该对象的名字
      :param obj: 目标对象
      :param include_global: 是否包含全局变量
      :return: 变量名列表
      """
      names = []
      # 1. 查找当前调用者的局部变量(函数内的变量)
      caller_locals = inspect.currentframe().f_back.f_locals
      names.extend([name for name, value in caller_locals.items() if value is obj])

      # 2. 可选:包含全局变量
      if include_global:
      names.extend([name for name, value in globals().items() if value is obj])

      return list(set(names)) # 去重(避免局部和全局变量名重复)

      # 测试:函数内的局部对象
      def test_local():
      local_dict = {"key": "value"}
      local_tensor = torch.tensor([1,2])
      # 查找局部变量中的对象名
      print(get_object_name(local_dict)) # 输出:['local_dict']
      print(get_object_name(local_tensor)) # 输出:['local_tensor']

      test_local()

Pandas——apply、applymap和map函数

Pandas库中apply, applymap和map函数的使用


所属类

  • apply : 属于DataFrame类和Series类
  • applymap : 属于DataFrame类
  • map : 属于Series类

作用

  • map 和 applymap都是对每个元素分别操作的:
  • apply
    • 在DataFrame中是对列或者行操作,每一列或者行都是一个Series(列: axis=0[默认值],行: axis=1)
    • 在Series中是对每个元素进行操作(其实换个角度理解为对Series的每一列操作也行,此时的每一列就是一个元素,值得注意的是此时的每个元素是数值类型而不是Series类型,所以不能对其调用sum等函数)

使用方法

1
2
3
4
5
6
7
8
9
# a simple example for apply(), applymap() and map()
func_series = lambda x: x.sum()
func_element = lambda x: "%.2f" % x

df.apply(func_series)
df.applymap(func_element)

ser.apply(func_element)
ser.map(func_element)

概率——共轭分布

待更新,共轭分布
参考:LDA数学八卦


扩展——关于共轭分布

  • 高斯分布和高斯分布
    • Gaussion-Gaussion共轭
  • Beta分布和二项分布
    • Beta-Binomial共轭
  • Dirichlet分布和多项分布
    • Dirichlet-Multinomial共轭

因果推断——PSM挑选对照组


问题描述

  • 共 100 个实验商家,需要从 2000+ 个商家中使用PSM匹配找到相似商家作为 AB 实验

PSM 基本流程

  • 共三个集合:实验组、对照组(初始化为空)、候选商家集合
  • 特征构造:选择可能影响实验结论的关键特征
  • label 构建:标记 100 个实验商家 label 为 1,其他商家 label 为 0
  • 模型训练:用构建的样本训练一个分类模型,一般为 LR 模型即可
  • PSM 匹配:迭代访问实验商家 A,分别进行如下操作
    • 在候选商家集合中选择模型预估值和 A 差异小于一定阈值的商家作为匹配候选集,从匹配候选集中随机选择一个商家 B
    • 将商家 B 加入对照组,并在候选商家集合中删除对照组

流程思考

  • 如果找不到指定阈值的商家作为匹配候选集

Python 实现 Demo

  • PSM 匹配 Demo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    import pandas as pd
    import numpy as np
    from sklearn.linear_model import LogisticRegression
    from sklearn.preprocessing import StandardScaler

    # 生成模拟数据(假设有2000个商家)
    np.random.seed(42)
    n = 2000

    # 生成商家特征
    data = pd.DataFrame({
    "shop_id": range(n),
    "monthly_sales": np.random.normal(50000, 15000, n), # 月销售额
    "store_size": np.random.choice([50, 100, 150, 200], n), # 店铺面积
    "is_chain": np.random.binomial(1, 0.3, n), # 是否连锁店
    "city_tier": np.random.choice([1, 2, 3], p=[0.2,0.5,0.3], n), # 城市等级
    "category": np.random.choice(["餐饮", "零售", "服务", "其他"], p=[0.4,0.3,0.2,0.1], n) # 类别
    })

    # 添加实验组标记(随机选择100家作为实验组)
    experiment_group = np.random.choice(n, 100, replace=False)
    data['is_treated'] = data.shop_id.isin(experiment_group).astype(int)

    # 特征预处理
    features = data[['monthly_sales', 'store_size', 'is_chain', 'city_tier']]
    scaler = StandardScaler()
    features_scaled = scaler.fit_transform(features)

    # 训练倾向得分模型
    model = LogisticRegression(max_iter=1000)
    model.fit(features_scaled, data['is_treated'])

    # 计算倾向得分
    data['propensity_score'] = model.predict_proba(features_scaled)[:, 1]

    # PSM匹配函数
    def psm_match(data, treated_col='is_treated', score_col='propensity_score', caliper=0.02):
    treated = data[data[treated_col] == 1]
    control = data[data[treated_col] == 0]

    matches = []
    for _, treat in treated.iterrows():
    # 寻找最近邻匹配
    control['distance'] = abs(control[score_col] - treat[score_col])
    candidates = control[control.distance <= caliper]

    if not candidates.empty:
    # 随机选择最近邻
    match = candidates.sample(1, random_state=42)
    matches.append(match.index[0])
    control = control.drop(match.index)

    return treated, data.loc[matches]

    # 执行匹配
    treated_group, control_group = psm_match(data)

    # 评估匹配质量
    print(f"匹配成功实验商家数:{len(treated_group)}")
    print(f"找到对照组商家数:{len(control_group)}")

    # 检查协变量平衡性
    matched_data = pd.concat([treated_group, control_group])
    for col in ['monthly_sales', 'store_size', 'is_chain']:
    treat_mean = treated_group[col].mean()
    control_mean = control_group[col].mean()
    std_diff = (treat_mean - control_mean) / treated_group[col].std()
    print(f"{col}标准化差异:{std_diff:.3f}")

    # 输出匹配结果
    matched_pairs = pd.DataFrame({
    'experiment_id': treated_group.shop_id.values,
    'control_id': control_group.shop_id.values
    })
  • 代码说明:

    • 数据模拟:生成包含销售额、店铺面积、是否连锁等特征的2000个商家数据
    • 实验组选择:随机选择100个商家作为实验组(实际业务中应根据业务逻辑选择)
    • 特征标准化:对连续变量进行标准化处理
    • 倾向得分模型:使用逻辑回归预测进入实验组的概率
    • 卡钳匹配:设置最大允许得分差异(caliper=0.02),确保匹配质量
    • 平衡性检查:通过标准化差异评估匹配质量(<0.1为良好)

ML——LdaModel在gensim的使用

LDA在Python库gensim中的模型和参数介绍


API

1
2
3
4
5
6
7
class LdaModel(interfaces.TransformationABC, basemodel.BaseTopicModel):
def __init__(self, corpus=None, num_topics=100, id2word=None,
distributed=False, chunksize=2000, passes=1, update_every=1,
alpha='symmetric', eta=None, decay=0.5, offset=1.0, eval_every=10,
iterations=50, gamma_threshold=0.001, minimum_probability=0.01,
random_state=None, ns_conf=None, minimum_phi_value=0.01,
per_word_topics=False, callbacks=None, dtype=np.float32)
1
2
3
# a simple example
import gensim
gensim.models.ldamodel.LdaModel(corpus, num_topics=2, id2word=dictionary, passes=20)

Parameters

  • 主要参数:

    • corpus: 语料库,类似于

      [ [(1, 1),(4, 1)], [(2, 1),(3, 2)] ]

      • gensim库中一般默认corpus参数是经过字典编码统计的,类似于上面的形式,而texts是文本的列表的形式
    • num_topics: 主题数量,超参数

    • id2word: dict of (int, str), :class:gensim.corpora.dictionary.Dictionary

      • 用于将corpus中的数字与词进行对应,这里应该为把texts转成corpus的那个字典
    • passes: 训练时的迭代次数

    • iterations: 推断时的迭代次数

    • alpha: 主题的先验概率

      • 一个num_topics大小的数组表明每个主题的概率
      • 也可以是str类型的值
        • “asymmetric”: 固定初始化为1.0/num_topics
    • decay: (0.5, 1]之间的浮点数,前一个lambda值被遗忘的百分比?【待确认参数】

  • 其他参数:

    • distributed: 是否使用分布式计算

相关类介绍

  • gensim.corpora.dictionary.Dictionary
    1
    2
    class Dictionary(utils.SaveLoad, Mapping):
    def__init__(self, documents=None, prune_at=2000000)
1
2
3
4
5
6
7
# a simple example
from gensim.corpora import Dictionary
texts = [['human', 'interface', 'computer']]
dct = Dictionary(texts) # initialize a Dictionary
dct.add_documents([["cat", "say", "meow"], ["dog"]]) # add more document (extend the vocabulary)
dct.doc2bow(["dog", "computer", "non_existent_word"])
# output: [(0, 1), (6, 1)]

完整代码示例

ML——机器学习中的编码方式

One-hot encoding与Dummy-encoding易混淆点区分
Label Encoding标签编码


独热码(One-Hot code)

  • 又称独热编码、一位有效编码,直观来说就是有多少个状态就有多少比特,而且只有一个比特为1,其他全为0的一种码制
  • 其方法是使用N位状态寄存器来对N个状态进行编码,每个状态都有它独立的寄存器位,并且在任意时候,其中只有一位有效

 哑变量编码(Dummy encoding)

  • 哑变量编码直观的解释就是在One-Hot编码的基础上任意的将一个状态位去除
    • 比热独码少一维即可编码
  • 可以理解为多个状态位之间是相关的,已知n-1个那么可以推出剩下的那个
    • 比如已知前n-1个状态位为0,那么最后一位一定为1
    • 一种做法是: 全0算是一维(理解: 由于全0可以默认最后一位为1, 其他非全0的可以默认最后一维为0,所以能够区分不同样本)

哑变量(Dummy variable)

亦称指示变量(Indicator variable)

  • 以上两种编码得到的变量都称为指示变量或者哑变量

为什么需要One-Hot编码?

  • 大部分算法是基于向量空间中的度量来进行计算的,为了使非偏序关系的变量取值不具有偏序性,并且到圆点是等距的, 使用one-hot编码, 将离散特征的取值扩展到了欧式空间, 离散特征的某个取值就对应欧式空间的某个点, 将离散型特征使用one-hot编码,会让特征之间的距离计算更加合理
  • 将离散特征通过one-hot编码映射到欧式空间,是因为,在回归,分类,聚类等机器学习算法中,特征之间距离的计算或相似度的计算是非常重要的,而我们常用的距离或相似度的计算都是在欧式空间的相似度计算,计算余弦相似性,基于的就是欧式空间

独热编码优缺点

优点

  • 独热编码解决了分类器不好处理属性数据的问题,在一定程度上也起到了扩充特征的作用
  • 它的值只有0和1,不同的类型存储在垂直的空间
  • 数据天然归一化了, 非常优秀

缺点

  • 当类别的数量很多时,特征空间会变得非常大
    • 一般可以用PCA来减少维度
    • One-Hot encoding + PCA 这种组合在实际中也非常有用

什么时候不用独热编码

  • 有些基于树的算法在处理变量时,并不是基于向量空间度量,数值只是个类别符号,即没有偏序关系,所以不用进行独热编码, 典型的代表如XGBoost, LightGBM等
  • 存在偏序关系的特征,不能用独热编码, 独热编码会使得特征失去原来的偏序关系

标签编码

Label Encoding

  • 将类别编码为连续的数值类型(0,1,2,3…)
  • 举例
    1
    2
    3
    4
    5
    6
    7
    from sklearn.preprocessing import LabelEncoder
    le = LabelEncoder()
    le.fit([1,8,9,67,5,8,6])
    print(le.transform([1,1,8,9,67,5,5]))

    # Output:
    [0 0 3 4 5 1 1]

附录: 机器学习过程

ML——误差棒简单介绍

Reference[1]: 维基百科
Reference[2]: How to Calculate Error Bars?


误差棒

(Error bar, 也称为误差线)

显示潜在的误差或相对于系列中每个数据标志的不确定程度

  • 误差线可以用标准差(standard deviation)或者标准误差(standard error),一般用标准差

标准差与标准误差

  • 标准差是离均差平方和平均后的方根
  • 标准误是标准误差,定义为各测量值误差的平方和的平均值的平方根
  • 标准差与均数结合估计参考值范围,计算变异系数,计算标准误等。标准误用于估计参数的可信区间,进行假设检验等
  • 当样本含量 n 足够大时,标准差趋向稳定;而标准误随n的增大而减小,甚至趋于0

计算误差棒

  • Step1: 计算均值 E
  • Step2: 计算标准差 D
  • Step3: 计算误差棒的两端值
    • barBegin = E-D
    • barEnd = E+D
1…484950…64
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

631 posts
53 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4