Python 【基础面试题】

前言

面试题仅作学习参考,学习者阅后也要用心钻研其中的原理,重要知识须要系统学习、透彻学习,造成本身的知识链。如下五点建议但愿对您有帮助,早日拿到一份心仪的offer。php

  1. 作好细节工做,细致的人运气不会差
  2. 展示特别能够,但要创建在已充分展现实力的基础上
  3. 真诚比圆滑重要,请真诚地回答问题
  4. 把握当下,考场外的表现能起的做用微乎其微
  5. 没有经过不表明你不优秀,选人先考虑的是与岗位相匹配

 

Python 三程三器

  • 进程
    • 进程是资源分配的最小单位(内存、CPU、网络、io)
    • 一个运行起来的程序就是一个进程
      • 什么是程序(程序使咱们存储在硬盘里的代码)
      • 硬盘(256G)、内存条(8G)
      • 当咱们双击一个图标、打开程序的时候,实际上就是经过IO(读写)内存条里面
      • 内存条就是咱们所指的资源
      • CPU分时
        • CPU比你的手速快多了,分时处理每一个线程,可是因为太快然你以为每一个线程都是独占cpu
        • cpu是计算,只有时间片到了,获取cpu,线程真正执行
        • 当你想使用 网络、磁盘等资源的时候,须要cpu的调度
    • 进程具备独立的内存空间,因此没有办法相互通讯
      • 进程如何通讯
        • 进程queue(父子进程通讯)
        • pipe(同一程序下两个进程通讯)
        • managers(同一程序下多个进程通讯)
        • RabbitMQ、redis等(不一样程序间通讯)
    • 为何须要进程池
      • 一次性开启指定数量的进程
      • 若是有十个进程,有一百个任务,一次能够处理多少个(一次性只能处理十个)
      • 防止进程开启数量过多致使服务器压力过大
  • 线程
    • 有了进程为何还须要线程
      • 由于进程不能同一时间只能作一个事情
    • 什么是线程
      • 线程是操做系统调度的最小单位
      • 线程是进程正真的执行者,是一些指令的集合(进程资源的拥有者)
      • 同一个进程下的读多个线程共享内存空间,数据直接访问(数据共享)
      • 为了保证数据安全,必须使用线程锁
    • GIL全局解释器锁
      • 在python全局解释器下,保证同一时间只有一个线程运行
      • 防止多个线程都修改数据
    • 线程锁(互斥锁)
      • GIL锁只能保证同一时间只能有一个线程对某个资源操做,但当上一个线程还未执行完毕时可能就会释放GIL,其余线程就能够操做了
      • 线程锁本质把线程中的数据加了一把互斥锁
        • mysql中共享锁 & 互斥锁
          • mysql共享锁:共享锁,全部线程都能读,而不能写
          • mysql排它锁:排它,任何线程读取这个这个数据的权利都没有
        • 加上线程锁以后全部其余线程,读都不能读这个数据
      • 有了GIL全局解释器锁为何还须要线程锁
        • 由于cpu是分时使用的
    • 死锁定义
      • 两个以上的进程或线程在执行过程当中,因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法推动下去
    • 多线程
      • 为何要使用多线程?
        • 线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄
        • 和其余进程应有的状态。
        • 由于线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享
        • 内存,从而极大的提高了程序的运行效率。
        • 线程比进程具备更高的性能,这是因为同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境
        • 包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通讯。
        • 操做系统在建立进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但建立线程则简单得多。所以,使用多线程来实现并发比使用多进程的性能高得要多。
      • 总结起来,使用多线程编程具备以下几个优势:?
        • 进程之间不能共享内存,但线程之间共享内存很是容易。
        • 操做系统在建立进程时,须要为该进程从新分配系统资源,但建立线程的代价则小得多。所以使用多线程来实现多任务并发执行比使用多进程的效率高
        • python语言内置了多线程功能支持,而不是单纯地做为底层操做系统的调度方式,从而简化了python的多线程编程。
import threading
import time
def sayhi(num): #定义每一个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(10):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()
for循环同时启动多个线程
    • join/setDaemon
      • 当一个进程启动以后,会默认产生一个主线程,由于线程是程序执行流的最小单元,当设置多线程时,主线程会建立多个子线程,在python中,默认状况下(其实就是setDaemon(False)),主线程执行完本身的任务之后,就退出了,此时子线程会继续执行本身的任务,直到本身的任务结束。
      • 当咱们使用setDaemon(True)方法,设置子线程为守护线程时,主线程一旦执行结束,则所有线程所有被终止执行,可能出现的状况就是,子线程的任务尚未彻底执行结束,就被迫中止。此时join的做用就凸显出来了,join所完成的工做就是线程同步,即主线程任务结束以后,进入阻塞状态,一直等待其余的子线程执行结束以后,主线程在终止。
      • join有一个timeout参数:
        • 当设置守护线程时,含义是主线程对于子线程等待timeout的时间将会杀死该子线程,最后退出程序。因此说,若是有10个子线程,所有的等待时间就是每一个timeout的累加和。简单的来讲,就是给每一个子线程一个timeout的时间,让他去执行,时间一到,无论任务有没有完成,直接杀死。
        • 没有设置守护线程时,主线程将会等待timeout的累加和这样的一段时间,时间一到,主线程结束,可是并无杀死子线程,子线程依然能够继续执行,直到子线程所有结束,程序退出。线程池
        • 对于任务数量不断增长的程序,每有一个任务就生成一个线程,最终会致使线程数量的失控,例如,整站爬虫,假设初始只有一个连接a,那么,这个时候只启动一个线程,运行以后,获得这个连接对应页面上的b,c,d,,,等等新的连接,做为新任务,这个时候,就要为这些新的连接生成新的线程,线程数量暴涨。在以后的运行中,线程数量还会不停的增长,彻底没法控制。因此,对于任务数量不端增长的程序,固定线程数量的线程池是必要的。
import threading
import time
start_time = time.time()

def sayhi(num): #定义每一个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
t_objs = []    #将进程实例对象存储在这个列表中
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()          #启动一个线程,程序不会阻塞
    t_objs.append(t)
print(threading.active_count())    #打印当前活跃进程数量
for t in t_objs: #利用for循环等待上面50个进程所有结束
    t.join()     #阻塞某个程序
print(threading.current_thread())    #打印执行这个命令进程

print("----------------all threads has finished.....")
print(threading.active_count())
print('cost time:',time.time() - start_time)
join()实现全部线程都执行结束后再执行主线程
import threading
import time
start_time = time.time()
def sayhi(num): #定义每一个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.setDaemon(True)  #把当前线程变成守护线程,必须在t.start()前设置
    t.start()          #启动一个线程,程序不会阻塞
print('cost time:',time.time() - start_time)
setDaemon()守护线程,主线程退出时,须要子线程随主线程退出
import requests
from concurrent.futures import ThreadPoolExecutor

def fetch_request(url):
    result = requests.get(url)
    print(result.text)

url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         #google页面会卡住,知道页面超时后这个进程才结束
    'http://dig.chouti.com/',          #chouti页面内容会直接返回,不会等待Google页面的返回
]

pool = ThreadPoolExecutor(10)            # 建立一个线程池,最多开10个线程
for url in url_list:
    pool.submit(fetch_request,url)       # 去线程池中获取一个线程,线程去执行fetch_request方法

pool.shutdown(True)  
线程实现并发
  • 协程
    • 什么是协程
      • 协程微线程,纤程,本质是一个单线程
      • 协程能在单线程处理高并发
        • 线程遇到I/O操做会等待、阻塞,协程遇到I/O会自动切换(剩下的只有CPU操做)
        • 线程的状态保存在CPU的寄存器和栈里而协程拥有本身的空间,因此无需上下文切换的开销,因此快、
      • 为甚么协程可以遇到I/O自动切换
        • 协程有一个gevent模块(封装了greenlet模块),遇到I/O自动切换
    • 协程缺点
      • 没法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程须要和进程配合才能运行在多CPU上
      • 线程阻塞(Blocking)操做(如IO时)会阻塞掉整个程序
    • 协程最大的优势
      • 不只是处理高并发(单线程下处理高并发)
      • 特别节省资源(500日活,用php写须要两百多态机器,可是golang只须要二十多太机器)
        • 200多台机器一年
        • 二十多天机器一年
    • 协程为什么能处理大并发
      • greeenlet遇到I/O手动切换
      • 协程遇到I/O操做就切换,其实Gevent模块仅仅是对greenlet的封装,将I/O的手动切换变成自动切换
      • 协程之因此快是由于遇到I/O操做就切换(最后只有CPU运算)
      • 这里先演示用greenlet实现手动的对各个协程之间切换
      • 其实Gevent模块仅仅是对greenlet的再封装,将I/O间的手动切换变成自动切换
from greenlet import greenlet



def test1():

    print(12)       #4 gr1会调用test1()先打印12

    gr2.switch()    #5 而后gr2.switch()就会切换到gr2这个协程

    print(34)       #8 因为在test2()切换到了gr1,因此gr1又从上次中止的位置开始执行

    gr2.switch()    #9 在这里又切换到gr2,会再次切换到test2()中执行



def test2():

    print(56)       #6 启动gr2后会调用test2()打印56

    gr1.switch()    #7 而后又切换到gr1

    print(78)       #10 切换到gr2后会接着上次执行,打印78



gr1 = greenlet(test1)    #1 启动一个协程gr1

gr2 = greenlet(test2)    #2 启动第二个协程gr2

gr1.switch()             #3 首先gr1.switch() 就会去执行gr1这个协程
greeenlet遇到I/O手动切换
      • Gevent遇到I/O自动切换
      • Gevent是一个第三方库,能够经过gevent实现并发同步或异步编程,Gevent原理是只要是遇到I/O操做就自动切换下一个协程
      • Gevent 是一个第三方库,能够轻松经过gevent实现并发同步或异步编程
      • 在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程
      • Greenlet所有运行在主程序操做系统进程的内部,但它们被协做式地调度。
      • Gevent原理是只要遇到I/O操做就会自动切换到下一个协程
from urllib import request

import gevent,time

from gevent import monkey

monkey.patch_all()      #把当前程序全部的I/O操做给我单独作上标记



def f(url):

    print('GET: %s' % url)

    resp = request.urlopen(url)

    data = resp.read()

    print('%d bytes received from %s.' % (len(data), url))



#1 并发执行部分

time_binxing = time.time()

gevent.joinall([

        gevent.spawn(f, 'https://www.python.org/'),

        gevent.spawn(f, 'https://www.yahoo.com/'),

        gevent.spawn(f, 'https://github.com/'),

])

print("并行时间:",time.time()-time_binxing)



#2 串行部分

time_chuanxing = time.time()

urls = [

        'https://www.python.org/',

        'https://www.yahoo.com/',

        'https://github.com/',

                                        ]

for url in urls:

    f(url)

print("串行时间:",time.time()-time_chuanxing)



# 注:为何要在文件开通使用monkey.patch_all()

# 1. 由于有不少模块在使用I / O操做时Gevent是没法捕获的,因此为了使Gevent可以识别出程序中的I / O操做。

# 2. 就必须使用Gevent模块的monkey模块,把当前程序全部的I / O操做给我单独作上标记

# 3.使用monkey作标记仅用两步便可:

      第一步(导入monkey模块):  from gevent import monkey

      第二步(声明作标记)    :   monkey.patch_all()
使用Gevent实现并发下载网页与串行下载网页时间比较
  • Gevent实现简单的自动切换小例子
  • 注:在Gevent模仿I/O切换的时候,只要遇到I/O就会切换,哪怕gevent.sleep(0)也要切换一次
import gevent



def func1():

    print('\033[31;1m第一次打印\033[0m')

    gevent.sleep(2)          # 为何用gevent.sleep()而不是time.sleep()由于是为了模仿I/O

    print('\033[31;1m第六次打印\033[0m')



def func2():

    print('\033[32;1m第二次打印\033[0m')

    gevent.sleep(1)

    print('\033[32;1m第四次打印\033[0m')



def func3():

    print('\033[32;1m第三次打印\033[0m')

    gevent.sleep(1)

    print('\033[32;1m第五次打印\033[0m')



gevent.joinall([            # 将要启动的多个协程放到event.joinall的列表中,便可实现自动切换

    gevent.spawn(func1),    # gevent.spawn(func1)启动这个协程

    gevent.spawn(func2),

    gevent.spawn(func3),

])



# 运行结果:

# 第一次打印

# 第二次打印

# 第三次打印

# 第四次打印

# 第五次打印

# 第六次打印
自动切换小例子
    • select、epool、pool
      • I/O的实质是什么?
      • I/O的实质是将硬盘中的数据,或收到的数据实现从内核态 copy到 用户态的过程
      • 本文讨论的背景是Linux环境下的network IO。
      • 好比微信读取本地硬盘的过程
        • 微信进程会发送一个读取硬盘的请求----》操做系统
        • 只有内核才可以读取硬盘中的数据---》数据返回给微信程序(看上去就好像是微信直接读取)
      • 用户态 & 内核态
        • 系统空间分为两个部分,一部分是内核态,一部分是用户态的部分
        • 内核态:内核态的空间资源只有操做系统可以访问
        • 用户态:咱们写的普通程序使用的空间

    • select
      • 只能处理1024个链接(每个请求均可以理解为一个链接)
      • 不能告诉用户程序,哪个链接是活跃的
    • pool
      • 只是取消了最大1024个活跃的限制
      • 不能告诉用户程序,哪个链接是活跃的
    • epool
      • 不只取消了1024这个最大链接限制
      • 并且能告诉用户程序哪个是活跃的

    • 猴子补丁
      • monkey patch指的是在运行时动态替换,通常是在startup的时候.
      • 用过gevent就会知道,会在最开头的地方gevent.monkey.patch_all();把标准库中的thread/socket等给替换掉.这样咱们在后面使用socket的时候能够跟日常同样使用,无需修改任何代码,可是它变成非阻塞的了
  • 装饰器
    • 什么是装饰器
      • 装饰器本质是函数,用来给其余函数添加新的功能
      • 特色:不修改调用方式、不修改源代码
    • 装饰器的应用场景
      • 用户认证,判断用户是否登陆
      • 计算函数运行时间(算是一个功能、在项目里用的很少)
      • 插入日志的时候
      • redis缓存
    • 为何使用装饰器
      • 结合应用场景说需求
    • 如何使用装饰器
      • 装饰器求函数运行时间
    • Python 闭包
      • 当一个嵌套函数在其外部区域引用了一个值时,该嵌套函数就是一个闭包。其意义就是会记录这个值。
def A(x):
    def B():
        print(x)
    return B
 deco(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)      #run test1
        stop_time = time.time()
        print("running time is %s"%(stop_time-start_time))
    return deco

# @timer     # test1=timer(test1)
def test1():
    time.sleep(3)
    print("in the test1")
test1()
装饰器求函数运行时间
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import time
def auth(auth_type):
    print("auth func:",auth_type)
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            print("wrapper func args:", *args, **kwargs)
            print('运行前')
            func(*args, **kwargs)
            print('运行后')
        return wrapper
    return outer_wrapper

@auth(auth_type="local") # home = wrapper()
def home():
    print("welcome to home  page")
    return "from home"
home()
三级装饰器
  • 生成器
    • 什么是生成器
      • 生成器就是一个特殊的迭代器
      • 一个有yield关键字的函数就是一个生成器
        • 生成器是这样一个函数,它记住上一次返回时在函数体中的位置。
        • 对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的全部局部变量都保持不变。
        • yield简单说来就是一个生成器,这样函数它记住上次返 回时在函数体中的位置。对生成器第 二次(或n 次)调用跳转至该函 次)调用跳转至该函数。
    • 生成器哪些场景应用
      • 生成器是一个概念,咱们日常写代码可能用的并很少,可是python源码大量使用
      • 好比咱们tornado框架就是基于 生成器+协程
      • 在咱们代码中使用举例
      • 好比咱们要生成一百万个数据,若是用生成器很是节省空间,用列表浪费大量空间
import time
t1 = time.time()
g = (i for i in range(100000000))
t2 = time.time()
lst = [i for i in range(100000000)]
t3 = time.time()
print('生成器时间:',t2 - t1)  # 生成器时间: 0.0
print('列表时间:',t3 - t2)    # 列表时间: 5.821957349777222
    • 为何使用生成器
      • 节省空间
      • 高效
    • 如何使用
#!/usr/bin/python
# -*- coding: utf-8 -*-
def read_big_file_v(fname):
    block_size = 1024 * 8
    with open(fname,encoding="utf8") as fp:
        while True:
            chunk = fp.read(block_size)
            # 当文件没有更多内容时,read 调用将会返回空字符串 ''
            if not chunk:
                break
            print(chunk)
path = r'C:\aaa\luting\edc-backend\tttt.py'
read_big_file_v(path)
  • 迭代器
    • 什么是迭代器
      • 迭代器是访问集合内元素的一种方法
      • 老是从集合内第一个元素访问,直到全部元素都被访问过结束,当调用 __next__而元素返回会引起一个,StopIteration异常
    • 有两个方法:_iter_ _next_
      • _iter_ : 返回迭代器自身
      • _next_: 返回下一个元素
    • 可迭代对象
      • 内置iter方法的,都是可迭代的对象。 list是可迭代对象,dict是可迭代对象,set也是可迭代对象
    • 定义
      • 迭代器:可迭代对象执行iter方法,获得的结果就是迭代器,迭代器对象有next方法
      • 它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了iter和next()方法的对象都是迭代器,iter返回迭代器自身,next返回容器中的下一个值,若是容器中没有更多元素了,则抛出StopIteration异常
      •     

Python 面向对象

  • 什么是面向对象
    • 使用模板的思想,将世界完事万物使用对象来表示一个类型
  • 方法
    • 静态方法
      • 特色:名义上归类管理,实际上不能访问类或者变量中的任意属性或者方法
      • 做用:让咱们代码清晰,更好管理
      • 调用方式: 既能够被类直接调用,也能够经过实例调用
    • 类方法
      • 做用:无需实例化直接被类调用
      • 特性:类方法只能访问类变量,不能访问实例变量
      • 类方法使用场景:当咱们还未建立实例,可是须要调用类中的方法
      • 调用方式:既能够被类直接调用,也能够经过实例调用
    • 属性方法
      • 属性方法把一个方法变成一个属性,隐藏了实现细节,调用时没必要加括号直接d.eat便可调用self.eat()方
    • 魔法方法
      • 单例模式
        • 单例模式:永远用一个对象得实例,避免新建太多实例浪费资源
        • 实质:使用__new__方法新建类对象时先判断是否已经创建过,若是建过就使用已有的对象
        • 使用场景:若是每一个对象内部封装的值都相同就能够用单例模式
class Foo(object):
   instance = None
   def __init__(self):
      self.name = 'alex'

   def __new__(cls, *args, **kwargs):
      if Foo.instance:
         return Foo.instance
      else:
         Foo.instance = object.__new__(cls,*args,**kwargs)
         return Foo.instance

obj1 = Foo()       # obj1和obj2获取的就是__new__方法返回的内容
obj2 = Foo()
print(obj1,obj2)   # 运行结果: <__main__.Foo object at 0x00D3B450>    
<__main__.Foo object at 0x00D3B450>
# 运行结果说明: # 这能够看到咱们新建的两个Foo()对象内存地址相同,说明使用的•同一个类,没有重复创建类
    • __new__
      • 产生一个实例
    • __init__
      • 产生一个对象
    • __del__
      • 析构方法,删除无用的内存对象(当程序结束会自动执行析构方法)

  • 特性
    • 封装
      • 对类中属性和方法进行一种封装,隐藏了实现细节
    • 继承
      • 之类继承父类后,就具备了父类的全部属性和方法,先继承,后重写
    • 多态
      • 一种接口,多种表现形状
      • 中国人、和美国人都能讲话,调用中国人类讲中文,调用美国人类讲英文
    • 新式类经典类的区别
      • pythn3不管新式类仍是经典类都是用 广度优先
      • python2中,新式类:广度优先,经典类:深度优先

class D:
    def talk(self):
        print('D')

class B(D):
    pass
    # def talk(self):
    #     print('B')

class C(D):
    pass
    def talk(self):
        print('C')

class A(B,C):
    pass
    # def talk(self):
    #     print('A')

a = A()
a.talk()
代码
  • 属性
    • 公有属性(类变量)
      • 若是函数、方法或者属性的名称没有以两个下划线开始,则为公有属性;
    • 普通属性(实例变量)
      • 以self做为前缀的属性;
    • 私有属性
      • 函数、方法或者属性的名称以两个下划线开始,则为私有类型;
    • 局部变量
      • 类的方法中定义的变量没有使用self做为前缀声明,则该变量为局部变量;
  • 反射
    • hasattr: 判断当前类是否有这个方法
    • getattr: 经过字符串反射出这个方法的内存地址
    • setattr:将当前类添加一个方法
    • delatrr: 删除实例属性

 

Python 常识概念

  • 深浅拷贝
    • 底层原理
      • 浅copy: 无论多么复杂的数据结构,浅拷贝都只会copy一层
      • deepcopy : 深拷贝会彻底复制原变量相关的全部数据,在内存中生成一套彻底同样的内容,咱们对这两个变量中任意一个修改都不会影响其余变量

    •  可变与不可变数据的区别
      • 可变类型(mutable):列表,字典
      • 不可变类型(unmutable):数字,字符串,元组
      • 这里的可变不可变,是指内存中的那块内容(value)是否能够被改变。若是是不可变类型,在对对象自己操做的时候,必须在内存中新申请一块区域(由于老区域#不可变#)。若是是可变类型,对对象操做的时候,不须要再在其余地方申请内存,只须要在此对象后面连续申请(+/-)便可,也就是它的address会保持不变,但区域会变长或者变短。
      • copy.copy() 浅拷贝
      • copy.deepcopy() 深拷贝
      • 浅拷贝是新建立了一个跟原对象同样的类型,可是其内容是对原对象元素的引用。这个拷贝的对象自己是新的,但内容不是。拷贝序列类型对象(列表\元组)时,默认是浅拷贝。
  • 垃圾回收机制
    • 引用计数
      • 原理
        • 当一个对象的引用被建立或者复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1.
        • 当对象的引用计数减小为0时,就意味着对象已经再没有被使用了,能够将其内存释放掉。
      • 优势
        • 引用计数有一个很大的优势,即实时性,任何内存,一旦没有指向它的引用,就会被当即回收,而其余的垃圾收集技术必须在某种特殊条件下才能进行无效内存的回收。
      • 缺点
        • 引用计数机制所带来的维护引用计数的额外操做与Python运行中所进行的内存分配和释放,引用赋值的次数是成正比的,
        • 显然比其它那些垃圾收集技术所带来的额外操做只是与待回收的内存数量有关的效率要低。
        • 同时,由于对象之间相互引用,每一个对象的引用都不会为0,因此这些对象所占用的内存始终都不会被释放掉。
    • 标记清楚
      • 它分为两个阶段:第一阶段是标记阶段,GC会把全部的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收。
      • 对象之间经过引用(指针)连在一块儿,构成一个有向图
      • 从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。
      • 根对象就是全局变量、调用栈、寄存器。

      • 在上图中,能够从程序变量直接访问块1,而且能够间接访问块2和3,程序没法访问块4和5
      • 第一步将标记块1,并记住块2和3以供稍后处理。
      • 第二步将标记块2,第三步将标记块3,但不记得块2,由于它已被标记。
      • 扫描阶段将忽略块1,2和3,由于它们已被标记,但会回收块4和5。
    • 分代回收
      • 分代回收是创建在标记清除技术基础之上的,是一种以空间换时间的操做方式。
      • Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代)
      • 他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减少。
      • 新建立的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发
      • 把那些能够被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推
      • 老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
  • TCP/UDP
    • TCP/UDP比较
      • TCP面向链接(如打电话要先拨号创建链接);UDP是无链接的,即发送数据以前不须要创建链接
      • TCP提供可靠的服务,也就是说,经过TCP链接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
      • TCP经过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。
      • UDP具备较好的实时性,工做效率比TCP高,适用于对高速传输和实时性有较高的通讯或广播通讯。
      • 每一条TCP链接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通讯
      • TCP对系统资源要求较多,UDP对系统资源要求较少。
      • 注:UDP通常用于即时通讯(QQ聊天 对数据准确性和丢包要求比较低,但速度必须快),在线视频等
    • 三次挥手四次握手
      • 三次握手:
        • 第一次握手:创建链接时,客户端发送SYN包到服务器,其中包含客户端的初始序号seq=x,并进入SYN_SENT状态,等待服务器确认。
        • 第二次握手:服务器收到请求后,必须确认客户的数据包。同时本身也发送一个SYN包,即SYN+ACK包,此时服务器进入SYN_RECV状态。
        • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送一个序列号(seq=x+1),确认号为ack(客户端)=y+1,此包发送完毕,
        • 客户端和服务器进入ESTAB_LISHED(TCP链接成功)状态,完成三次握手。
      • 四次挥手:
        • 第一次挥手:首先,客户端发送一个FIN,用来关闭客户端到服务器的数据传送,而后等待服务器的确认。其中终止标志位FIN=1,序列号seq=u。
        • 第二次挥手:服务器收到这个FIN,它发送一个ACK,确认ack为收到的序号加一。
        • 第三次挥手:关闭服务器到客户端的链接,发送一个FIN给客户端。
        • 第四次挥手:客户端收到FIN后,并发回一个ACK报文确认,并将确认序号seq设置为收到序号加一。首先进行关闭的一方将执行主动关闭,而另外一方执行被动关闭。
    • 相关的问题
      • 为何须要三次握手?
        • 目的:为了防止已失效的链接请求报文段忽然又传送到了服务端,于是产生错误。主要防止资源的浪费。
        • 具体过程:当客户端发出第一个链接请求报文段时并无丢失,而是在某个网络节点出现了长时间的滞留,以致于延误了链接请求在某个时间以后才到达服务器。这应该是一个早已失效的报文段。可是服务器在收到此失效的链接请求报文段后,觉得是客户端的一个新请求,因而就想客户端发出了确认报文段,赞成创建链接。假设不采用三次握手,那么只要服务器发出确认后,新的链接就能够创建了。可是因为客户端没有发出创建链接的请求,所以不会管服务器的确认,也不会向服务器发送数据,但服务器却觉得新的运输链接已经创建,一直在等待,因此,服务器的资源就白白浪费掉了。
      • 若是在TCP第三次握手中的报文段丢失了会出现什么状况?
        • 客户端会认为此链接已创建,若是客户端向服务器发送数据,服务器将以RST包响应,这样就能感知到服务器的错误了。
      • 为何要四次挥手?
        • 为了保证在最后断开的时候,客户端可以发送最后一个ACK报文段可以被服务器接收到。若是客户端在收到服务器给它的断开链接的请求以后,回应完服务器就直接断开链接的话,若服务器没有收到回应就没法进入CLOSE状态,因此客户端要等待两个最长报文段寿命的时间,以便于服务器没有收到请求以后从新发送请求。
        • 防止“已失效的链接请求报文”出如今链接中,在释放链接的过程当中会有一些无效的滞留报文,这些报文在通过2MSL的时间内就能够发送到目的地,不会滞留在网络中。这样就能够避免在下一个链接中出现上一个链接的滞留报文了。
      • 为何TCP链接的时候是3次?2次不能够吗?
        • 由于须要考虑链接时丢包的问题,若是只握手2次,第二次握手时若是服务端发给客户端的确认报文段丢失,此时服务端已经准备好了收发数(能够理解服务端已经链接成功)据,而客户端一直没收到服务端的确认报文,因此客户端就不知道服务端是否已经准备好了(能够理解为客户端未链接成功),这种状况下客户端不会给服务端发数据,也会忽略服务端发过来的数据。
        • 若是是三次握手,即使发生丢包也不会有问题,好比若是第三次握手客户端发的确认ack报文丢失,服务端在一段时间内没有收到确认ack报文的话就会从新进行第二次握手,也就是服务端会重发SYN报文段,客户端收到重发的报文段后会再次给服务端发送确认ack报文。
      • 为何TCP链接的时候是3次,关闭的时候倒是4次?
        • 由于只有在客户端和服务端都没有数据要发送的时候才能断开TCP。而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的。而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我服务端还有一些数据没发完,等这些数据发完了服务端才能给客户端发FIN报文(因此不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。
      • 为何客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP链接?
        • 这里一样是要考虑丢包的问题,若是第四次挥手的报文丢失,服务端没收到确认ack报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL,因此须要等这么长时间来确认服务端确实已经收到了。
      • 若是已经创建了链接,可是客户端忽然出现故障了怎么办?
        • TCP设有一个保活计时器,客户端若是出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会从新复位这个计时器,时间一般是设置为2小时,若两小时尚未收到客户端的任何数据,服务器就会发送一个探测报文段,之后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭链接。

高阶函数html

    • map
      • 第一个参数接收一个函数名,第二个参数接收一个可迭代对象
      • 利用map,lambda表达式将全部偶数元素加100
# -*- coding:utf8 -*-
l1= [11,22,33,44,55]
ret = map(lambda x:x-100 if x % 2 != 0 else x + 100,l1)
print(list(ret))
# 运行结果: [-89, 122, -67, 144, -45]

# lambda x:x-100 if x % 2 != 0 else x + 100
# 若是 "if x % 2 != 0" 条件成立返回 x-100
# 不成立:返回 x+100
def F(x):
    if x%2 != 0:
        return x-100
    else:
        return x+100
ret = map(F,l1)
print(list(ret))
    • reduce
      • 字符串反转
      • 把上一次的执行结果单作参数返回
# -*- coding:utf8 -*-
'''使用reduce将字符串反转'''
s = 'Hello World'
from functools import reduce

result = reduce(lambda x,y:y+x,s)
# # 一、第一次:x=H,y=e  => y+x = eH
# # 二、第二次:x=l,y=eH  => y+x = leH
# # 三、第三次:x=l,y=leH  => y+x = lleH
print( result )      # dlroW olleH
    • filter
      • filter()函数能够对序列作过滤处理,就是说可使用一个自定的函数过滤一个序列,把序列的每一项传到自定义的过滤函数里处理,并返回结果作过滤。
      • 最终一次性返回过滤后的结果。
        • filter()函数有两个参数:
          • 第一个,自定函数名,必须的
          • 第二个,须要过滤的列,也是必须的
      • 利用 filter、lambda表达式 获取l1中元素小于33的全部元素 l1 = [11, 22, 33, 44, 55]
l1= [11,22,33,44,55]
a = filter(lambda x: x<33, l1)
print(list(a))
# -*- coding:utf8 -*-
def F(x):
    if x<33:
        return x
b = filter(F,l1)
print(list(b))
    • sorted
      • 经典面试题只 列表排序
students = [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
print( sorted(students, key=lambda s: s[2], reverse=False) )    # 按年龄排序
# 结果:[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

def f(x):
    # ('john', 'A', 15)
    return x[2]
print( sorted(students, key=f, reverse=False) )    # 按年龄排序
      • 对字典的value排序
d = {'k1':1, 'k3': 3, 'k2':2}
# d.items() = [('k1', 1), ('k3', 3), ('k2', 2)]
a = sorted(d.items(), key=lambda x: x[1])
print(a)            # [('k1', 1), ('k2', 2), ('k3', 3)]
      • 两个列表编一个字典
L1 = ['k1','k2','k3']
L2 = ['v1','v2','v3']
print( list(zip(L1,L2)))
# zip(L1,L2) : [('k1', 'v1'), ('k2', 'v2'), ('k3', 'v3')]
# dict( [('k1', 'v1'), ('k2', 'v2'), ('k3', 'v3')] )  = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
      • 匿名函数
(x,y,z):
    return x+y+z

f = lambda x:x if x % 2 != 0 else x + 100
print(f(10))                    # 110
      • 三元运算
name = 'Tom' if 1 == 1 else 'fly'
print(omname)
# 运行结果: T
  • 读写文件
    • ReadLine():逐行读取,适合读大文件
    • Readlines():一次性读取全部文件, 将文件按行读取成列表
    • read():指定读取指定大小的文件(默认一次读取全部)
    • 经典面试题:如今有一个5G的文件,用python写入另外一个文件里
    • 咱们使用了一个 while 循环来读取文件内容,每次最多读取 8kb 大小
    • 这样能够避免以前须要拼接一个巨大字符串的过程,把内存占用下降很是多
#!/usr/bin/python
# -*- coding: utf-8 -*-
def read_big_file_v(fname):
    block_size = 1024 * 8
    with open(fname,encoding="utf8") as fp:
        while True:
            chunk = fp.read(block_size)
            # 当文件没有更多内容时,read 调用将会返回空字符串 ''
            if not chunk:
                break
            print(chunk)
path = r'C:\aaa\luting\edc-backend\tttt.py'
read_big_file_v(path)
  • 经常使用模块
    • subprocess模块
      • subprocess原理以及经常使用的封装函数
        • 运行python的时候,咱们都是在建立并运行一个进程。像Linux进程那样,一个进程能够fork一个子进程,并让这个子进程exec另一个程序
        • 在Python中,咱们经过标准库中的subprocess包来fork一个子进程,并运行一个外部的程序。
        • subprocess包中定义有数个建立子进程的函数,这些函数分别以不一样的方式建立子进程,因此咱们能够根据须要来从中选取一个使用
        • 另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通讯。
#一、返回执行状态:0 执行成功
retcode = subprocess.call(['ping', 'www.baidu.com', '-c5'])

#二、返回执行状态:0 执行成功,不然抛异常
subprocess.check_call(["ls", "-l"])

#三、执行结果为元组:第1个元素是执行状态,第2个是命令结果
>>> ret = subprocess.getstatusoutput('pwd')
>>> ret
(0, '/test01')

#四、返回结果为 字符串 类型
>>> ret = subprocess.getoutput('ls -a')
>>> ret
'.\n..\ntest.py'


#五、返回结果为'bytes'类型
>>> res=subprocess.check_output(['ls','-l'])
>>> res.decode('utf8')
'总用量 4\n-rwxrwxrwx. 1 root root 334 11月 21 09:02 test.py\n'
subprocess经常使用函数
subprocess.check_output(['chmod', '+x', filepath])
subprocess.check_output(['dos2unix', filepath])
将dos格式文件转换成unix格式
    • subprocess.Popen()
      • 实际上,上面的几个函数都是基于Popen()的封装(wrapper),这些封装的目的在于让咱们容易使用子进程
      • 当咱们想要更个性化咱们的需求的时候,就要转向Popen类,该类生成的对象用来表明子进程
      • 与上面的封装不一样,Popen对象建立后,主程序不会自动等待子进程完成。咱们必须调用对象的wait()方法,父进程才会等待 (也就是阻塞block)
      • 从运行结果中看到,父进程在开启子进程以后并无等待child的完成,而是直接运行print。
#一、先打印'parent process'不等待child的完成
import subprocess
child = subprocess.Popen(['ping','-c','4','www.baidu.com'])
print('parent process')

#二、后打印'parent process'等待child的完成
import subprocess
child = subprocess.Popen('ping -c4 www.baidu.com',shell=True)
child.wait()
print('parent process')
child.wait()等待子进程执行
child.poll()          # 检查子进程状态
child.kill()            # 终止子进程
child.send_signal()       # 向子进程发送信号
child.terminate()           # 终止子进程
    • subprocess.PIPE 将多个子进程的输入和输出链接在一块儿
      • subprocess.PIPE实际上为文本流提供一个缓存区。child1的stdout将文本输出到缓存区,随后child2的stdin从该PIPE中将文本读取走
      • child2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。
      • 注意:communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成
import subprocess
#下面执行命令等价于: cat /etc/passwd | grep root
child1 = subprocess.Popen(["cat","/etc/passwd"], stdout=subprocess.PIPE)
child2 = subprocess.Popen(["grep","root"],stdin=child1.stdout, stdout=subprocess.PIPE)
out = child2.communicate()               #返回执行结果是元组
print(out)
#执行结果: (b'root:x:0:0:root:/root:/bin/bash\noperator:x:11:0:operator:/root:/sbin/nologin\n', None)
分步执行cat /etc/passwd | grep root命
import subprocess

list_tmp = []
def main():
    p = subprocess.Popen(['ping', 'www.baidu.com', '-c5'], stdin = subprocess.PIPE, stdout = subprocess.PIPE)
    while subprocess.Popen.poll(p) == None:
        r = p.stdout.readline().strip().decode('utf-8')
        if r:
            # print(r)
            v = p.stdout.read().strip().decode('utf-8')
            list_tmp.append(v)
main()
print(list_tmp[0])
获取ping命令执行结果
    • paramiko模块(详细介绍
      • Paramiko模块做用
        • 若是须要使用SSH从一个平台链接到另一个平台,进行一系列的操做时,
        • 好比:批量执行命令,批量上传文件等操做,paramiko是最佳工具之一。
        • paramiko是用python语言写的一个模块,遵循SSH2协议,支持以加密和认证的方式,进行远程服务器的链接
        • 因为使用的是python这样的可以跨平台运行的语言,因此全部python支持的平台,如Linux, Solaris, BSD,MacOS X, Windows等,paramiko均可以支持
        • 若是须要使用SSH从一个平台链接到另一个平台,进行一系列的操做时,paramiko是最佳工具之一
        • 如今若是须要从windows服务器上下载Linux服务器文件:
          • a. 使用paramiko能够很好的解决以上问题,它仅须要在本地上安装相应的软件(python以及PyCrypto)
          • b. 对远程服务器没有配置要求,对于链接多台服务器,进行复杂的链接操做特别有帮助。
    • re模块(详细介绍

python

mysql

compile(pattern[, flags])git

根据正则表达式字符串建立模式对象github

search(pattern, string[, flags])golang

在字符串中寻找模式面试

match(pattern, 经常使用模块[, flags])正则表达式

在字符串的开始处匹配模式redis

split(pattern, string[, maxsplit=0])

根据模式的匹配项来分割字符串

findall(pattern, string)

列出字符串中模式的全部匹配项并以列表返回

sub(pat, repl, string[, count=0])

将字符串中全部pat的匹配项用repl替换

escape(string)

将字符串中全部特殊正则表达式字符转义

  • Python2和Python3的区别
    • 不等于<>比较运算符,python3不识别,pyhon2.7中!=和<>都能运行。
    • print函数的使用,python3必须加括号,python2加不加都行。
    • python2 的默认编码是ASCII,python3的默认编码是UTF-8。
    • python3字符串解码后会在内存里自动转换成Unicode,而python2不会。若是在文件头指定了解码编码,python2和python3都会按指定解码,全部系统都支持Unicode,因此python3只要指定对了解码编码,在哪一个系统上均可以正常显示,python2若是不是gbk编码的,解码后windous就会是乱码。
    • python2中有Unicode数据类型,python3中没有,字符串都是Unicode格式的str数据类型。
    • 用户输入不一样,python3中只有input()输出都是str和python2中的raw_input()同样,而python2中也有input(),输入字符串要带引号,数字输出相应的数字类型
    • python2之前没有布尔型,0表示False,用1表示True;Python3 把 True 和 False 定义成关键字,它们的值仍是 1 和 0,能够和数字运算。
    • python2的除法中不是浮点数则只返回商,python3除法返回值正常。
    • python3运行程序能够识别相同目录下普通文件夹中的模块,python2只能识别文件夹标识后的包中的模块。
    • 建立类时,python2分为经典类和新式类,新式类就是继承object的类,经典类是没有继承的类,而python3中所有是新式类,默认继承object。在属性查找时,经典类查找方式为深度优先,新式类是广度优先。仅python3中有类的mro函数方法,输出继承父类的顺序列表。
  • with(上下文管理)
    • 上下文管理器对象存在的目的是管理 with 语句,就像迭代器的存在是为了管理 for 语句同样。
    • with 语句的目的是简化 try/finally 模式。这种模式用于保证一段代码运行完毕后执行某项操做,即使那段代码因为异常、 return 语句或 sys.exit() 调用而停止,也会执行指定的操做。 finally 子句中的代码一般用于释放重要的资源,或者还原临时变动的状态。
    • ==上下文管理器协议包含enter和exit两个方法==。 with 语句开始运行时,会在上下文管理器对象上调用enter方法。 with 语句运行结束后,会在上下文管理器对象上调用exit方法,以此扮演 finally 子句的角色。
    • ==执行 with 后面的表达式获得的结果是上下文管理器对象,把值绑定到目标变量上(as 子句)是在上下文管理器对象上调用enter方法的结果==。with 语句的 as 子句是可选的。对 open 函数来讲,必须加上 as子句,以便获取文件的引用。不过,有些上下文管理器会返回 None,由于没什么有用的对象能提供给用户。
  • is和==比较
    • 基本要素
      • id(身份标识)
      • type(数据类型)
      • value(值)。
    • is和==都是对对象进行比较判断做用的,但对对象比较判断的内容并不相同。
    • ==比较操做符和is同一性运算符区别
    • ==是python标准操做符中的比较操做符,用来比较判断两个对象的value(值)是否相等,例以下面两个字符串间的比较:
# >> a = 'yms'
# >> b = 'yms'
# >> a == b
# True
    • is也被叫作同一性运算符,这个运算符比较判断的是对象间的惟一身份标识,也就是id是否相同。
>> x = y = [4,5,6]
>> z = [4,5,6]
>> x == y
True
>> x == z
True
>> x is y
True
>> x is z
False
>>
>> print id(x)
3075326572
>> print id(y)
3075326572
>> print id(z)
3075328140
    • 前三个例子都是True,这什么最后一个是False呢?x、y和z的值是相同的,因此前两个是True没有问题。至于最后一个为何是False,看看三个对象的id分别是什么就会明白了。
    • 下面再来看一个例子,同一类型下的a和b的(a==b)都是为True,而(a is b)则否则。
>> a = 1 #a和b为数值类型
>> b = 1
>> a is b
True
>> id(a)
14318944
>> id(b)
14318944
>> a = 'cheesezh' #a和b为字符串类型
>> b = 'cheesezh'
>> a is b
True
>> id(a)
42111872
>> id(b)
42111872
>> a = (1,2,3) #a和b为元组类型
>> b = (1,2,3)
>> a is b
False
>> id(a)
15001280
>> id(b)
14790408
>> a = [1,2,3] #a和b为list类型
>> b = [1,2,3]
>> a is b
False
>> id(a)
42091624
>> id(b)
42082016
>> a = {'cheese':1,'zh':2} #a和b为dict类型
>> b = {'cheese':1,'zh':2}
>> a is b
False
>> id(a)
42101616
>> id(b)
42098736
>> a = set([1,2,3])#a和b为set类型
>> b = set([1,2,3])
>> a is b
False
>> id(a)
14819976
>> id(b)
14822256
  • 经过以上三个示例能够看出,只有数值型和字符串型的状况下,a is b才为True,当a和b是tuple,list,dict或set型时,a is b为False

效果图Python基础到此就分解完了,有不足的地方请留言,多多支持,持续更新中哦!!

相关文章
相关标签/搜索