Python知识点整理


1.可迭代对象、迭代器、生成器

可迭代对象包括迭代器,序列,字典,支持innot in访问对象中的元素。迭代器可以记住操作的位置,是可迭代对象的子集,而生成器又是迭代器的子集。

迭代器与其他可迭代对象的区别在于迭代器支持__next()__方法,可以通过iter()方法将可迭代对象转化为迭代器。

生成器是迭代器的子集。

yield可以与函数组合成一个生成器,其作用有2个:1.类似于return,返回一个值,函数执行到此处退出。

2.可以记住每次遍历的位置,再次调用函数,就从上次执行的yield的下一句开始执行。

生成器与迭代器相比,在提高运行速度的同时,大大降低了占用的内存。

可以看个例子:

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))

输出:

starting...
4
********************
res: None
4

2.深拷贝与浅拷贝

在python中,对象赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用。

判断的法则是:看拷贝前后变量的内存地址是否发生变化

(1)直接赋值,默认浅拷贝传递对象的引用而已,原始列表改变,被赋值的b也会做相同的改变

>>> b=alist
>>> print b
[1, 2, 3, ['a', 'b']]
>>> alist.append(5)
>>> print alist;print b
[1, 2, 3, ['a', 'b'], 5]
[1, 2, 3, ['a', 'b'], 5]

img

(2)copy浅拷贝,没有拷贝子对象,所以原始数据改变,子对象会改变,但注意仅仅是被拷贝的部分

>>> import copy

>>> c=copy.copy(alist)
>>> print alist;print c
[1, 2, 3, ['a', 'b']]
[1, 2, 3, ['a', 'b']]
>>> alist.append(5)
>>> print alist;print c
[1, 2, 3, ['a', 'b'], 5]
[1, 2, 3, ['a', 'b']]

>>> alist[3]
['a', 'b']
>>> alist[3].append('cccc')
>>> print alist;print c
[1, 2, 3, ['a', 'b', 'cccc'], 5]
[1, 2, 3, ['a', 'b', 'cccc']] 里面的子对象被改变了

img

(3)深拷贝,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变

>>> import copy
>>> d=copy.deepcopy(alist)
>>> print alist;print d
[1, 2, 3, ['a', 'b']]
[1, 2, 3, ['a', 'b']]始终没有改变
>>> alist.append(5)
>>> print alist;print d
[1, 2, 3, ['a', 'b'], 5]
[1, 2, 3, ['a', 'b']]始终没有改变
>>> alist[3]
['a', 'b']
>>> alist[3].append("ccccc")
>>> print alist;print d
[1, 2, 3, ['a', 'b', 'ccccc'], 5]
[1, 2, 3, ['a', 'b']]  始终没有改变

img

3.python下划线

  • 以单下划线开头,表示这是一个保护成员,只有类对象和子类对象自己能访问到这些变量。以单下划线开头的变量和函数被默认当作是内部函数,使用from module improt *时不会被获取,但是使用import module可以获取

  • 以单下划线结尾仅仅是为了区别该名称与关键词

  • 双下划线开头,表示为私有成员,只允许类本身访问,子类也不行。在文本上被替换为_class__method

  • 双下划线开头,双下划线结尾。一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突。是一些 Python 的“魔术”对象,表示这是一个特殊成员,例如:定义类的时候,若是添加__init__方法,那么在创建类的实例的时候,实例会自动调用这个方法,一般用来对实例的属性进行初使化,Python不建议将自己命名的方法写为这种形式。

4.方法

Python其实有3个方法,即静态方法(staticmethod),类方法(classmethod)和实例方法,如下:

class A(object):
    '''
    实例方法:对于实例方法,我们知道在类里每次定义方法的时候都需要绑定这个实例,就是`foo(self, x)`,为什么     要这么做呢?因为实例方法的调用离不开实例,我们需要把实例自己传给函数,调用的时候是这样的`a.foo(x)`(其实     是`foo(a, x)`).
    '''
    def foo(self,x):
        print "executing foo(%s,%s)"%(self,x)
    '''
    类方法一样,只不过它传递的是类而不是实例,`A.class_foo(x)`.注意这里的self和cls可以替换别的参数,但是     python的约定是这俩,还是不要改的好.
    '''
    @classmethod
    def class_foo(cls,x):
        print "executing class_foo(%s,%s)"%(cls,x)
    '''
    对于静态方法其实和普通的方法一样,不需要对谁进行绑定,唯一的区别是调用的时候需要使用`a.static_foo(x)`     或者`A.static_foo(x)`来调用.
    '''
    @staticmethod
    def static_foo(x):
        print "executing static_foo(%s)"%x

a=A()

5.为什么python不支持函数重载

函数重载主要是为了解决两个问题。
1。可变参数类型。
2。可变参数个数。

对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。

那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。

6.函数参数传递

对于不可变参数,例如数字,字符串,元组,类似于值传值,函数内部产生一个副本,函数内对参数的操作,不会影响到函数外的参数。

对于可变参数,例如列表,集合,字典,类似于引用传值,在函数内对参数的操作,在函数外部也会显现出来,但是这种操作不包括重新创建一个对象的修改,这样内存内置就会发生变化。

7.*args and **kwargs

表示可变长度的参数,其中*args表示位置参数,**kwargs表示关键字参数。

args因为 *前缀 的存在, 会把位置参数收集到一个tuple元組中。
kwargs因为 **前缀 的存在, 会把位置参数收集到一个dict字典中

当然 argskwargs可以为空

8.闭包

闭包又被称为闭合函数,其特征是:一个函数(外函数)嵌套了另外一个函数(内函数),外函数返回了内函数的引用,而内函数使用了外函数的临时变量。

当外函数执行时,会将外函数的临时变量存储下来供内函数使用,需要注意是,多次调用闭包函数,其内函数使用的都是一套外函数的临时变量。可以使用闭包的 closure 属性,来查看该临时变量的地址。

def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of
square = nth_power(2)
#查看 __closure__ 的值
print(square.__closure__)

************************************
(<cell at 0x0000014454DFA948: int object at 0x00000000513CC6D0>,)

为什么要使用闭包函数?

闭包函数的优势主要在多次调用上,一般情况下,在确定具有某些功能的函数的时候,都需要先执行一些额外的工作,如果多次执行这个函数,那么这些额外的操作会执行多次,而使用闭包函数,就可以将这些额外操作放在外函数内,这样闭包函数的多次调用,便会直接去提取外函数的这些变量。避免了不必要的开销,提高了程序的运行效率。此外,表达也会更加简洁。

9.装饰器

装饰器基于闭包,本质上是一个函数,其可以让函数在不做任何代码改动的情况下,增加其额外的功能。

def test(func):
    def wrapper():
        print("ok")
        return func()
    return wrapper

这是一个最简单的装饰器的例子, print("ok")是原本的功能,func()是拓展额外的功能。从这也可以看出,他的基本结构就是一个闭包。

@test
def func():
    print("yes")

func()
***************************
ok
yes

@符号是装饰器的语法糖,等同于func=test(func),其避免了额外的赋值操作。

从以上的例子不难看出,装饰器提高了程序的可重复利用性,并增加了程序的可读性。

  • 带参数的装饰器

    def nth_power(exponent):
        def exponent_of(base):
            return base ** exponent
        return exponent_of
    
  • 类装饰器

    相比于函数装饰器,类装饰器灵活度更大,封装性更强。使用类装饰器可以依靠内部的__call__方法

    class test(object):
        def __init__(self,func):
            self._func=func
        def __call__(self, *args, **kwargs):
            print("ok")
            self._func()
            print("yes")
            
    @test
    def foo():
        print("good")
    
    foo()
    
  • functools.wrap

    装饰器有个问题就是func的函数的元信息会被wrapper取代,这些元信息包括__name__,docstring等

    这个时候可以使用functools.wrap,wraps本身也是一个装饰器,其可以使得func的函数的元信息不会被wrapper取代。

    from  functools import wraps
    def test(func):
        @wraps(func)
        def wrapper():
            print("ok")
            return func()
        return wrapper
    @test
    def func():
        print("yes")
    
    print(func.__name__)   #加 @wraps返回func,不加 @wraps返回wrapper
    

10.read,readline和readlines

  • read 读取整个文件
  • readline 读取下一行,使用生成器方法
  • readlines 读取整个文件到一个迭代器以供我们遍历

11.内存管理

python的内存管理机制主要类似于金字塔模型

  • -1,-2层主要有操作系统进行操作;
  • 第0层是C中的malloc,free等内存分配和释放函数进行操作;
  • 第1层和第2层是内存池,有Python的接口函数PyMem_Malloc函数实现,当对象小于256K时有该层直接分配内存;
  • 第3层是最上层,也就是我们对Python对象的直接操作; 

对第二层的python的内存池机制做一些说明:

由于频繁创建大量消耗小内存的对象时,调用new/malloc会导致大量的内存碎片,致使效率降低,因此第1层和第2层的内存池,主要是为了处理小块内存的申请和释放,操作原理是先预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再使用第0层的malloc申请256kb的内存,这样做最显著的优势就是能够减少内存碎片,提升效率。

释放内存时,当一个对象的引用计数变为 0 时,python就会调用它的析构函数。调用析构函数并不意味着最终一定会调用free 释放内存空间,如果真是这样的话,那频繁地申请、释放内存空间会使 Python的执行效率大打折扣。因此在析构时也采用了内存池机制,从内存池申请到的内存会被归还到内存池中,以避免频繁地 释放 动作。

img

12.异常处理

  • try…except...else

    s = 'Hello girl!'
    try:
        print( s[100])
    except:
         print('error')
    print ('continue')
    
    ************************
    '''
    try-except语句按照如下方式工作: 
    1)执行try子句(在关键字try和except之间的语句) 
    2)如果没有异常发生,忽略except子句,try子句执行后结束 
    3)如果在执行try子句的过程中发生了异常,那么try子句余下的部分将被忽略;如果异常的类型和excet之后的名称相符,那么对应的except子句将被执行;最后执行try语句之后的代码 
    4)如果一个异常没有与任何except匹配,那么这个异常将会传递给上层的try语句中
    
    一个try语句可以包含多个except子句,分别用来处理特定的异常,但是最多只有一个except子句分支会被执行
    
    一个except子句可以同时处理多个异常,这些异常将被放在圆括号里以元组形式出现
    
    except关键字后面甚至可以不加名称(当通配符使用)
    
    另外,try-except语句还有一个可选的else子句,这个子句必须写在except子句之后,当且仅当try子句没有任何异常发生时执行(Phthonic的写法)
    '''
    
  • try…except...finally

    简而言之,finally语句就是用来做异常处理的扫尾工作的

    不管try子句中到底有没有异常,finally子句都会执行

    如果一个异常在try子句中(或者except和else子句中)被抛出,而没有任何的except把它截住,那么这个异常就会在fianly子句执行后再次被抛出

13.多线程、多进程

  • 多线程和多进程关系

    '''
    ”进程是资源分配的最小单位,线程是CPU调度的最小单位“
    
    做个简单的比喻:进程=火车,线程=车厢
    
    1.线程在进程下行进(单纯的车厢无法运行)
    2.一个进程可以包含多个线程(一辆火车可以有多个车厢)
    3.不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
    4.同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
    5.进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
    6.进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
    7.进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
    8.进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
    '''
    

14.GIL

  • 背景:

    1.GIL是全局解释器锁,用来保证数据安全。

    2.cpu在同一时间只能执行一个线程。(单核cpu的多线程其实是单线程的并发,并行是同一时刻干多个事情,并发是相同的时间间隔干多个事情)

  • GIL的执行过程

    在python中,GIL的执行过程是:1.线程获取GIL。2.执行代码直至sleep或者python虚拟机将其挂起。3.释放GIL

    从这里看出,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行,因此python的多线程其实是单线程

  • python多线程的效率如何呢?

    判断效率前,首先需要明确GIL锁的释放方法:当前线程遇见IO操作或者ticks计数达到100时,自动释放GIL。(在python3中改为了计时器,达到一定的时间来释放)

    • 对于计算密集型任务来说,ticks计数很容易达到100,这样很容易出发GIL的释放于再竞争,容易造成资源浪费。
    • 对于IO密集型任务来说,IO操作会进行IO等待。此时,开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率
    • 需要强调的是,多核多线程比单核单线程更差,因为涉及了核与核之间线程的竞争,容易造成线程颠簸。
  • 综上所述,在多核任务下,每个进程都有一个GIL锁,因此多进程类似于多线程,多进程的执行效率更高。

参考:https://zhuanlan.zhihu.com/p/20953544

15.内置数据类型、可变数据类型、不可变数据类型

  • 内置数据类型

    数字,字符串,列表,字典,元组,集合

  • 可变数据类型

    列表,集合,字典

  • 不可变数据类型

    数字,字符串,元组

16.python的特点

python是面向对象的解释型的脚本语言,主要特点如下

  • 简单好学(近乎伪代码)
  • 开源
  • 可扩展性(第三方库很多)
  • 可移植性
  • 解释型(编译型语言:c++/c需要编译器编译,python之间利用解释器,将源代码转换成机器语言)
  • 高级语言(自动管理内存,封装了很多实现细节)
  • 功能强大

17.python解释器的种类与特点

  • CPython

    c语言开发的 使用最广的解释器

  • IPython

    • 基于cpython之上的一个交互式计时器 交互方式增强 功能和cpython一样
  • JPython

    • 运行在Java上的解释器 直接把python代码编译成Java字节码执行


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM