标签: python

  • TF-IDF (词频-逆文件频率)简介。

    简介

    TF-IDF(Term Frequency-Inverse Document Frequency)是一种在信息检索和文本挖掘中广泛使用的统计方法,用于评估一个词在一个文档中的重要性。它结合了两个关键概念:词频(Term Frequency, TF)和逆文档频率(Inverse Document Frequency, IDF)。

    词频 (Term Frequency, TF)

    定义:词频是指某个词在文档中出现的次数。为了防止文档长度对结果的影响,通常会将词频进行归一化处理。

    公式:
    \[ \text{TF}(t, d) = \frac{\text{词t在文档d中出现的次数}}{\text{文档d中所有词的总数}} \]

    例如,如果词 `apple` 在文档 `d` 中出现了5次,而文档 `d` 总共有100个词,那么 `apple` 的TF值为:
    \[ \text{TF}(apple, d) = \frac{5}{100} = 0.05 \]

    逆文档频率 (Inverse Document Frequency, IDF)

    **定义**:逆文档频率衡量了一个词在整个文档集合中的普遍重要性。如果一个词在很多文档中都出现,那么它的IDF值会较低;反之,如果一个词只在少数文档中出现,那么它的IDF值会较高。

    **公式**:
    \[ \text{IDF}(t) = \log\left(\frac{\text{总文档数}}{\text{包含词t的文档数} + 1}\right) \]

    其中,加1是为了防止分母为零的情况。

    例如,假设总文档数为1000,词 `apple` 出现在100个文档中,那么 `apple` 的IDF值为:
    \[ \text{IDF}(apple) = \log\left(\frac{1000}{100 + 1}\right) = \log(9.90099) \approx 0.996 \]

    TF-IDF

    **定义**:TF-IDF是词频和逆文档频率的乘积,用来表示一个词在文档中的重要性。

    **标准公式**:
    \[ \text{TF-IDF}(t, d) = \text{TF}(t, d) \times \text{IDF}(t) \]

    公式推导

    综合上述两个部分,TF-IDF的完整公式如下:

    \[ \text{TF-IDF}(t, d) = \left( \frac{\text{词t在文档d中出现的次数}}{\text{文档d中所有词的总数}} \right) \times \log\left(\frac{\text{总文档数}}{\text{包含词t的文档数} + 1}\right) \]

     示例

    假设我们有一个文档集合,总共有1000个文档。考虑以下情况:

    • 文档 `d` 中有100个词。
    • 词 `apple` 在文档 `d` 中出现了5次。
    • 词 `apple` 出现在100个文档中。

    计算 `apple` 在文档 `d` 中的TF-IDF值:

    • 计算TF
      \[ \text{TF}(apple, d) = \frac{5}{100} = 0.05 \]
    • 计算IDF
      \[ \text{IDF}(apple) = \log\left(\frac{1000}{100 + 1}\right) = \log(9.90099) \approx 0.996 \]
    • 计算TF-IDF
      \[ \text{TF-IDF}(apple, d) = 0.05 \times 0.996 \approx 0.0498 \]

    变体和优化

    虽然上面介绍的是标准的TF-IDF公式,但在实际应用中,可能会有一些变体和优化:

    • 平滑处理: 为了防止IDF值过高,有时会在IDF公式中添加一个小常数 \( k \):
      \[ \text{IDF}(t) = \log\left(\frac{\text{总文档数}}{\text{包含词t的文档数} + k}\right) \]
      常见的 \( k \) 值是1。
    • 不同的对数底数: 有些实现使用自然对数(以 \( e \) 为底),而有些使用以10或2为底的对数。选择不同的底数会影响IDF值的具体数值,但不会改变其相对大小。
    • 词频归一化: 除了简单的频率外,还可以使用其他方式来归一化词频,例如平方根归一化:
      \[ \text{TF}(t, d) = \sqrt{\frac{\text{词t在文档d中出现的次数}}{\text{文档d中所有词的总数}}} \]

    应用

    TF-IDF广泛应用于各种文本分析任务,包括但不限于:

    **信息检索**:提高搜索结果的相关性。
    **文本分类**:识别文档的主题或类别。
    **关键词提取**:找出文档中的重要词汇。
    **文档相似度计算**:比较不同文档之间的相似度。

     

  • Python Scrapy 爬虫框架

    Scrapy 相关细节

    • 安装scrapy框架
      • pip install scrapy -i https://pypi.douban.com/simple
    • 创建scrapy爬虫
      • 进入项目文件夹
        • cd project_name/project_name/spiders
      • 运行命令
        • scrapy startproject project_name url
        • D:\Practice\Python\Scrapy_20230226\scrapy_carhome\scrapy_carhome\spiders> scrapy genspider car https://car.autohome.com.cn/price/brand-15.html
    • 运行爬虫
      • 进入当前爬虫所在文件夹
        • scrapy crawl spider_name
        • D:\Practice\Python\Scrapy_20230226\scrapy_carhome\scrapy_carhome\spiders> scrapy crawl car

     

     

     

    Scrapy 数据流程

    https://docs.scrapy.org/en/latest/topics/architecture.html

    1. 引擎得到来自于Spider的url请求
    2. 引擎调用调度器将待爬取url放入列队中
    3. 调度器返回请求给引擎
    4. 引擎发送请求给下载器
    5. 下载器获取互联网资源, 获取数据后返回给引擎
    6. 引擎收到来自下载器的响应, 将其发送给Spider, 以进行处理
    7. Spider返回处理过的数据给引擎(同时也发送下一个新的URL给引擎)
    8. 引擎发送处理过的数据给Pipelines, 以保存数据
    9. 重复第三步之后的步骤, 直到没有新的任务

     

     

    The data flow in Scrapy is controlled by the execution engine, and goes like this:

    1. The Engine gets the initial Requests to crawl from the Spider.
    2. The Engine schedules the Requests in the Scheduler and asks for the next Requests to crawl.
    3. The Scheduler returns the next Requests to the Engine.
    4. The Engine sends the Requests to the Downloader, passing through the Downloader Middlewares (see process_request()).
    5. Once the page finishes downloading the Downloader generates a Response (with that page) and sends it to the Engine, passing through the Downloader Middlewares (see process_response()).
    6. The Engine receives the Response from the Downloader and sends it to the Spider for processing, passing through the Spider Middleware (see process_spider_input()).
    7. The Spider processes the Response and returns scraped items and new Requests (to follow) to the Engine, passing through the Spider Middleware (see process_spider_output()).
    8. The Engine sends processed items to Item Pipelines, then send processed Requests to the Scheduler and asks for possible next Requests to crawl.
    9. The process repeats (from step 3) until there are no more requests from the Scheduler.

     

     

  • Python 环境问题

    Pycharm

    Anaconda

    Conda

    创建Conda虚拟环境

    • 指定python版本和环境名称
      • conda create -n env_name python=3.8
    • 指定python版本, 环境名称, 环境安装路径
      • conda create –prefix=C:/ProgramData/Anaconda3/envs/ENV_spider python=3.9
    • 激活所安装的环境
      • conda activate env_name
    • 在当前激活的环境里安装包
      • conda install package_name
    • 退出当前激活的环境
      • conda deactivate

    PIP

     

    Jupyter

    安装内核

    • 激活/切换到要添加的虚拟环境, 确认是否安装 ipykernel
      • python -m ipykernel –version
    • 如果没有,则安装
      • python -m pip install ipykernel
    • 为 jupyter 添加内核
      • python -m ipykernel install –name env_name

    查看已安装内核

    • jupyter kernelspec list
    (ENV_machinelearning) λ jupyter kernelspec list
    Available kernels:
      %s    %s python3             C:\ProgramData\Anaconda3\envs\ENV_machinelearning\share\jupyter\kernels\python3
      %s    %s pycharm-78712231    C:\Users\Jin\AppData\Roaming\jupyter\kernels\pycharm-78712231
      %s    %s pycharm-ae39b4c1    C:\Users\Jin\AppData\Roaming\jupyter\kernels\pycharm-ae39b4c1
      %s    %s env_machinelearning C:\ProgramData\jupyter\kernels\env_machinelearning

     

    删除 jupyter 内核

    • jupyter kernelspec remove kernelname

     

     

     

  • Django Rest Framework

    Web应用开发模式

    前后端分离

     

    通俗地讲, 其实判定很简单:如果前端和后端这两个角色, 只通过API 文档就能进行数据交流,就说明他们的逻辑是分离的。我们可以称之为 “前后端代码分离”。

    如果除了 API 文档之外还需要各种其他的数据交流方式,比如后端把数据藏在一个 div 的属性里,那么就不是前后端分离的。像各种框架里的模板引擎和渲染功能。

    至于所谓的 “最佳实践”并不存在, 但通用的, 类似的方案如下:

    • 前端服务器
      • 将前端的所有代码使用javascript+node.js的方式部署在node.js服务器上,作为前端稳定的服务,提供页面使用。
    • 后端服务器
      • 后端的服务器写各种业务逻辑代码, 如业务逻辑, 鉴权逻辑.
    • 前后端交互
      • 后端服务为前端提供一个统一的访问入口(一般是单独部署一个服务),使用restful等风格提供http服务供前端调用,实现前后端数据传输(通常以JSON来交换数据)。

     

     

    这样一来, 现在 前端服务器不再处理任何业务,它接收到请求后,经过转换,发送给各个相关后端服务器,将各个后端服务器返回的,处理过的业务数据填入 HTML 模板,最后发送给浏览器。前端服务器和后端服务器间,可以选用任何你觉得合适的通信手段,可以是 REST,可以是 RPC,选用什么样的通信手段,这是另一个议题了。

    前后端分离, 是浏览器和后端服务分离吗?

    不是,前后端分离里的前端不是浏览器,指的是生成 HTML 的那个服务,它可以是一个仅仅生成 HTML 的 Web 服务器,也可以是在浏览器中通过 JS 动态生成 HTML 的 单页应用。

     

    实践中,有实力的团队往往在实现前后端分离里时,前端选用 node 服务器,后端选用 C#、Java 等。

    API接口规范

     

    Restful

    • 基于http协议
    • 传统的路由设计
      • 增: localhost.com/user/add
      • 删: localhost.com/user/delete/1
      • 改: localhost.com/user/edit/1
      • 查: localhost.com/user/query/1
    • Restful的设计规范, 基于资源, 资源操作由请求方式决定
    • 比如 api.localhost/users/
      • 增: post
      • 删: delete
        • 删除单个资源: api.localhost.com/users/1
        • 删除所有资源: localhost/users/
      • 改:
        • put: 更新单个资源
        • patch: 更新局部/部分资源
      • 查: get
        • 查单个资源: api.localhost.com/users/1
        • 查所有资源: api.localhost.com/users/
      • 过滤,通过在url上传参的形式传递搜索条件
        • https://api.localhost.com/v1/zoos?limit=10:指定返回记录的数
        • https://api.localhost.com/v1/zoos?offset=10:指定返回记录的开始位置
        • https://api.localhost.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数
        • https://api.localhost.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
        • https://api.localhost.com/v1/zoos?animal_type_id=1:指定筛选条件

    RPC

    • 性能更高
    • 基于TCP/IP协议(传输层)

    序列化

    什么是序列化?

    序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。

    • 序列化:把对象转化为可传输的字节序列过程称为序列化。
    • 反序列化:把字节序列还原为对象的过程称为反序列化。

     

    为什么要序列化?

    如果光看定义我想你很难一下子理解序列化的意义,那么我们可以从另一个角度来推导出什么是序列化, 那么究竟序列化的目的是什么?

    其实序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组

     

    在大多数开发者, 所谓序列化是指数据在 对象和json之前的相互转换.

     

     

    Django Rest Framework

    DRF的安装

    创建新环境

    conda create –prefix=C:/ProgramData/Anaconda3/envs/ENV_DRF_01 python=3.9

    – 激活环境

    • conda activate C:\ProgramData\Anaconda3\envs\ENV_DRF_01

    安装django

    • conda install django=3.2.*

    创建django项目

    • django-admin startproject myshop

    安装drf, drf是基于django的一个包

    • pip install djangorestframework
    • pip install pymysql

    创建django项目内的APP

    • python manage.py startapp LearnDRF

    进行django配置

    • 数据库
    • INSTALLED_APPS

     

    DRF快速体验

    创建并配置数据库

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'learndrf',  # 数据库名字
            'USER': 'root',
            'PASSWORD': 'zxcvbnm',
            'HOST': '127.0.0.1',  # 那台机器安装了MySQL
            'PORT': 3306,
        }
    }

    模型类

    class Student(models.Model):
        # 模型字段
        name = models.CharField(max_length=100,verbose_name="姓名")
        sex = models.BooleanField(default=1,verbose_name="性别")
        age = models.IntegerField(verbose_name="年龄")
        class_null = models.CharField(max_length=5,verbose_name="班级编号")
        description = models.TextField(max_length=1000,verbose_name="个性签名")
    
        class Meta:
            db_table="tb_student"
            verbose_name = "学生"
            verbose_name_plural = verbose_name

    执行迁移

    python manage.py makemigrations
    python manage.py migrate

    可以添加一些数据, 方便测试.

     

    FBV 和CBV

    FBV

    fbv是指function based view, 望文生义, 义如其名.

    fbv 模式的路由

    path(‘login_fbv/’, login)

    • login是view.py的login函数.
    CBV

    path(‘student/’, StudentView.as_view())

    • StudentView是view.py的类
    • as_view()是drf包里面视图方法

     

    DRF的使用

    DRF的优势?

    谈到DRF的使用, 很容易会产生一堆问题? DRF有什么作用? 通常用于哪些场景? 它的优势是什么? 为什么用DRF, 而不是django?

    一句话回答: DRF可以更快, 更高效的完成REST API开发.

    DRF如何实现这些优势的?

    DRF包含一系列高度抽象和功能丰富的组件

    序列化组件

    1.作用: 把python中的对象,转成json格式字符串

    使用步骤1: 写一个类继承Serializer或者ModelSerializer
    举例(类中选取字段进行序列化):
    class BookSerializer(serializers.Serializer):
    id = serializers.CharField()
    title = serializers.CharField()
    price = serializers.CharField()
    举例(把类中字段全部进行序列化):

    class TestSer(serializers.ModelSerializer):
    class Meta:
    model = models.Takes
    fields = ‘__all__’
    总结:
    变量名和source指定的值不能一样

    source=’publish.name’还支持继续 .

    source 还支持方法(没用)

    支持写方法,如下
    方法一定传一个参数,是当前book对象

    publish_dic=serializers.SerializerMethodField()
    def get_publish_dic(self,obj):
    return
    结果:{‘id’:obj.publish.pk,’name’:obj.publish.name}

    认证组件

    1.认证组件的好处:
    比如要访问books/路径,必须登录之后才能访问。一旦登录成功,在响应结果中写一个随机字符串

    举例: {status:100
    msg:登录成功
    token:sdafsdfasd
    }

    2.使用步骤1:写一个类,继承BaseAuthentication

    3.使用步骤2:def authenticate(self,request) ,记住传request对象

    4.在视图类中使用:(不要加括号):

    1.局部使用:authentication_classes=[AuthLogin](写在views中)

    2.全局使用:-REST_FRAMEWORK=
    {“DEFAULT_AUTHENTICATION_CLASSES”:[“app01.auth.AuthLogin”,](在setting中配置)

    3.全局使用的局部禁用:authentication_classes = [](写在views中)

     

    权限组件

    1.使用步骤1:写一个类,继承BasePermission

    2.使用步骤2:def has_permission(self, request, view): ,记住传
    request对象和view

    3.在视图类中使用:(不要加括号):

    1.局部使用:permission_classes=[MyPer](写在views中)

    2.全局使用:-REST_FRAMEWORK={“DEFAULT_PERMISSION_CLASSES”:[‘app01.auth.MyPer’]}(在setting中配置)

    3.全局使用的局部禁用:permission_classes = [](写在views中)

     

    节流组件

    1.使用步骤1:写一个类,继承SimpleRateThrottle

    2.使用步骤2:def get_cache_key(self, request, view):,记住传request对象和view

    3.使用步骤3
    :’DEFAULT_THROTTLE_RATES’: {
    这个key跟scope对应,value值3/m 3/h 4/d
    ‘xx’: ‘3/m’
    }(在setting中配置)

    节流作用:节流(Throttling)类似于权限,因为它决定了是否应该对请求进行授权。节流表示一个临时状态,并用于控制客户端对API的请求率。

    视图组件

    使用:
    1.局部使用:throttle_classes = [VisitThrottle](写在views中)
    2.全局使用:REST_FRAMEWORK={“DEFAULT_THROTTLE_CLASSES”:[“app01.auth.VisitThrottle”]}(在setting中配置)
    3.全局使用的局部禁用:throttle_classes = [](写在views中)

    视图作用: 数据库查询, 2. 构建序列化器, 进行序列化操作, 返回数据

     

    解析器

    1.局部使用:parser_classes=[JSONParser,](写在views中)
    2.全局使用:’DEFAULT_PARSER_CLASSES’:[‘rest_framework.parsers.JSONParser’](在setting中配置)

    解析器作用:解析器的作用就是服务端接收客户端传过来的数据,把数据解析成自己可以处理的数据。本质就是对请求体中的数据进行解析。

     

    渲染器

    DRF提供的渲染器有很多,默认是
    ‘DEFAULT_RENDERER_CLASSES’: (
    ‘rest_framework.renderers.JSONRenderer’,
    ‘rest_framework.renderers.BrowsableAPIRenderer’,
    ),

    渲染器的作用:渲染器同解析器相反,它定义了框架按照content_type来返回不同的响应。

     

    分页组件

    1.路由

    urlpatterns = [
    re_path(‘(?P<version>[v1|v2]+)/page1/’, Pager1View.as_view(),) #分页1
    ]
    序列化

    from rest_framework import serializers
    from api import models
    class PagerSerialiser(serializers.ModelSerializer):
    class Meta:
    model = models.Role
    fields = “__all__”
    视图

    from api.utils.serializsers.pager import PagerSerialiser
    from rest_framework.response import Response
    from rest_framework.pagination import PageNumberPagination
    class Pager1View(APIView):
    def get(self,request,*args,**kwargs):
    #获取所有数据
    roles = models.Role.objects.all()
    #创建分页对象
    pg = PageNumberPagination()
    #获取分页的数据
    page_roles = pg.paginate_queryset(queryset=roles,request=request,view=self)
    #对数据进行序列化
    ser = PagerSerialiser(instance=page_roles,many=True)
    return Response(ser.data)
    settings配置

    REST_FRAMEWORK = {
    #分页
    “PAGE_SIZE”:2 #每页显示多少个
    }

    版本组件

    自定义版本处理

    class ParamVersion(object):
    def determine_version(self, request, *args, **kwargs):
    # version = request._request.GET.get(“version”)
    # version = request.query_params.get(‘version’)
    version = request.GET.get(“version”)
    return version

    class UsersView(APIView):
    versioning_class = ParamVersion

    def get(self, request, *args, **kwargs):
    version = request.version
    return JsonResponse({‘version’: version})

    路由组件

    使用方法

    # format参数是给渲染器用的
    url(r’^(?P<version>[v1|v2]+)/myview\.(?P<format>\w+)$’, MyView.as_view({“get”: “list”, “post”: “create”})),
    创建router对象,并注册视图集。

    from rest_framework import routers
    router = routers.DefaultRouter()
    router.register(r’myview’, MyView)

    最原始的 Serializer + APIView

    2. ModelSerializer + APIView

     

    序列化和视图类的使用

    前置了解

    视图类

    • APIView
      • 继承APIView父类(Django中View的子类)
      • 具备View的所有特性
      • 提供了认证、授权、限流等功能
    • GenericAPIView
      • GenericAPIView为APIView的子类,它拓展了过滤、查询、分页的功能

    序列化器

    • Serializer
    • ModelSerializer

    这里将会通过以下三种组合来熟悉DRF的写法.

    DRF的 Serializer + DRF的 APIView

    DRF的 ModelSerializer + DRF的 APIView

    DRF的 ModelSerializer + DRF的 GenericAPIView

     

    Serializer + APIView

    path('student/', StudentView.as_view()), #drf的as_view方法
    re_path('student/(\d+)/', StudentDetailView.as_view()), #drf的as_view方法

     

    class StudentView(APIView):
    
    
        def get(self, request):
            print(request.GET)
            print(request.query_params)
            students = Student.objects.all()
            print(type(students))
            # students 是一个queryset 数据集. 所谓的序列化就是对该数据再进行封装, 这里其实是for循环, 将里面的数据都拿到,
    
            serializer = StudentsSerializer(instance=students, many=True)
            return Response(serializer.data)
    
    
    
        def post(self, request):
            print(request)
            print(request.data)
            print(type(request.data))
    
            serializer = StudentsSerializer(data=request.data)
            if serializer.is_valid():
                stu = Student.objects.create(**serializer.validated_data)
                ser = StudentsSerializer(instance=stu, many=False)
                return Response(ser.data)
    
            else:
                 return Response(serializer.errors)
    
    
    
    class StudentDetailView(APIView):
    
        print('StudentDetailView')
        def get(self, request, id):
            students = Student.objects.get(pk=id)
            serializer = StudentsSerializer(instance=students, many=False)
            return Response(serializer.data)
    
    
        def delete(self, request, id):
            Student.objects.get(pk=id).delete()
    
            return Response("已删除")
    
        def put(self, request, id):
            print(request)
            print(request.data)
            print(type(request.data))
    
            serializer = StudentsSerializer(data=request.data)
            if serializer.is_valid():
                print(serializer.validated_data)
    
                # 成功修改数据的条数, 为1
                # n = Student.objects.filter(pk=id).update(**serializer.validated_data)
                print(n)
    
                #要修改的数据对象
                stu = Student.objects.get(id=id)
                print(id)
                ser = StudentsSerializer(instance=stu, many=False)
                print(ser.data)
                return Response(ser.data)
    
            else:
                return Response(serializer.errors)

    get 查询所有数据

    • http://127.0.0.1:8000/student/

    post 添加数据

    • http://127.0.0.1:8000/student/

    delete 删除单条数据

    • http://127.0.0.1:8000/student/1/

    put 修改单条数据

    • http://127.0.0.1:8000/student/1/

    get 查询单条数据

    • http://127.0.0.1:8000/student/1/

    ModelSerializer + APIView

    path('publish/', PublishView.as_view()),
    re_path('publish/(\d+)/', PublishDetailView.as_view()),

     

    class PublishSerializer(serializers.ModelSerializer):
        class Meta:
            model = Publish
            # fiedls = "__all__"
            fields = ["name", "email"]
    
    
    class PublishView(APIView):
        def get(self, reqeust):
            publishes = Publish.objects.all()
            ps = PublishSerializer(instance=publishes, many=True)
            return Response(ps.data)
    
        def post(self, request):
            print(request.data, type(request.data))
    
            serializer = PublishSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save()  # create方法 将符合条件的数据插入到数据库
                return Response(serializer.data)
            else:
                return Response(serializer.errors)
    
    
    class PublishDetailView(APIView):
    
        def get(self, reqeust, id):
            publish = Publish.objects.get(pk=id)
            ps = PublishSerializer(instance=publish, many=False)
            return Response(ps.data)
    
        def delete(self, request, id):
            Publish.objects.get(pk=id).delete()
            return Response()
    
        def put(self, request, id):
            # post请求
            instance = Publish.objects.get(pk=id)
            serializer = PublishSerializer(data=request.data, instance=instance)
    
            if serializer.is_valid():
                serializer.save()  # instance的 update
                return Response(serializer.data)
    
            else:
                return Response(serializer.errors)

     

     

    ModelSerializer + GenericAPIView

    path('author/', AuthorView.as_view()),
    re_path('author/(?P<pk>\d+)/', AuthorDetailView.as_view()),

     

    from rest_framework.generics import GenericAPIView
    
    
    class AuthorSerializer(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = ["name", "age"]
    
    
    class AuthorView(GenericAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    
        def get(self, reqeust):
            # serializer = self.serializer_class(instance=self.queryset,many=True)
            serializer = self.get_serializer(instance=self.get_queryset(), many=True)
    
            return Response(serializer.data)
    
        def post(self, request):
    
            serializer = self.get_serializer(data=request.data)
    
            if serializer.is_valid():
                serializer.save()  # create方法 将符合条件的数据插入到数据库
                return Response(serializer.data)
            else:
    
                return Response(serializer.errors)
    
    
    class AuthorDetailView(GenericAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    
        def get(self, request, pk):  # url的pk名
            serializer = self.get_serializer(instance=self.get_object(), many=False)
            return Response(serializer.data)
    
        def delete(self, request, pk):
            self.get_object().delete()
            return Response()
    
        def put(self, request, id):
            # post请求
            instance = self.get_object()
            serializer = self.get_serializer(data=request.data, instance=instance)
    
            if serializer.is_valid():
                serializer.save()  # instance的 update
                return Response(serializer.data)
    
            else:
                return Response(serializer.errors)

     

    ModelSerializer + Mixin

    from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, \
        UpdateModelMixin
    
    from rest_framework.generics import GenericAPIView
    
    
    class AuthorSerializer(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = ["name", "age"]
    
    
    class AuthorView(GenericAPIView, ListModelMixin, CreateModelMixin):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    
        def get(self, reqeust):
            return self.list(reqeust)
    
        def post(self, request):
            return self.create(request)
    
    
    class AuthorDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    
        def get(self, request, pk):  # url的pk名
            return self.retrieve(request)
    
        def delete(self, request, pk):
            return self.destroy(request)
    
        def put(self, request, pk):
            return self.update(request)

    基于Mixin的进一步封装

    ListModelMixin, CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, \ UpdateModelMixin

     

    from rest_framework.generics import ListCreateAPIView,RetrieveUpdateDestroyAPIView
    
    
    class AuthorSerializer(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = ["id","name", "age"]
    
    
    class AuthorView(ListCreateAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    
    
    class AuthorDetailView(RetrieveUpdateDestroyAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    

     

  • Python 进程, 线程, 协程

    什么是进程?

    进程是对资源进行分配和调度的最小单位,是操作系统结构的基础,是线程的容器(就像是一幢房子,一个空壳子,并不能运动)。

    • 进程是一个实体,每个进程都有自己的地址空间,一般包括文本区域(text region)、数据区域(data region)和堆栈(stack region)
    • 文本区域存储处理器执行的代码;数据区域存储变量和进程在执行期间所使用的动态分配的内存;堆栈区域存储在活动过程中所调用的指令和本地变量
    • 进程是一个“执行中的程序”。程序是一个没有生命的实体,只有在操作系统调用时,他才会成为一个活动的实体:进程。

    什么是线程

    线程被称为轻量级进程,是操作系统能够运算调度的最小单位,线程被包含在进程中,是进程中实际处理单位(就像是房子里的人,人才能动)

    • 一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组 成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,
    • 线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个 进程的其它线程共享进程所拥有的全部资源。

    线程的三种状态

    一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程 在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。

    • 就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;
    • 运行状态是指线程占有处理机正在运行
    • 阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。
    • 每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。

     

    什么是协程

    协程又叫微线程,一个程序可以包含多个协程,就好比一个进程包含多个线程。协程的调度完全由用户控制。

    协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。

    直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

    协程和线程的阻塞是有本质区别的。协程的暂停完全由程序控制,线程的阻塞状态是由操作系统内核来进行切换。因此,协程的开销远远小于线程的开销。

     

    相互比较

     

    进程与线程的区别:

      • 进程有自己独占的地址空间,每启动一个进程,系统就需要为它分配地址空间;
      • 而一个进程下所有线程共享该进程的所有资源,使用相同的地址空间,因此CPU在线程之间切换远远比在进城之间切换花费小,而且创建一个线程的开销也远远比开辟一个进程小得多。
      • 线程之间通信更加方便,同一进程下所有线程共享全局变量、静态变量等数据。
      • 而进程之间通信需要借助第三方。
      • 线程只能归属于一个进程并且它只能访问该进程所拥有的资源。
      • 当操作系统创建一个进程后,该进程会自动申请一个名为主线程或首要线程的线程。
      • 处理IO密集型任务或函数用线程;
      • 处理计算密集型任务或函数用进程。

    线程和协程的区别:

     

    • 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。
    • 线程进程都是同步机制,而协程则是异步
    • 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态

     

    我们常说python中的多线程都是假的,因为无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行。

    这又是为什么呢?其实这主要是由于GIL的存在而造成的,详情查阅 http://bayestalk.com/592

     

     

    总结

     

    至此, 不难发现, 在某个角度来讲。它们三者体现的是一种颗粒粗细度的关系。就像切菜,

    • 你用青龙偃月刀来切, 肯定是可以的, 但应该切得比较大块。
    • 用菜刀, 这种对生活而言, 颗粒粗细度刚好。
    • 但如果是做出蔬菜工艺品, 那么可能要用到非常小巧, 锋利的雕刻工具了。

    再比如对于PC而言,

    • 一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
    • 有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
    • 由于每个进程至少要干一件事,所以,一个进程至少有一个线程。
      • 当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。

    大部分情况下, Python程序,都是执行单任务的进程,也就是只有一个线程。如果我们要同时执行多个任务怎么办?

     

    • 一种是启动多个进程,每个进程虽然只有一个线程,但多个进程可以一块执行多个任务。
    • 还有一种方法是启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务。
    • 当然还有第三种方法,就是启动多个进程,每个进程再启动多个线程,这样同时执行的任务就更多了,当然这种模型更复杂,实际很少采用。

    多进程和多线程的程序涉及到同步、数据共享的问题,编写起来更复杂。

    多线程编程

    多线程编程导致的问题

    import time, threading
    
    # 假定这是你的银行存款:
    balance = 0
    
    def change_it(n):
        # 先存后取,结果应该为0:
        global balance
        balance = balance + n
        balance = balance - n
    
    def run_thread(n):
        for i in range(1000000):  # 循环的次数要设置得足够大。
            change_it(n)
    
    t1 = threading.Thread(target=run_thread, args=(5,))
    t2 = threading.Thread(target=run_thread, args=(8,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    
    
    run_thread(1)
    print(balance)

    以单线程的思路来解释, 就是两个人玩一个非常无聊的游戏, A给B 1块, B又给A 1块, 重复无数次。 那么亿万年后, 他们的财产不会因这个游戏有任何影响。

    • balance 初始值 = 0
    • change_it(n) 函数
      • balance+1
      • balance-1
    • 一加一减, 相抵消
    • 所以最后balance

    但是在多线程中, 而多线程中,所有变量都由所有线程共享, 任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量。

    怎么解决?

    import time, threading
    
    # 假定这是你的银行存款:
    balance = 0
    
    def change_it(n):
        # 先存后取,结果应该为0:
        global balance
        balance = balance + n
        balance = balance - n
    
    def run_thread(n):
        for i in range(10000000): # 循环的次数要设置得足够大。
            change_it(n)
    
    t1 = threading.Thread(target=run_thread, args=(5,))
    t2 = threading.Thread(target=run_thread, args=(8,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    
    
    run_thread(1)
    print(balance)

    结果始终为0, 不同线程间被lock隔绝吗相互独立, 互不干扰。

     

    threading 类

    Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

    启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:

     

     

    其他

     

    1. 假设你经营着一家物业管理公司。最初,公司的业务量很小,事事都需要你亲力亲为,给老张家修完暖气管道,立马就去老李家换电灯泡—这叫单线程,所有的工作都得顺序执行。
    2. 后来业务拓展了,你雇用了几个工人,这样你的物业公司就可以同时为多户人家提供服务—这叫多线程,而你是主线程。
      1. 工人们使用的工具是物业管理公司提供的,这些工具由大家共享,并不专属于某一个人—这叫多线程资源共享。
      2. 工人们在工作中都需要管钳,可是管钳只有一把—这叫冲突。解决冲突的办法有很多,如排队等候、等同事用完后微信通知等—这叫线程同步。
      3. 你给工人布置任务—这叫创建线程。布置任务后你还要告诉他,可以开始工作了,不然他会一直停在那儿不动—这叫启动线程(start)。
      4. 如果某个工人(线程)的工作非常重要,你(主线程)也许会亲自监工一段时间;如果不指定时间,则表示你会一直监工到该项工作完成—这叫线程参与(join)。
      5. 业务不忙的时候,你就在办公室喝喝茶。下班时间一到,你群发微信,通知工人该下班了,所有的工人不管手里正在做的工作是否完成,都立刻下班。因此如果有必要,你得避免在工人正忙着的时候发下班通知—这叫线程守护属性设置和管理(daemon)。
      6. 再后来,公司规模扩大了,同时为很多生活社区服务,在每个生活社区都设置了分公司,分公司由分公司经理管理,运营机制和总公司几乎一模一样—这叫多进程,总公司叫主进程,分公司叫子进程。
      7. 总公司和分公司,以及各个分公司之间使用的工具都是独立的,不能借用、混用—这叫进程间不能共享资源。各个分公司之间可以通过专线电话联系—这叫管道。各个分公司之间还可以通过公司公告栏交换信息—这叫进程间共享内存。另外,各个分公司之间还有各种协同手段,以便完成更大规模的作业—这叫进程间同步。
      8. 分公司可以跟着总公司一起下班,也可以把当天的工作全部做完之后再下班—这叫守护进程设置。

     

  • Python 进程/线程/协程/异步编程

    前置基础

    什么是GIL?

    进程

     

    线程

     

    协程

    非协程实例

    首先来看非协程的代码实例

    t1 = time.time()
    def func1():
        print("当前执行function 1")
        time.sleep(1)  # 当程序出现了同步操作的时候. 异步就中断了
        print("当前执行function 1")
    
    
    def func2():
        print("当前执行function 2")
        time.sleep(2)
        print("当前执行function 2")
    
    def func3():
        print("当前执行function 3")
        time.sleep(3)
        print("当前执行function 3")
    
    if __name__ == '__main__':
        f1 = func1()
        f2 = func2()
        f3 = func3()
        tasks = [
            f1, f2, f3
        ]
        # 一次性启动多个任务(协程)
        # asyncio.run(asyncio.wait(tasks))
        t2 = time.time()
        print(t2 - t1)
    

     

    结果

     

    当前是function 1
    当前是function 1
    当前是function 2
    当前是function 2
    当前是function 3
    当前是function 3
    6.002589225769043

     

    协程实例

    async def func1():
        print("当前执行function_1")
        await asyncio.sleep(1)
        print("当前执行function_1")
    
    
    async def func2():
        print("当前执行function_2")
        await asyncio.sleep(2)
        print("当前执行function_2")
    
    
    async def func3():
        print("当前执行function_3")
        await asyncio.sleep(3)
        print("当前执行function_3")
    
    
    async def main():
        # 第一种写法
        # f1 = func1()
        # await f1  # 一般await挂起操作放在协程对象前面
        # 第二种写法(推荐)
        tasks = [
            asyncio.create_task(func1()),  # py3.8以后加上asyncio.create_task()
            asyncio.create_task(func2()),
            asyncio.create_task(func3())
        ]
        await asyncio.wait(tasks)
    
    
    if __name__ == '__main__':
        t1 = time.time()
        # 一次性启动多个任务(协程)
        asyncio.run(main())
        t2 = time.time()
        print(t2 - t1)
    

     

    结果

    当前执行function_1
    当前执行function_2
    当前执行function_3
    当前执行function_1
    当前执行function_2
    当前执行function_3
    3.0129427909851074

     

    对比与发现

    1. 一共三个任务
    2. 非协程写法里
      1. 如果使用非协程写法,  time.sleep(1)  ,将会导致IO阻塞。 因此程序会在设定的等待时间结束后, 才会往下执行。
      2. 耗时 6.002589225769043
      3. 打印的顺序自上而下
    3. 协程写法里
      1. 耗时3.0129427909851074
      2. 可以反推: 总耗时 = await 挂起时间最长的那个任务所花的时间(function 3) + 切换协程上下文的所需开销的时长(0.01)
    4. 异步协程的语法结构

     

     

    异步协程最简实例

    import asyncio
    import aiohttp
    import aiofiles
    
    # 构造无数个urls
    urls = [
        "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png",
        "https://inews.gtimg.com/newsapp_bt/0/12171811596_909/0.png",
        "http://www.soso.com/soso/images/logo_index_sosox2.png"
    ]
    
    async def aiodownload(url):
        # 异步下载功能的函数, 以下三个步骤都是IO操作
            # 1.发送请求 aiohttp
            # 2. 得到图片内容 async
            # 3. 保存到文件 aiofiles
    
        filename = url.rsplit("/", 1)[1]
    
        async with aiohttp.ClientSession() as session:  # 类似同步的 requests
            async with session.get(url) as resp:        # 类似同步的 resp = requests.get()
                # 请求回来. 异步写入文件
                async with aiofiles.open(filename, mode='wb') as f:
                    # await f.write(await resp.content.read())  # 读取内容是异步的. 需要await挂起, resp.text()
                    # Response对象的read()和text()方法会将响应一次性全部读入内存,这会导致内存爆满,导致卡顿,影响效率。 因此采取字节流的形式,每次读取4096个字节并写入文件。
                    while True:
                        pic_stream = await resp.content.read(4096)
                        if not pic_stream:
                            break
                        await f.write(pic_stream)
        print(f'{filename} 已下载')
    
    async def main():
        # tasks = []
        # for url in urls:
        #     # tasks.append(aiodownload(url))
        #     d = asyncio.create_task(aiodownload(url))
        #     tasks.append(d)
    
        # 利用推导式的简写方式
        tasks = [asyncio.create_task(aiodownload(url)) for url in urls]
        await asyncio.wait(tasks)
    
    if __name__ == '__main__':
        asyncio.run(main())
    
    
    

     

    反思

    在爬虫的三个基本操作都是涉及到IO

    •  1.发送请求 aiohttp
    • 2. 得到图片内容 async
    • 3. 保存到文件 aiofiles

    但2和1、3有显著区别。

    • 磁盘io与网络io不同,磁盘顺序读写单个文件最快,并发读写会涉及到多个文件的切换问题,反而花了更多的时间,所以异步编程使用aiofiles要谨慎。
    • 举一个有差异但基本原理类似的例子。在同一台电脑上, 将C盘里的文件复制到D盘去, 这里有个很明显的经验是如果同时只有一个这样的复制操作, 那么“较为高效”。如果“将C盘里的文件复制到D盘去”的同时,  开启多个文件转移复制粘贴进程, 速度极为缓慢甚至进程管理亲卡死。
    • 解决的办法应该有很多种。
      • 可以考虑把文件读写任务抽离出来,放到队列里面,然后用专门的线程或进程去按顺序去处理。
      • 因此采取字节流的形式,每次读取4096个字节并写入文件
  • What does if __name__ == “__main__”: do?

    a = 'a'
    print('我是script a')
    print(a)
    
    
    
    import script_a
    b = 'b'
    print('我是script b')
    print(b)

     

    a = 'a'
    print('我是script a')
    
    if __name__ == "main":
        print(a)
    
    
    import script_a
    b = 'b'
    print('我是script b')
    print(b)

     

     

    1. 现在有A.py和 B.py两个脚本文件
      1.  A.py
      2. B.py
    2. 在B中import A
      1. import A这个动作, 意味着导入并执行A里面的每一行(所有)代码.
    3. 因此会导致一个问题: 有些时候, 我并不希望B在引入A时, 去执行A的所有代码.
    4. 为了解决问题,  python里允许你使用条件检测, 它基于以下机制
      1. __name__ 是语言预置的变量
      2. 如果A.py被导入, 则它的__name__ 的值为文件名A
      3. 如果A.py被直接执行, 则它的__name__ 的值为 “__main__”
    5. 因此, 对于A里面, 被导入到B时, 不希望被执行的代码可以放到 if __name__ == “__main__”:里面去. 因为此时条件不成立, __name__ == “A”, 而不是”__main__”

     

    这其实是个非常自然而然的事情, 但最近有初学的朋友问到这问题. 以及另外相关的奇怪问题:

    1. 为什么 “我并不希望B在引入A时, 去执行A的所有代码.”? 这种逻辑在需求中是常见的.
    2. 为什么会有”__name__” 和 “__main__” 这种看起来很奇怪的东西? 这是语言设计者设计出来, 就像人类设计数字来计数一样. 这是个哲学问题和喜好问题. 不是个编程问题.

     

    print(a) 没有被执行

  • Python 符号用法总结

    下划线

    单下划线 _

    函数名称前单下划线

    def _add():
    	...
        return

    是一种私有函数的命名约定,即提示程序员该函数只能在类或者该文件内部使用,但实际上也可以在外部使用。

     

    _xxx 单下划线

    • protected 类型变量
    • 只允许其本身与子类进行访问
    • 也不能使用from xxx import * 的方式导入

    xxx_ 单下划线

    • 避免名称与关键字冲突

     

    星号*

  • Django – 前端提交数据, 后端接收并入库简例 (ModelForm)

    models.py

     

    class Boss(models.Model):
        name = models.CharField(verbose_name="姓名", max_length=64)
        age = models.IntegerField(verbose_name="年龄")
        img = models.CharField(verbose_name="头像", max_length=256)

     

    这种写法需要在view_name.py文件中去处处理 待保存文件的路径问题, 并调用create方法.

    media_file_path = os.path.join("media", image_object.name)
    print(media_file_path)
    f = open(media_file_path, mode="wb")
    for chunk in image_object.chunks():
        f.write(chunk)
        f.close()
    
    models.Boss.objects.create(
        name = form.cleaned_data['name'],
        age = form.cleaned_data['age'],
        img = media_file_path,

     

     

    class City(models.Model):
        name = models.CharField(verbose_name="名称", max_length=64)
        count = models.IntegerField(verbose_name="人口")
    
        # 此处写成"FileField", 而不是"IntegerField", 这样FileField会多出upload_to='目录名'属性, 在入库时可快速将图片保存到该目录
        logo = models.FileField(verbose_name="logo", max_length=256, upload_to='city/')

     

    view_name.py

    class UpModelForm(BootStrapModelForm):
        class Meta:
    
            model = models.City
            fields = "__all__"
    
    def upload_model_form(request):
        title = "ModelForm上传"
        if request.method == "GET":
            form = UpModelForm()
            return render(request, 'upload_form.html', {'form': form, "title": title})
    
    
        form = UpModelForm(data=request.POST, files=request.FILES)
        if form.is_valid():
            form.save()
            return HttpResponse('成功')

     

    可以看到这种写法非常简捷,  保存路径已在创建model时设置, 而保存数据只需form.save()即可.

    结果验证

    • 保存路径: logo = models.FileField(verbose_name=”logo”, max_length=256, upload_to=’city/’)
    • 如何保存: form.save()

     

     

    前置条件

    配置media目录. (实际使用中, 由于目录位置等差异, 根据实际情调整.)

    setting.py

    MEDIA_ROOT = os.path.join(BASE_DIR, "media")
    MEDIA_URL = "/media/"

     

    urls.py 路由

     

    from django.views.static import serve
    from django.conf import settings
    re_path(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}, name='media'),

     

  • Django – 前端提交数据, 后端接收并入库简例 (Form)

    view

    class UpForm(BootStrapForm):  封装了bootstrap的Form组件的类, 让表单快速获得bootstrap的样式
        bootstrap_exclude_fields = ['img']  # 在BootStrapForm中,排除img表单的样式
        name = forms.CharField(label="姓名")
        age = forms.IntegerField(label="年龄")
        img = forms.FileField(label="头像")
    
    
    def upload_form(request):
        title = "表格上传"
    
        if request.method == "GET":
            form = UpForm()
            return render(request, 'upload_form.html', {'form':form, "title":title})
    
        if request.method == "POST":
            # 获取前端用户提交的数据
            form = UpForm(data=request.POST, files = request.FILES)
            if form.is_valid():
                image_object = form.cleaned_data.get("img")
              
                # 对图片数据进行特殊处理
                db_file_path = os.path.join('static', 'img', image_object.name)
                file_path = os.path.join('app01', db_file_path)
                print(file_path)
                f = open(file_path, mode="wb")
                for chunk in image_object.chunks():
                    f.write(chunk)
                    f.close()
                # 写入数据库
                models.Boss.objects.create(
                    name = form.cleaned_data['name'],
                    age = form.cleaned_data['age'],
                    img = db_file_path,
                )
    
        return render(request, 'upload_form.html', {'form': form, "title": title})
    

     

    template

    <form method="post" novalidate enctype="multipart/form-data">
        {% csrf_token %}
    
        {% for field in form %}
            <div class="form-group">
                <label>{{ field.label }}</label>
                {{ field }}
                <span style="color: red">{{ field.errors.0 }}</span>
            </div>
        {% endfor %}
    
        <button type="submit" class="btn btn-primary">提交</button>
    
    </form>

     

    router

    path('upload/form/', upload.upload_form)