分类: Python 面向对象

  • 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
    

     

  • 什么是CPython GIL?

    什么是Python GIL?

    什么是解释器?

    Python作为一门解释性语言,先把源代码编译为字节码,再放进虚拟机中执行,整个过程是由解释器执行并完成的。类似的还有JavaScript和PHP等。

    但是解释器并不是只有一种,官方的解释器是基于C语言开发的CPython。但是除了CPython,还有基于Java实现的Jython、基于R 语言实现的RPython等等。

     

     

     什么是GIL?

    GIl 是一种互斥锁

    什么是互斥锁?

    在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

     

    当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。

    线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

    互斥锁为资源引入一个状态:锁定/非锁定

    某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

     

    为什么会产生GIL?

    GIL的产生是因为CPython的内存管理不安全

    In CPython, the global interpreter lock, or GIL, is a mutex that protects access to Python objects, preventing multiple threads from executing Python bytecodes at once. The GIL prevents race conditions and ensures thread safety. A nice explanation of how the Python GIL helps in these areas can be found here. In short, this mutex is necessary mainly because CPython’s memory management is not thread-safe。

    在CPython中,GIL是一个互斥锁,它在任一时刻只允许一个线程对字节码进行执行。这样避免了竞争危害,从而保证了线程安全。简单来说就是,互斥锁之所以存在是因为CPython的内存管理不是“线程安全的”

    由此Python的官方文档可知, 因为因为CPython的内存管理不是“线程安全的”, 所以需要互斥锁, 那么自然会引出另外两个问题。

    GIL的产生是因为CPython的内存管理不安全

    为什么CPython的内存管理不安全?

    Python 第一次发布是在 1991 年,当时的 CPU 都是单核,单核中,多线程主要为了一边做IO,一边做 CPU 计算而设计的,Python 编译器是由 C 语言编写的,因此也叫 CPython,那时候很多编程语言没有自动内存管理的功能,为了实现自动垃圾回收,Python 为每一个对象进行了引用计数,当引用计数为 0 的时候说明该对象可以回收,从而释放内存了,比如:

    >>> import sys
    >>> a = []
    >>> b = a
    >>> sys.getrefcount(a)
    3

    这里 a对象就有 3 个引用,

    • 一个是本身,
    • 一个是变量 b,
    • 一个是 getrefcount 函数的参数,

    如果此时又有一个线程引用了 a,那么引用计数再增加 1,如果某个线程使用了 a 后运行结束,那么引用计数就减少 1,多线程对同一个变量「引用计数」进行修改,就会遇到 race conditions(竞争)。

     

    怎么解决内存管理不安全的问题?

    为了避免 race conditions,最简单有效的办法就是加一个互斥锁。但如果对每一个对象都加锁,有可能引发另一个问题,就是死锁,而且频繁的获取和释放会导致性能下降。

    所以至此, 最简单有效的方法就是加一个解释器锁,线程在执行任何字节码时都先获取解释器锁,这就避免了死锁,而且不会有太多的性能消耗。当时 CPU 都是单核,而且这种 GIL 设计简单,并不会影响性能,因此一直沿用至今天。GIL 存在最主要的原因,就是因为 Python 的内存管理不是线程安全的,这就是 GIL 产生并存在的主要缘由。

    互斥锁的代码实例

    threading模块中定义了Lock类,可以方便的处理锁定:

     # 创建锁
     mutex = threading.Lock()
     ​
     # 锁定
     mutex.acquire()
     ​
     # 释放
     mutex.release()
    • 如果这个锁之前是没有上锁的,那么acquire不会堵塞
    • 如果在调用acquire对这个锁上锁之前,它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止

     

    互斥锁在for循环外面
    import threading
    import time
    
    # 定义一个全局变量
    g_num = 0
    
    
    def test1(num):
     global g_num
     # 上锁,如果之前没有被上锁,那么此时 上锁成功
     # 如果上锁之前 已经被上锁了,那么此时会堵塞在这里,直到 这个锁被解开位置
     mutex.acquire()
     for i in range(num):
         g_num += 1
     mutex.release()   # 解锁
     print("-----in test1 g_num=%d----" % g_num)
    
    
    def test2(num):
     global g_num
     mutex.acquire()   # 上锁
     for i in range(num):
         g_num += 1
     mutex.release()   # 解锁
     print("-----in test2 g_num=%d=----" % g_num)
    
    
    # 创建一个互斥锁,默认是没有上锁的
    mutex = threading.Lock()
    
    
    def main():
     t1 = threading.Thread(target=test1, args=(1000000,))
     t2 = threading.Thread(target=test2, args=(1000000,))
    
     t1.start()
     t2.start()
    
     # 等待上面的2个线程执行完毕....
     time.sleep(2)
    
     print("-----in main Thread g_num = %d---" % g_num)
    
    if __name__ == "__main__":
     main()
    
    #-----in test1 g_num=1000000----
    #-----in test2 g_num=2000000=----
    #-----in main Thread g_num = 2000000---
    互斥锁在for循环里面
    import threading
    import time
    
    # 定义一个全局变量
    g_num = 0
    
    def test1(num):
     global g_num
     for i in range(num):
         mutex.acquire()  # 上锁
         g_num += 1
         mutex.release()  # 解锁
    
     print("---test1---g_num=%d"%g_num)
    
    def test2(num):
     global g_num
     for i in range(num):
         mutex.acquire()  # 上锁
         g_num += 1
         mutex.release()  # 解锁
    
     print("---test2---g_num=%d"%g_num)
    
    # 创建一个互斥锁
    # 默认是未上锁的状态
    mutex = threading.Lock()
    
    # 创建2个线程,让他们各自对g_num加1000000次
    p1 = threading.Thread(target=test1, args=(1000000,))
    p1.start()
    
    p2 = threading.Thread(target=test2, args=(1000000,))
    p2.start()
    
    # 等待计算完成
    while len(threading.enumerate()) != 1:
     time.sleep(1)
    
    print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)
    
    
    # ---test1---g_num=1909909
    # ---test2---g_num=2000000
    # 2个线程对同一个全局变量操作之后的最终结果是:2000000

     

    上锁解锁过程
    • 当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。
    • 每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。
    • 线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。
    锁的好处
    • 确保了某段关键代码只能由一个线程从头到尾完整地执行
    锁的坏处
    • 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
    • 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁。

     

    死锁代码实例

     

    import threading
    import time
    
    
    #创建互斥锁
    lock = threading.Lock()
    
    
    #根据下标去取值, 保证同一时刻只能有一个线程去取值
    def get_value(index):
    
        # 上锁
        lock.acquire()
        print(threading.current_thread())
        my_list = [3,6,8,1]
        # 判断下标释放越界
        if index >= len(my_list):
            print("下标越界:", index)
    
            return
        value = my_list[index]
        print(f'值是:{value}')
        time.sleep(0.2)
        # 释放锁
        lock.release()
    
    
    if __name__ == '__main__':
        # 模拟大量线程去执行取值操作
        for i in range(30):
            sub_thread = threading.Thread(target=get_value, args=(i,))
            sub_thread.start()

    出现死锁的情况, 程序无法正常停止, 一直在等待

    <Thread(Thread-1, started 30364)>
    值是:3
    <Thread(Thread-2, started 27120)>
    值是:6
    <Thread(Thread-3, started 29632)>
    值是:8
    <Thread(Thread-4, started 29988)>
    值是:1
    <Thread(Thread-5, started 20984)>
    下标越界: 4
    

    避免死锁的代码示例

    # 在合适的地方释放锁
    import threading
    import time
    
    #创建互斥锁
    lock = threading.Lock()
    
    
    #根据下标去取值, 保证同一时刻只能有一个线程去取值
    def get_value(index):
    
        # 上锁
        lock.acquire()
        print(threading.current_thread())
        my_list = [3,6,8,1]
        if index >= len(my_list):
            print("下标越界:", index)
            # 当下标越界需要释放锁,让后面的线程还可以取值
            lock.release()
            return
        value = my_list[index]
        print(value)
        time.sleep(0.2)
        # 释放锁
        lock.release()
    
    
    if __name__ == '__main__':
        # 模拟大量线程去执行取值操作
        for i in range(10):
            sub_thread = threading.Thread(target=get_value, args=(i,))
            sub_thread.start()

     

    <Thread(Thread-1, started 30336)>
    3
    <Thread(Thread-2, started 5920)>
    6
    <Thread(Thread-3, started 28308)>
    8
    <Thread(Thread-4, started 27324)>
    1
    <Thread(Thread-5, started 26840)>
    下标越界: 4
    <Thread(Thread-6, started 30104)>
    下标越界: 5
    <Thread(Thread-7, started 28900)>
    下标越界: 6
    <Thread(Thread-8, started 2676)>
    下标越界: 7
    <Thread(Thread-9, started 28912)>
    下标越界: 8
    <Thread(Thread-10, started 30068)>
    下标越界: 9
    
    Process finished with exit code 0

     

    最后, GIL导致了什么结果?

    正面

    解决安全问题。

    负面

    单线程CPU消耗

    约16% (i5 11代)

     

    import threading
    
    def dead_loop():
        while True:
            pass
    dead_loop()

     

    双线程CPU消耗

    仍然约16%, 而不是32%。

    import threading
    
    def dead_loop():
        while True:
            pass
        # 新起一个死循环线程
        t = threading.Thread(target=dead_loop)
    
        t.start()
        # 主线程也进入死循环
        dead_loop()
        t.join()
    
    dead_loop()

     

    结论:

    • 当双线程时, Cpython 缩小好的CPU的资源, 和单线程时一致。
    • Cpython 当前只能运行一个GIL线程。

     

     

    如果再更进一步, 尝试十个或N个线程, Python的CPU利用率仍然不变。

    但是用C、C++或Java来改写相同的死循环,直接可以把全部核心跑满,为什么Python不行?正是GIL。

    Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

    在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。

    不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

    其它

    • 对于 “Python的GIL” 这种表述是不够严谨, 但也不算错。
    • GIL是相对于Cpython 解释器而言, 而不是Python 语言。
    • Cpython是用来解析Python代码.
    • Cpython是目前最流行的, 主流的解释器.

     

    最后, 只要你愿意, 你可以自己开发一个没有GIL的解释器.

     

     

     

  • Python 面向对象

    简介

    对具象化的事物进行抽象, 尽管细节有偏差, 但与其他大部分编程语言的面向对象概念类似.

    一个极简实例

    class Animal(object):
    
        def __init__(self, name, color):
            self.name = name
            self.color = color
    
        def describe(self):
            # return self.name + self.score
            print(f'名字:{self.name} | 颜色:{ self.color}')
    
    
    # 注意到__init__方法的第一个参数永远是self,表示创建的实例本身,
    # 因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
    # 构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值
    xialing = Animal('黠灵', '灰白')
    xialing.describe()
    
    # 名字:黠灵 | 颜色:灰白
    # Process finished with exit code 0

    魔术方法 __init__

    class Human(object):
        def describe(self):
            pass
    
    xialing = Human()
    xialing.name= "黠灵"
    xialing.ability= "飞翔"
    
    huban = Human()
    xialing.name= "虎斑"
    xialing.ability= "瞬移"
    
    ...
    ...
    ...

     

    • 这其实是一个不是问题的问题,  初始化, 在其他语言和很多程序的设计中, 都会使用到.
    • 比如人, 有些属性是需要通过学习和机遇才能获得, 想象科学家和普通人的区别.
    • 有些属性则是人类天生就有的共性, 比如一出生就拥有了机体, 会睡觉, 会摄取营养.
    • 很显然, 作为实例/个体的人, 有许多共性, 都具有姓名, 年龄, 族别, 性别等.  如果为每一个实例去一个个添加这些共同的属性, 会非常麻烦.
    class Human(object):
        def __init__(self, name, age, gender, nationality):
            self.name = name
            self.age = age
            self.gender = gender
            self.nationality = nationality
    
        def describe(self):
            pass
    
    xialing = Human("黠灵", "6", "男", "汉") # 此时xialing 便具有所有的初始属性
    print(xialing.name, xialing.age, xialing.gender, xialing.nationality )

    望文生义 # initialization / initiate, 初始化,是一种特殊的方法。在创建对象时, 自动初始化对象,不需要手动/显式地调用. 即为对象成员变量赋初始值.

     

    魔术方法 __new__

    __init__ 的功能很明显, 其内部如何实现功能, 则与__new__相关.

    Python 中实例化对象时, 不需要显式地使用new关键字 (其他语言比如PHP中则需要).

    但实际上在实例化对象时, Python会自动效用__new__方法

     

    class Animal(object):
        def __init__(self):
            print('init方法被执行')
    
    
        def __new__(cls, *args, **kwargs):
            print('new方法被执行')
            
            print(cls)                    # cls 是 Animal 类        
            print(object.__new__(cls) )   # 得到 Animal 类的对象     
        
            return super().__new__(cls)   # return object.__new__(cls) 这样写也行
    
    
    xialing = Animal()
    
    # new方法被执行
    # init方法被执行
    # Process finished with exit code 0

    __new__ 先执行

    __init__ 后执行

     

    self 是什么?

    class Person(object):
        def ability(self):
            print(self)
            print('self的内存地址:' + str(id(self)))
    
    laozi = Person()
    laozi.ability()
    print("实例化得到的对象的内存地址:" + str(id(laozi)))
    
    zhuangzi = Person()
    zhuangzi.ability()
    print("实例化得到的对象的内存地址:" + str(id(zhuangzi)))
    
    
    # <__main__.Person object at 0x00000233A6D5D750>
    # self的内存地址:2420865619792
    # 实例化得到的对象的内存地址:2420865619792
    #
    # <__main__.Person object at 0x00000233A6D5D7D0>
    # self的内存地址:2420865619920
    # 实例化得到的对象的内存地址:2420865619920
    #
    # Process finished with exit code 0

     

    由此可知:

    • self 和 当前对象指向相同的内存地址
    • self 是 对象的引用

     

    析构方法 __del__

    # 当一个对象被删除或销毁时, Python 解释器会默认调用__del__()方法, 它称之为析构方法.
    
    class Animal(object):
        def __init__(self, name):
            self.name = name
            print("__init__ 方法被调用")
    
    
        def __del__(self):
            print("__del__ 方法被调用")
            print(f'{self.name}对象被销毁')
    
    cat = Animal("kitty")
    # print(input("请输入"))
    
    # __init__ 方法被调用
    # __del__ 方法被调用
    # kitty对象被销毁
    # Process finished with exit code 0

     

    继承

    单继承: 继承的最简实例

    class Animal(object):  # 创建一个基类
        def run(self):
            print('这是Animal基类内部的run方法')
    
    
    class Dog(Animal):  # 一个Dog子类, 继承自Animal. 子类能获得父类的全部功能, 称之为继承
        pass
    
    class Cat(Animal):  # 一个cat子类
        pass
    
    
    xialing = Dog()  # 将子类实例化, 得到对象xialing, 验证该对象是否获得基类的方法
    xialing.run()
    
    
    # 这是Animal基类内部的run方法
    # Process finished with exit code 0
    • Dog类继承自Animal类
    • Dog类实例化生成的对象xialing获得了Animal类的方法

     

    多继承

    class Animal(object):
        def climb(self):
            print('动物会爬树')
    
    
    class Human(object):
        def talk(self):
            print("人类会说话")
    
    
    class Immortal(object):
        def magic(self):
            print("神仙会仙术")
    
    
    class Monkey(Animal, Human, Immortal):
        pass
    
    wukong = Monkey()
    wukong.climb()
    wukong.talk()
    wukong.magic()
    
    # 动物会爬树
    # 人类会说话
    # 神仙会仙术
    # Process finished with exit code 0
    • Monkey类 继承了三个基类
    • Monkey类实例化的对象获得了三个类的方法

     

    多继承里的查找顺序问题

     

    class Animal(object):
        def climb(self):
            print('动物会爬树')
    
    
    class Human(Animal):
        def climb(self):
            print("人类会会爬树")
    
    
    class Immortal(Animal):
        def climb(self):
            print("神仙也会爬树")
    
    
    class Monkey(Human, Immortal):
        pass
    
    wukong = Monkey()
    wukong.climb()
    print(Monkey.__mro__)
    
    # 人类会会爬树
    # (<class '__main__.Monkey'>, 
    # <class '__main__.Human'>, 
    # <class '__main__.Immortal'>,
    # <class '__main__.Animal'>, 
    # <class 'object'>)
    
    # Process finished with exit code 0
    • 自下往上, 由近及远的去查找climb方法. (注意monkey类的继承顺序, Human在前)
    • 如果Monkey类里增加climb 方法, 并print(“猴子当然会爬树”), 那么最终的输出结果是猴子当然会爬树.
    • 可通过 __mro__ 来查看python内部的查找顺序
    • 这个涉及到语言本身的算法, 如广度优先.

    方法重写

    适用条件

    • 参数列表必须完全和被重写方法的参数列表一致. 如果Human类的__init__方法中添加其他参数, 则会报错. TypeError: Animal.__init__() takes 3 positional arguments but 4 were given
    • 返回类型必须完全和被重写方法的返回类型一致.
    • 访问修饰符的限制一定要大于被重写方法的访问修饰符.

     

    class Animal(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def describe(self):
            print('动物会爬树')
    
    
    class Human(Animal):
        def __init__(self, name, age):
    
            # Animal.__init__(self, name, age)    # 手动调用
            super().__init__(name, age)     # super()是一个特殊的类,调用super得到一个对象,该对象指向父类的名称空间。
            self.title = "齐天大圣"          # 拓展其他的属性
    
        def describe(self):
            print(self.name)
            print(self.age)
            print(self.title)
    
    wukong = Human('悟空', '99999')
    wukong.describe()
    
    
    # 悟空
    # 99999
    # 齐天大圣
    # Process finished with exit code 0

     

    方法重载

    重载是让类以统一的方式处理不同类型数据的一种手段。

    适用条件:

    • 一个类里面
    • 方法名字相同
    • 参数不同

    基本设计本原则:

    • 仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载

    解决的问题:

    • 可变参数类型
    • 可变参数个数

    由此可知:

    • 函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数.
    • 函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。

    进而得到:

    • Python 里并没有语言规范上的方法重载.
    • 但有其他语言里方法重载里的功能
    • 因为Python函数自身的独特性和灵活性,  已经可以实现其他语言里的方法重载功能.

     

    多态

     

    定义: 同一种行为 , 对于不同的子类对象有不同的行为表现

    适用条件:

    • 继承:多态必须发生在父类和子类之间
    • 重写: 子类重写父类的方法
    class Animal(object):  # 创建一个基类
        def describe(self):
            print('这是Animal基类')
    
    class Dog(Animal):
        def describe(self):
            print('这是继承Animal类的Dog类, 这里是狗')
    
    
    class Cat(Animal):  # 子类
        def describe(self):
            print('这是继承Animal类的Cat类, 这里是猫')
    
    xialing = Dog()
    xialing.describe()
    
    lihua = Cat()
    lihua.describe()
    
    # 这是继承Animal类的Dog类, 这里是狗
    # 这是继承Animal类的Cat类, 这里是猫
    # Process finished with exit code 0
    

     

    • 黠灵 之于Animal类, 得到了Dog子类的方法.
    • 狸花 之于Animal类, 得到了Cat子类的方法.
    • 同一个机器, 能根据需求制造出不同的产品
      • Animal类  = 机床
        • 重写和继承 = 调整和设计程序
          • 第一次加工出齿轮/黠灵
          • 第二次加工出叶片/狸花猫

    多态的优越性

     

    class Animal(object):  # 创建一个基类
        def describe(self):
            print('这是Animal基类')
    
    class Dog(Animal):
        def describe(self):
            print('这是继承Animal类的Dog类, 这里是狗')
    
    
    class Cat(Animal):  # 子类
        def describe(self):
            print('这是继承Animal类的Cat类, 这里是猫')
    
    
    class Bird(Animal):  # 子类
        def describe(self):
            print('这是继承Animal类的Bird类, 这里是鸟')
    
    def uniform_invoke(obj):
        obj.describe()
    
    obj_list = [Dog(), Cat(), Bird()]
    for i in obj_list:
        uniform_invoke(i)
    
    # 这是继承Animal类的Dog类, 这里是狗
    # 这是继承Animal类的Cat类, 这里是猫
    # 这是继承Animal类的Bird类, 这里是鸟
    # 
    # Process finished with exit code 0

    这里需要一点想象力, 假设之后需要拓展功能代码, 直接增加bird类, 然后让如列表即可.

     

    鸭子类型 duck typing

    • 鸭子类型(英语:duck typing)是动态类型的一种风格。
    • 在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由”当前方法和属性的集合”决定。
    • “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
    • “当看到一个人走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这个人就可以被称为鸭子。”
    • 在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型。

     

    class Animal(object):  # 创建一个基类
        def describe(self):
            print('这是Animal基类')
    
    class Dog(Animal):
        def describe(self):
            print('这是继承Animal类的Dog类, 这里是狗')
    
    
    class Cat(Animal):  # 子类
        def describe(self):
            print('这是继承Animal类的Cat类, 这里是猫')
    
    
    class Bird(Animal):  # 子类
        def describe(self):
            print('这是继承Animal类的Bird类, 这里是鸟')
    
    #### 鸭子类型特性
    class Human(object):
        def describe(self):
            print('这是基类Human')
    
    class Student(Human):
        def describe(self):
            print('这是继承Human类的学生类, 这里是学生')
    ####
    
    
    def uniform_invoke(obj):
        obj.describe()
    
    obj_list = [Dog(), Cat(), Bird(), Student()]
    for i in obj_list:
        uniform_invoke(i)
    
    # 这是继承Animal类的Dog类, 这里是狗
    # 这是继承Animal类的Cat类, 这里是猫
    # 这是继承Animal类的Bird类, 这里是鸟
    # 这是继承Human类的学生类, 这里是学生
    #
    # Process finished with exit code 0

     

    只关注Student()对象本身, 而不管其属于Animal类还是Human类.

     

    封装

     

    前置基础知识

    前瞻与概括

    • 实例方法,第一个参数必须要默认传实例对象,一般习惯用self。如果类调用实例方法,需要显示的传self, 也就是实例对象自己
      • def function_name(self, parm1, parm2)
    • 类方法(由@classmethod装饰的方法),可以被类或类的实例对象调用。第一个参数必须要默认传类,一般习惯用cls。
      • @classmethod
            def function_name(cls):
    • 静态方法,参数没有要求 (但可以带参数)。静态方法(由@staticmethod装饰的方法)
      • @staticmethod
            def function_name():
      • 静态方法可以通过类对象和实例对象去访问, 但是很少使用实例对象去访问.
        • 静态方法可通过类对象直接方法, 不需要额外的实例化操作,  减少代码和开销.
        • 由于静态方法主要来存放逻辑性的代码,本身和类以及实例对象没有交互. 即在静态方法中,通常不会涉及到类中方法和属性的操作.
    • 备注:
      • 如果存在相同名称的 实例属性 和 类属性, 实例属性的优先级更高.

    类属性和实例属性

    类属性, 类对象和实例对象, 都可以访问

    实例属性, 只有实例对象可以访问

     

    class Animal(object):
        name = "黠灵"
    
        def __init__(self, age):
            self.age = age
    
    xialing = Animal(3)
    
    print(xialing.name)     # 实例对象 访问 类属性 √
    print(xialing.age)      # 实例对象 访问 实例属性 √
    
    print(Animal.name)      # 类对象 访问 类属性 √
    # print(Animal.age)     # 类对象 访问 实例属性 × # AttributeError: type object 'Animal' has no attribute 'age'
    

     

     

    类方法

    class Animal(object):
        name = "黠灵"  # 声明一个类属性
    
        @classmethod  # 运用 @classmethod 声明一个类方法
        def describe(cls):
            return cls.name
    
    xialing = Animal()
    
    print(xialing.describe())  # 实例对象 访问 类方法 √
    print(Animal.describe())   # 类对象 访问 类方法 √
    
    # 黠灵
    # 黠灵
    # Process finished with exit code 0
    通过类方法修改类属性
    class Animal(object):
        name = "黠灵"  # 声明一个类属性
    
        @classmethod  # 运用 @classmethod 声明一个类方法
        def describe(cls):
            return cls.name
    
        @classmethod  # 运用 @classmethod 声明一个类方法
        def describe_modified(cls, parm):
            cls.name = parm
    
    
    xialing = Animal()
    print(xialing.describe())  # 实例对象 访问 类方法 √ 黠灵
    print(Animal.describe())   # 类对象 访问 类方法 √ 黠灵
    
    Animal.describe_modified('狸花')
    print(Animal.describe())  # 狸花
    
    # 类方法可以修改类属性

    静态方法

    class Animal(object):
        name = "黠灵"  # 声明一个类属性
    
        @classmethod  # 运用 @classmethod 声明一个类方法
        def describe(cls):
            return cls.name
    
    
        @staticmethod
        def describe_again():
            return Animal.name
    
    xialing = Animal()
    print(xialing.describe())  # 实例对象 访问 静态方法 √ 黠灵 
    print(Animal.describe())   # 类对象 访问 静态方法 √ 黠灵
    
    print(xialing.describe_again())  # 实例对象 访问 静态方法 √ 黠灵 (一般不会这么做)
    print(Animal.describe_again())   # 直接通过类对象 访问静态方法 √ 不需要实例化.
    
    直接通过类对象 访问静态方法
    import  time
    class TimeTest:
        # def __init__(self,hour,min,second):
        #     self.hour=hour
        #     self.min = min
        #     self.second = second
    
        @staticmethod
        def showTime():
            return time.strftime("%H:%M:%S",time.localtime())
            pass
        pass
    
    print(TimeTest.showTime())
    
    
    # t=TimeTest(2,10,15)
    # print(t.showTime()) #没有必要通过这种方式去访问 静态方法
    带参数的静态方法
    class Demo(object):
        
        @staticmethod
        def summation(a, b):
            print(a + b)
            
    Demo.summation(1,2)
    
    # 3
    Process finished with exit code 0

     

    什么是封装?

    如何实现封装

    私有属性

    私有化的实例/类属性, 不能在类外部直接访问

    没有使用私有属性封装的代码形式

    class Animal(object):
        def __init__(self):
            self.name = "黠灵"
            self.age = "6"
    
    xialing = Animal()
    print(xialing.name, xialing.age)

    使用了私有属性封装的代码形式

    class Animal(object):
        def __init__(self):
            self.__name = "黠灵"      # 使用双下划线, 设置一个私有化属性__name
            self.age = "9999"
    
        # __str__ 将类的实例变成 str; print打印出来是个对象;使用了就把对象变成字符串
        def __str__(self):
            return f'狗名:{self.__name} | 狗命 {self.age}'
    
    xialing = Animal()
    print(xialing)
    # print(xialing.name, xialing.age) # AttributeError: 'Animal' object has no attribute 'name'
    

     

    如果不理解__str__,  可以看另一个实例

    
    class Animal(object):
        def __init__(self):
            self.__name = "黠灵"      # 设置一个私有化属性
            self.age = "9999"
    
        def get_data(self):
            print(f'狗名:{self.__name} | 狗命 {self.age}')
    
    xialing = Animal()
    xialing.get_data()      # 此时在类的外部, 实例对象, 通过类内部的get_data, 间接地访问__name
    print(xialing.age)      # 此时在类的外部, 实例对象, 直接访问类内部的age
    #print(xialing.__name)  # 此时在类的外部, 实例变量, 直接访问类内部的私有变量 __name (报错)
    
    

     

    父类中私有化的实例/类属性, 不能被子类继承
    class Animal(object):
        def __init__(self):
            self.__name = "黠灵"      # 设置一个私有化属性
            self.age = "9999"
    
        # __str__ 将类的实例变成 str; print打印出来是个对象;使用了就把对象变成字符串
        def __str__(self):
            return f'狗名:{self.__name} | 狗命 {self.age}'
    
    xialing = Animal()
    print(xialing)
    # print(xialing.name, xialing.age) # AttributeError: 'Animal' object has no attribute 'name'
    
    class Cat(Animal):
        pass
    
    lihua = Cat()
    print(lihua.__name)  # AttributeError: 'Cat' object has no attribute '__name'
    # 子类的Cat的实例化对象lihu 在类的外部, 无法访问__name 属性

     

    私有化的实例/类属性, 可以在类的内部访问

    以上代码已证实

     

    私有方法

     

    class Animal(object):
        def fly(self):
            print("fly")
    
        def climb(self):
            print('climb')
    
    
    xialing = Animal()
    xialing.fly(), xialing.climb() # 正常访问
    

     

    class Animal(object):
        def __fly(self):    # 一个私有化方法
            print("fly")
    
        def climb(self):
            print('climb')
    
    
    xialing = Animal()
    xialing.fly(), xialing.climb()  # 报错
    

     

    property方法

     

    封装的目的不是为了完全的封闭, 而是为了更安全, 更高效的提供数据. 大多数情况下, 私有化的属性和方法, 还是要和外部进行交互. 正常情况, 可以在类的内部设计一个用于和外部交换的方法. 但除此之外, Python 还提供了一个专门的函数 property(). 直观和通俗的讲:

    • 通常访问私有属性, 需要设置两个方法, 一个修改, 一个访问.
    • 这种调用方式, “感觉”是调用一个方法, 而不是访问属性. 那么如何让调用者可以直接访问属性, 而且我们又能控制的方式 提供给调用者?
    • property
    • property 还可以和装饰器结合使用, 快速高效地实现一些功能

     

    class Animal(object):
        def __init__(self):
            self.__age = 6
    
    
        def get_data(self):
            return self.__age
    
        def set_data(self, value):
           self.__age = value
    
    
        age = property(get_data, set_data)
    
        print(type(age))
    
    xialing = Animal()
    xialing.age = 999
    print(xialing.age)  

    语法特性

    • property 可以修饰类中属性的 get 、 set 和 del 方法,以及为属性对象创建文档字符串。
    • 原型为 class property(fget=None, fset=None, fdel=None, doc=None) ,它返回 property 属性。
    • fget 是获取属性值的函数。 fset 是用于设置属性值的函数。 fdel 是用于删除属性值的函数。 doc 为属性对象创建文档字符串。

    本实例中

    • xialing.age = 999 调用 setter
    • xialing.age 调用 getter

     

    单例模式

    魔术方法 __new__

    单例模式是常用和典型的”封装”形式和提现. Python里的单例模式有很多种实现方式,  比如模块, 装饰器, 类方法.  这里是通过魔术方法new.

    • 当我们实例化一个对象时,是先执行了类的__new__方法,实例化对象.
    • 然后再执行类的__init__方法,对这个对象进行初始化.
    • 那么在new 和 init之间, 可以进行判断, 如果类里面具有某个属性, 则不再创建对象.
    • 从而实现”一个类里面, 只有一个实例”.

     

    class Animal(object):
        def __init__(self):
            pass
    
    
        def __new__(cls, *args, **kwargs):
            cls._instance = super().__new__(cls, *args, **kwargs)
            return cls._instance
    
    xialing = Animal()
    print(id(xialing))
    
    huban = Animal()
    print(id(huban))
    
    
    # 1639090149520
    # 1639090149648
    # Process finished with exit code 0

    可以看到生成两个类

     

    class Animal(object):
        def __init__(self):
            pass
    
    
        def __new__(cls, *args, **kwargs):
            if not hasattr(cls,'_instance' ):
                cls._instance = super().__new__(cls, *args, **kwargs)
            return cls._instance
    
    xialing = Animal()
    print(id(xialing))
    
    huban = Animal()
    print(id(huban))
    
    
    # 2642697705424
    # 2642697705424
    # Process finished with exit code 0

    单例模式的实现

     

    动态绑定

    动态绑定实例方法

    import types
    
    class Animal(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        # def __str__(self):
        #     print(f'self.name | self.age')
    
    
    def add_method(self):
        print(f'{self.name} | {self.age}| {self.color}')
    
    
    xialing = Animal('黠灵', '10')
    
    # 动态增加属性 color
    xialing.color = 'yellow'
    
    # 动态绑定add_method方法
    xialing.describe = types.MethodType(add_method, xialing)
    
    # 调用动态绑定的方法
    xialing.describe()

    实例方法describe

     

    动态绑定类方法

    import types
    
    class Animal(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        # def __str__(self):
        #     print(f'self.name | self.age')
    
    
    def add_method(self):
        print(f'{self.name} | {self.age}| {self.color}')
    
    @classmethod
    def add_classmethod(cls):
        print('Animal类 动态添加了 类方法describe_classmethod ')
    
    Animal.describe_classmethod = add_classmethod
    Animal.describe_classmethod()
    
    # Animal类 动态添加了 类方法describe_classmethod 

     

    动态绑定静态方法

    import types
    
    class Animal(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        # def __str__(self):
        #     print(f'self.name | self.age')
    
    
    def add_method(self):
        print(f'{self.name} | {self.age}| {self.color}')
    
    @staticmethod
    def add_staticmethod():
        print('Animal类 动态添加了 静态方法describe_staticmethod ')
    
    Animal.describe_staticmethod = add_staticmethod
    Animal.describe_staticmethod()
    
    # Animal类 动态添加了 静态方法describe_staticmethod 

    限制属性动态添加 __slot__()

     

    class Animal(object):
        __slots__ = ('name', 'age')
        # __slots__ = ('name') # AttributeError: 'Animal' object has no attribute 'age'
    
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    
        def describe(self):
            print(f'{self.name} | {self.age}')
    
    xialing = Animal('黠灵', '10')
    xialing.describe()

     

     

     

  • Python 数据类型和基础

    基本数据类型

    Number(数字)

    int

    float

    bool

    complex

    String(字符串)

     

    page = '1'
    pagination='2'
    id='3'
    link = f"https://www.url.com/{page}/{pagination}/{id}/string"
    print(link)
    
    # https://www.url.com/1/2/3/string

     

    List(列表)

    Dictionary(字典)

    Tuple(元组)

    Set(集合)

     

    复杂数据类型

     

     

    推导式, 迭代器, 生成器

    推导式

    迭代器

    生成器

     

    循环和条件控制

    for 循环

    while 循环

    异常处理

    常规异常处理

    try:
        可能出现错误的代码
        
    except:
        出错之后执行的代码
        
    else:
        没有出错的代码
    
    finally:
        无论是否出错都会执行的代码

     

    def f1(number):
        return 10 / int(number)
    
    
    def f2(number):
        return f1(int(number)) * 10
    
    
    def f3():
        try:
            f2('0')
    
        except Exception as e:
            print(e)
        pass
    
    
    if __name__ == "__main__":
        f3()
        
    # division by zero
    # Process finished with exit code 0

     

    try:
        print(a)
    except Exception as e:
        print(e)
    else:
        print("当try里面的代码没有错误时, 此处代码才会执行")
    
    
    
    
    try:
        print('a')
    except Exception as e:
        print(e)
    else:
        print("当try里面的代码没有错误时, 此处代码才会执行")

     

    try:
        print(a)
    except Exception as e:
        print(e)
    finally:
        print("无论try里面的代码没有错误, 此处代码都会执行")

     

    自定义抛出异常

    class NameLengthException(Exception):
        def __init__(self, length):
            self.length = length
    
        def __str__(self):
            return f'你输入的姓名长度是{str(self.length)}, 已超出'
    
    def mame_length_check():
        name = input('请输入姓名:')
    
        try:
    
            if len(name) > 4:
                raise NameLengthException(len(name))
            else:
                print(f'你的姓名长度是{len(name)}, 符合规则.')
        except NameLengthException as e:
            print(e)
    
        else:
            print('你已成功完成姓名输入')
    
    
    
    if __name__ == "__main__":
        mame_length_check()
    
    # 请输入姓名:李桃李满天下
    # 你输入的姓名长度是6, 已超出
    # Process finished with exit code 0

     

     

    文件操作

    文件创建

    文件写入

    目录读取