python知识摘要

  1. 以下划线开头的标识符是有特殊意义的。以单下划线开头_foo的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用 from xxx import * 而导入。以双下划线开头的 __foo代表类的私有成员,以双下划线开头和结尾的__foo__代表 Python 里特殊方法专用的标识,如 __init__() 代表类的构造函数。

  2. list是一种有序的集合,可以随时添加和删除其中的元素。要删除指定位置的元素,用pop(i),其中i是索引位置。

  3. tuple也是有序列表,但一旦初始化就不能修改。初始化:t = (1,2) 另外,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义。

  4. python内置了字典dict,在其他语言中称为map,使用键-值(key-value)存储,具有极快的查找速度。要删除一个key,使用pop(key)。

  5. set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。add(key), remove(key)。可以使用 { } 或 set( ) 创建集合,但是创建一个空集合时,只能使用set( )

  6. str是不可变对象,而list是可变对象。对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样就保证了不可变对象本身永远是不可变的。

  7. 函数名其实是指向一个函数对象的引用,完全把函数名赋给一个变量,相当于给这个函数起了一个别名。

  8. 数据类型检查可以用内置函数isinstance()实现。

  9. 写函数时定义默认参数,需要牢记:默认参数必须指向不变对象。必选参数在前,默认参数在后。另外,python函数还可以定义可变参数,可变参数就是传入的参数个数是可变的。可变参数允许传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。

    1
    2
    3
    def calc(*numbers): # 传入的numbers可以是list, tuple
    for n in numbers:
    pass
  10. 关键字参数:关键字参数允许传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装成一个dict

    1
    2
    3
    4
    def person(name, age, **kw):
    print('name:',name,'age:',age,'other:',kw)
    person('Michael',30) # 输出name: Michael age: 30 other: {}
    person('Bob',35,city='Beijing') # name: Bob age: 35 other: {'city': 'Beijing'}

    关键字参数的作用:扩展函数的功能。比如我们在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项。利用关键字参数来定义这个函数,就能满足注册的需求。

    1
    2
    extra = {'city':'Beijing', 'job':'Engineer'}
    person('Jack',24,**extra) #name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

    **extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数。

  11. 命名关键字参数:如果要限制关键字参数的名字,可以用命名关键字参数,比如只接受city和job作为关键字参数的定义函数如下:

    1
    2
    3
    def person(name, age, *, city, job):
    # 即需要一个特殊的
    print(name, age, city, job)

    命名关键字参数必须传入参数名,否则调用将报错。同时,可以有缺省值,从而简化调用。

  12. 参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。对于任意函数,都可以通过类似func(*args, **kw)的形式调用,无论它的参数是如何定义的。(因此,*args是可变参数,接受的是一个tuple;**kw是关键字参数,接受的是一个dict)

    可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))

    关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

    定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

  13. 列表生成式

    1
    [x * x for x in range(1, 11) if x % 2 == 0]

    注意,我们不可以在最后的if加else。这是因为跟在for后面的if是一个筛选条件,不能带else。而如果我们把if写在for前面,则必须加else,否则报错。因此,在一个列表生成式中,for前面的if ... else是表达式,而for后面的if是过滤条件,不能带else

    1
    [exp1 if condition else exp2 for x in data]
  14. for循环遍历dict

    1
    2
    for k, v in d.items():
    print(k, '=', v)
  15. 生成器:通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

    方法一:

    1
    g = (x * x for x in range(10)) # 和列表生成式比较,改成了括号

    可以通过next()函数获得generator的下一个返回值。但我们通常用for循环来迭代它。

    方法二:

    另外,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现,则可以用函数来实现。这是定义generator的另一种方法:如果一个函数定义中包含yield关键字,则其不是普通函数,而是generator。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
    yield b
    a, b = b, a + b
    n = n + 1
    return 'done'

    g = fib(6)
    while True:
    try:
    x = next(g)
    print('g:', x)
    except StopIteration as e:
    print('Generator return value', e.value)
    break

    要理解generator的工作原理,它是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。

  16. 迭代器:可以直接作用于for循环的对象统称为可迭代对象(Iterable)。可以使用isinstance()判断一个对象是否是Iterable对象。

    1
    2
    3
    4
    from collections import Iterable
    from collections import Iterator
    print(isinstance([], Iterable)) # True
    print(isinstance([], Iterator)) # False

    可以被next()函数调用并不断返回下一个值的对象称为迭代器(Iterator)。把list、dict、str等Iterable变成Iterator可以使用iter()函数:

    1
    isinstance(iter([]), Iterator) # True

    Iterator可以表示一个无限大的数据流,例如全体自然数。但是使用list是永远不可能存储完的。

  17. 函数式编程:其特点是允许把函数本身作为参数传入另一个函数,还允许返回一个函数。然而,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。python对函数式编程提供部分支持,由于python允许使用变量,因此python不是纯函数式编程。

  18. 变量可以指向函数:如我们有绝对值函数abs,而我们可以用f = abs;函数名也是变量

  19. 传入函数:一个函数可以接受另一个函数作为参数,称之为高阶函数。

    1
    2
    3
    4
    def add(x, y, f):
    return f(x) + f(y)
    # 调用
    add(-5, 4, abs) # 9
  20. map()函数:接收两个参数,一个是函数,一个是Iterable。map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

    1
    2
    3
    4
    def f(x):
    return x * x
    r = map(f, [1,2,3,4,5]) # r为Iterator
    list(r) # [1,4,9,16,25]

    由于r是一个IteratorIterator是一个惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。

  21. reduce()函数:reduce把一个函数作用在一个序列[x1,x2,x3,...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:reduce(f,[x1,x2,x3,x4]) = f(f(f(x1,x2),x3),x4)

    1
    2
    3
    4
    from functools import reduce
    def add(x, y):
    return x * 10 + y
    print(reduce(add, [1,3,5,7,9])) # 输出13579
  22. filter()函数:用于过滤序列(筛选函数)。和map()类似,filter()也接受一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

    1
    2
    3
    4
    def is_odd(n):
    return n % 2 == 1
    list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
    # 结果:[1,5,9,15]
  23. sorted()函数:

    1
    2
    3
    sorted([36,5,-12,9,-21], key=abs)
    # 还有 reverse=True(降序)
    # 输出[5, 9, -12, -21, 36]
  24. lambda匿名函数:为了减少单行函数的定义而存在。

    1
    2
    list(map(lambda x: x * x, [1,2,3,4]))
    # 输出 [1,4,9,16]
  25. 装饰器(decorator):一个返回函数的高阶函数。如果我们想在函数调用之前自动打印日志,但又不希望修改函数的定义,这种在代码运行期间动态增加功能的方式,称为装饰器。(比如经常使用的@timeit)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def log(func):
    def wrapper(*args, **kw): # 接受任意参数的调用
    print('call %s():' % func.__name__)
    return func(*args, **kw)
    return wrapper

    @log
    def now():
    print('2020-03-10')

    >>> now()
    call now():
    2020-03-10

    其中,把@log放到now()函数的定义处,相当于执行了语句:now = log(now)

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数。

  1. python内部使用引用计数,来保持追踪内存中的对象,Python内部记录了对象有多少个引用,即引用计数,当对象被创建时就创建了一个引用计数,当对象不再需要时,这个对象的引用计数为0时,它被垃圾回收。

    引用计算加1的情况:

    • 对象被创建:x = 4
    • 另外的别人创建:y = x
    • 被作为参数传递给函数:foo(x)
    • 作为容器对象的一个元素:a = [1,x’33’]

    引用计算减少情况:

    • 一个本地引用离开了它的作用域。比如上面的foo(x)函数结束时,x指向的对象引用减1。
    • 对象的别名被显式的销毁:del x ;或者del y
    • 对象的一个别名被赋值给其他对象:x=789
    • 对象从一个窗口对象中移除:myList.remove(x)
    • 窗口对象本身被销毁:del myList,或者窗口对象本身离开了作用域。
  2. 垃圾回收(import gc)

    • 当内存中有不再使用的部分时,垃圾收集器就会把他们清理掉。它会去检查那些引用计数为0的对象,然后清除其在内存的空间。当然除了引用计数为0的会被清除,还有一种情况也会被垃圾收集器清掉:当两个对象相互引用时,他们本身其他的引用已经为0了.
    • 垃圾回收机制还有一个循环垃圾回收器, 确保释放循环引用对象(a引用b, b引用a, 导致其引用计数永远不为0)。

    python中,许多时候申请的内存都是小块的内存,这些小块内存在申请后,很快又会被释放。由于这些内存的申请并不是为了创建对象,所以并没有对象一级的内存池机制。这就意味着python在运行期间会大量地执行malloc和free的操作,频繁地在用户态和核心态之间进行切换,这将严重影响python的执行效率。为了加速python的执行效率,python引入了一个内存池机制,用于管理对小块内存的申请和释放

    1
    2
    3
    import gc
    gc.get_threshold() # gc模块中查看阈值的方法
    gc.collect() # 手动启动垃圾回收

    如果存在循环引用,则创建数量>释放数量。当创建数量与释放数量的差值达到规定的阈值的时候,则有分代回收机制:python将所有的对象分为0,1,2三代:所有的新建对象是0代;当某一代对象经过垃圾回收后依然存活,则被归入下一代对象。

  3. 内存池机制:python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的 malloc。另外Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。(总结:大对象使用malloc进行分配,小对象使用内存池分配

  4. 模块:

    1583853580787

    文件www.py的模块名就是mycompany.web.www,两个文件utils.py的模块名分别是mycompany.utilsmycompany.web.utils

  5. 作用域:在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。

    • 正常的函数和变量名是公开的(public),可以被直接引用,比如:abcx123PI等;
    • 类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author____name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;
    • 类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc__abc等;以单下划线开头_foo的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用 from xxx import * 而导入。以双下划线开头的 __foo代表类的私有成员。
  6. 面向对象的设计思想是抽象出Class,根据Class创建Instance。面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。封装、继承和多态是面向对象的三大特点。

    面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

  7. 在类定义中,如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。在python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

  8. 继承的好处:子类获得了父类的全部功能。当然,我们也可以对子类增加一些方法。当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

代码小记:

  1. 一行完成变量值交换

    1
    a, b = b, a
  2. 按key或value对字典进行排序

    1
    2
    3
    4
    # 按key排序
    sorted(mydict.keys(), re0verse=True) # 降序
    # 按value排序
    sorted(mydict.items(), key=lambda item: item[1])