IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> Python高级笔记 -> 正文阅读

[Python知识库]Python高级笔记

简介

  • 上一篇介绍了python的迭代器和协程等知识点
  • 这里重点说一下GIL、方法解析、类属性等问题
  • Python最近又版本更新了,可以关注一下出了哪些新特性
    • 任何一门语言都是一个完整的体系,但因为不同的特性,擅长处理的问题不一样
    • 之前说过语言分两大块,编译型和解释型;如果能深入研究,从底层硬件开始了解,就又是一番风景

GIL锁

  • 之前在协程中提出了GIL问题,可以认为这是当时草率遗留的问题
  • 什么是GIL?先跑一下这段代码:
    import threading
    
    def test():
        while True:	# 子线程死循环
            pass
        
    t = threading.Thread(target = test)
    t.start()
    
    while True:	# 主线程死循环
        pass
    
  • 观察CPU状态,结果如下:
    sp1
    • 两个线程利用率相加占满了一个内核;
  • 变为多进程看一下结果
    import multiprocessing
    
    def process():
        while True:
            pass
        
    p = multiprocessing.Process(target = process)
    p.start()
    
    while True:
        pass
    
  • 可以发现,两个进程分别占用了两个核
    sp2
  • 一般情况下,进程是资源分配的基本单位,线程是处理器调度的基本单位
  • 一个进程拥有多个线程,一个进程的线程可以调用另一个进程的线程,线程让CPU调度开销更小(线程体量小,切换简单)
  • 多核可以让多个进程/线程并行,提高效率
  • 一个进程的多个线程需要争夺某些资源,得到资源的线程需要加锁,这个问题很常见
    • 但CPython解释器解决的方法是直接让其他线程停止工作,让得到资源的某个线程执行
    • 这就是GIL(全局解释器锁),多线程并不能使用多核真正实现并行,只能是单线程并发,现在可以回答问题了
  • 描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因
    • Python语言和GIL没有半毛钱关系。仅仅是由于历史原因在Cpython虚拟机(解释器),难以移除GIL
    • GIL:全局解释器锁。每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码
    • 线程释放GIL锁的情况: 在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL
    • Python 3.x使用计时器(执行时间达到阈值后,当前线程释放GIL)或Python 2.x,tickets计数达到100
    • Python使用多进程是可以利用多核的CPU资源的
    • 多线程爬取比单线程性能还是有提升的,因为遇到IO阻塞会自动释放GIL锁
  • 小结
    • 计算密集型程序建议使用多进程
    • IO密集型可以忽略GIL锁的影响
    • 可以用其他的语言替代多线程中的任务,例如变成C语言
      sp5
      • 通过命令gcc dead_loop.c -shared -o libdead_loop.so 将C代码编译成.so文件
      • python创建子线程,让其执行C语言编写的函数
    • 使用协程代替线程

深浅拷贝

  • 赋值操作有三类:直接赋值(值,无子对象);深拷贝、浅拷贝(都包含子对象

  • 直接赋值:即等号 = ,变量相当于一个指针,指向某一对象(类似浅拷贝)

    a = 1
    b = a
    
    print(id(a))	# 1816822573360
    print(id(b))	# 内存地址相同
    
  • 浅拷贝:如果对象内部不是简单的数值,而是列表之类的对象

    • 浅拷贝只会拷贝父对象,不会拷贝对象内部的子对象;即子对象还是原对象的引用
    >>> a = {1: [1,2,3], 2:'b'}	# 直接赋值		a = dict(1=[1,2,3])
    >>> b = a.copy()	# 字典,包含子对象,可以看出是浅拷贝;看后面
    >>> a, b
    ({1: [1, 2, 3], 2: 'b'}, {1: [1, 2, 3], 2: 'b'})
    >>> a[1].append(4)	# 注:字典不能通过下标索引取值
    >>> a, b
    ({1: [1, 2, 3, 4], 2: 'b'}, {1: [1, 2, 3, 4], 2: 'b'})
    >>> a[2] = 'c'
    ({1: [1, 2, 3, 4], 2: 'c'}, {1: [1, 2, 3, 4], 2: 'b'})	# b没改
    
  • 深拷贝:copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象;即完全独立

    >>>import copy
    >>> a = {1: [1,2,3,4]}
    >>> c = copy.deepcopy(a)	# 深拷贝
    >>> a, c
    ({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
    >>> a[1].append(5)
    >>> a, c
    ({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})
    
    # 也可以通过id()函数
    # CPython 中 id() 函数用于获取对象的内存地址;即对象的唯一标识符,标识符是一个整数
    
  • 总结:可以将拷贝理解为新开辟独立空间,其他都是引用;深浅拷贝都拷贝父对象,但一个指向子对象,一个拷贝子对象

    • Python中很多数据结构的操作都会copy出新对象,而不会在原对象上修改,例如切片
    str = 'RoyKun'
    result = str[0:5:1] # 起始下标:结束下标:步长 (不包含结束下标)
    print(result)   # RoyKu	返回新对象赋值给 result
    
    • 还要注意以下问题:
    import copy
    a = [11,22]
    b = [33,44]
    c = [a,b]	# 直接赋值(引用)
    
    d = c	# 列表没有copy()方法,有子对象,就不是直接赋值,而是浅拷贝
    e = copy.deepcopy(c)
    
    c.append([55,66])	# 不会改变e
    # 但是abc的改变都会影响d
    
  • 小结

    • 字典:有自己的copy()方法(浅拷贝)
    • 函数传参一般都是引用,会修改原对象;如果不想,深拷贝!
    • 应用场景:对数据测试一般先深拷贝,在此备份上操作

私有化

  • Python中没有C++中的public/protected/private等关键字控制作用域和继承方式,所以有了私有化的方式

    xx: 公有变量,类似public
    _x: 单前置下划线,私有化属性或方法,from somemodule import *禁止导入,类对象和子类可以访问,类似protected
    __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到),类似private
    __xx__:双前后下划线,用户名字空间的魔法对象或属性。例如:__init__ , __ 不要自己发明这样的名字,特殊的public
    xx_:单后置下划线,用于避免与Python关键词的冲突,常规自定义方法
    
  • 总结:前置下划线意义特殊

网络编程

  • 主要涉及TCP/UDP/HTTP等协议,使用socket包建立通信的方法

Linux基础

  • Linux基本操作
    • ctrl A 到命令行首
    • ctrl E到命令行末
    • ifconfig查看网络状态
    • mv 文件重命名
    • cp 拷贝文件到
  • vim基本操作
    • 命令模式下:
      • 直接跳转到某行:行号+G
      • 复制光标所在行粘贴到下一行:yyp
      • 跳到行末并进入编辑模式:A
      • 跳到行首:I
      • 选中后剪切:d
      • 粘贴:p
      • 选中后左移:<
      • 在光标行前面插入一行:O
      • 后插入一行:o
    • vim xxx.py +4 打开文件后光标在第四行
  • 计算机网络基础知识

socket通信

  • 完成网络通信必备的东西

  • import socket
    socket.socket(AddressFamily, Type)	 
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # tcp
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)	# udp
    
    s.close()
    # 创建套接字
    # 使用套接字收/发数据
    # 关闭套接字
    
  • 在Linux中使用sublime编程,相关Linux操作可以看总结“Linux面试常考点

  • 实现socket编程(见课件),缺那个Windows测试工具,应该类似postman吧

  • 基于UDP协议发送数据:

    # 要发送数据,必须确定对方端口
    
    from socket import *
    
    # 1. 创建udp套接字
    udp_socket = socket(AF_INET, SOCK_DGRAM)
    
    while True:
        if send_data == "exit":
            break
        # 2. 准备接收方的地址
        # '192.168.1.103'表示目的ip地址
        # 8080表示目的端口
        dest_addr = ('192.168.1.103', 7788)  # 注意 是元组,ip是字符串,端口是数字
    
        # 3. 从键盘获取数据(准备数据)
        send_data = input("请输入要发送的数据:")
    
        # 4. 发送数据
        udp_socket.sendto(send_data.encode('utf-8'), dest_addr)
    
    # 5. 关闭套接字
    udp_socket.close()
    
    # ubuntu下运行程序使用:
    	roy@ubuntu:$ python3 xxx.py 
    
  • 在发送之前ping一下,看网络通不通;虚拟机要改成桥接模式,如果此时IP还是不在同一网段,使用命令sudo dhclient,静静等待;

  • 错误提示TypeError: need is object not str意思就是别发字符串;解决方案:在字符串前面写个b,即变成字节对象;或者如代码所示,encode

  • 在ubuntu中python3ipython3都是交互模式,后者类似jupyter;

  • UDP接收数据:

    # 要接收数据,必须确定自身端口,需要绑定,这个IP不写表示要绑定任意IP的此端口,这里就应该是这台服务器的IP
    
    # 程序不是从第一行开始写的
    
    from socket import *
    
    def main():
        # 1. 创建套接字
        udp_socket = socket(AF_INET, SOCK_DGRAM)
        # 2. 绑定端口(本地信息)
        local_addr = ('', 7788)
        udp_socket.bind(local_addr)
        # 3. 接收数据
        recv_data = udp_socket.recvfrom(1024)  # 1024表示本次接收的最大字节数
        	# 如果没有收到数据会在此处 阻塞
        send_addr = recv_data[0]
        recv_msg = recv_data[1]
        # 4. 打印接收到的数据
        print("%s:%s"%(str(send_addr), recv_msg.decode("gbk")))	# 从Windows来的数据要用gbk解码
        # 5. 关闭套接字
        udp_socket.close()
    # 从这开始写代码
    if __name__ == "__main__":
        main()
    
  • 小结:

    • UDP协议的特点是不需要建立连接,只需要知道对方的IP和端口即可,数据的正确性依靠服务端校验
    • 发送方和接收方各自搞清楚必要参数,发送方是对方的(IP,端口),接收方是自身(IP,端口);套接住咯,诶~
    • 为何说“必要”参数呢?因为无论是作为发送方还是接收方,都需要有自己的端口才能发送数据的(tcp和udp是端对端的),但在这里发送方没有bind,所以OS会随机分配一个端口;
    • 因为端口是套接字的对接目标,所以在同一电脑(IP)上的不同程序(端口)可以互发数据,“互发”意思就是创建一个套接字即可,可收可发;(将发送接收程序写在一起)
    • 套接字是全双工的;
  • 聊天器

    • 程序在接收数据时若没有缓存消息,一闪而过,即OS会暂存收到的消息;这也存在弊端,可能会缓存过多信息,占用内存导致死机;(由于现在是单任务,只能实现半双工)
      1
  • 这里发送IP可以写127.0.0.1回环地址,自己发自己收;也可以查看ubuntu自身IP,实现自娱自乐;

TCP通信

  • UDP不安全,类似写信;TCP类似打电话,需要连接,有确认机制

    • 三次握手和四次挥手
  • TCP有拥塞控制和可靠传输机制

    • 拥塞控制:指数递增线性增长、快开始
    • 可靠传输包括:超时重传、错误校验
  • TCP是严格的客户服务器模式

    • 客户端:需要链接
       from socket import *
       
       # 创建socket
       tcp_client_socket = socket(AF_INET, SOCK_STREAM)	# TCP
       
       # 服务器信息
       server_ip = input("请输入服务器ip:")
       server_port = int(input("请输入服务器port:"))
       
       # 链接服务器
       tcp_client_socket.connect((server_ip, server_port))
       
       # 提示用户输入数据
       send_data = input("请输入要发送的数据:")
       
       # 先发(请求)
       tcp_client_socket.send(send_data.encode("gbk"))
       
       # 接收对方发送过来的数据,最大接收1024个字节
       recvData = tcp_client_socket.recv(1024)
       print('接收到的数据为:', recvData.decode('gbk'))
       
       # 关闭套接字
       tcp_client_socket.close()
    
    • 服务器:要绑定,再运行
    from socket import *
    
    # 创建socket
    tcp_server_socket = socket(AF_INET, SOCK_STREAM)
    
    # 本地信息
    address = ('', 7788)	# tuple
    # 绑定
    # 绑不绑定要看是否接收数据,如果只发送,不绑也行
    tcp_server_socket.bind(address)
    
    # 使用socket创建的套接字默认是主动的,
    # 使用listen将其变为被动的,接收别人的链接,可套接
    tcp_server_socket.listen(128)	# 监听套接字负责等待有新客户链接
    
    # accept()负责产生一个新的套接字 client_socket 专门为这个客户端服务
    # clientAddr 即“来电显示”
    client_socket, clientAddr = tcp_server_socket.accept()	# 默认阻塞,等待客户端connect()
    
    # 接收对方发送过来的数据(先收)
    recv_data = client_socket.recv(1024)  # 函数的写法和UDP不同
    print('接收到的数据为:', recv_data.decode('gbk'))
    
    # 发送一些数据到客户端
    client_socket.send("thank you !".encode('gbk'))
    
    # 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接
    client_socket.close()
    
    tcp_server_socket.close()
    
    • 区别在于,服务器端会产生两个套接字,分别用于监听和收发数据;
    • 这里,服务器端要先收数据(响应),客户端要先发数据(请求);然后互相收发;
  • 和UDP的主要区别:

    • 严格的客户服务器模式,分离
    • 需要链接,而不是简单的告知(IP+端口),保证数据传输的质量
      3

TCP文件下载器

  • 客户端代码

    from socket import *
    
    def main():
        # 创建socket
        tcp_client_socket = socket(AF_INET, SOCK_STREAM)
    
        # 目的信息
        server_ip = input("请输入服务器ip:")	# 这个可以绑死
        server_port = int(input("请输入服务器port:"))
    
        # 链接服务器
        tcp_client_socket.connect((server_ip, server_port))
    
        # 输入需要下载的文件名
        file_name = input("请输入要下载的文件名:")
    
        # 发送文件下载请求(先建立连接,发请求,再收!)
        tcp_client_socket.send(file_name.encode("utf-8"))
    
        # 接收对方发送过来的数据,最大接收1024个字节(1K)
        recv_data = tcp_client_socket.recv(1024)
        # print('接收到的数据为:', recv_data.decode('utf-8'))
        # 如果接收到数据再创建文件,否则不创建
        if recv_data:
            # 由于读写期间可能会出异常,需要捕获,with的作用是不用手动捕获并close
            with open("[接收]"+file_name, "wb") as f:
                f.write(recv_data)
                # with一般用于'w'模式,若要读取文件,加上try...except...捕获
    
        # 关闭套接字
        tcp_client_socket.close()
        
    if __name__ == "__main__":
        main()
    
  • 服务端:

    from socket import *
    import sys
    
    def get_file_content(file_name):
        """获取文件的内容"""	# 函数注释
        try:	# 可能文件不存在,这是读写文件的标准写法
            with open(file_name, "rb") as f:
                content = f.read()
            return content
        except:
            print("没有下载的文件:%s" % file_name)
    
    # 运行程序即启动服务器,输入参数:[0]是此程序文件名,[1]是端口号
    def main():
        # sys.argv[]其实就是一个列表,里边的项为用户输入的参数,且参数是从程序外部输入的,例如:python3 test.py a b c		# abc就是外部输入参数,用列表接收
        if len(sys.argv) != 2:
            # 保证输入了端口参数
            print("请按照如下方式运行:python3 xxx.py 7890")
            return
        else:
            # 运行方式为python3 xxx.py 7890
            port = int(sys.argv[1])
    
        # 创建socket
        tcp_server_socket = socket(AF_INET, SOCK_STREAM)
        # 本地信息
        address = ('', port)
        # 绑定本地信息(需要接收客户端信息)
        tcp_server_socket.bind(address)
        # 将主动套接字变为被动套接字
        tcp_server_socket.listen(128)	# 决定可以有多少个客户端连接,涉及到高并发了
    
        while True:	# 服务端继续运行
            # 等待客户端的链接,即为这个客户端发送文件
            client_socket, clientAddr = tcp_server_socket.accept()  # 阻塞
            # 接收对方发送过来的数据
            recv_data = client_socket.recv(1024)  # 接收1024个字节; 阻塞
            file_name = recv_data.decode("utf-8")
            print("对方请求下载的文件名为:%s" % file_name)
            file_content = get_file_content(file_name)
            # 发送文件的数据给客户端
            # 因为获取打开文件时是以rb方式打开,因此不需要encode编码
            if file_content:
                client_socket.send(file_content)
            # 关闭这个套接字
            client_socket.close()
    
        # 关闭监听套接字
        tcp_server_socket.close()
    
    if __name__ == "__main__":
        main()
    
  • TCP注意点:
    4

  • 关闭监听套接字,已经建立连接的accept套接字不会断的哦;

  • 服务端recv套接字解阻塞有两种方式:客户端关闭(挂电话)、服务端收到数据

  • 结合多任务可以实现服务多个用户

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2021-09-01 11:52:42  更:2021-09-01 11:54:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/26 23:23:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计