• django基础之Django模板


    Web模板系统

    一个模板的渲染需要三方参与:

    • 模板引擎: 主要参与模板渲染的系统
    • 内容源: 输入的数据。比较常见的有数据库、XML文件和用户请求这样的网络数据
    • 模板: 一般是和语言相关的文本

    模板系统的工作过程如图所示

    由图可知, 模板和内容源由模板引擎处理和组合, 批量生成Web文档。

    模板介绍

    在之前的章节中, 视图函数只是直接返回文本, 而在实际生产环境中其实很少这样用, 因为实际的页面大多是带有样式的HTML代码, 这可以让浏览器渲染出非常漂亮的页面。目前市面上有非常多的模板系统, 其中最知名最好用的就是DTL和jinja2。DTLDjango Template Language三个单词的缩写, 也就是django自带的模板语言。当然也可以配置Django支持jinja2等其他模板引擎, 但是作为Django内置的模板语言, 和Django可以达到无缝衔接而不会产生一些不兼容的情况

    DTL与普通的HTML的区别

    DTL模板是一种带有特殊语法的HTML文件, 这个HTML文件可以被Django编译, 可以传递参数进去, 实现数据动态化。在编译完成后, 生成一个普通的HTML文件, 然后发送给客户端

    渲染模板

    渲染模板有多种方式, 这里讲下两种常见的方式

    1. render_to_string: 找到模板, 然后将模板编译后渲染成Python的字符串格式。最后再通过HttpResponse类包装成一个HttpResponse对象返回回去。示例代码如下:

    from django.template.loader import render_to_string
    from django.http import HttpResponse
    
    def book_detail(request, book_id):
        html = render_to_string('detail.html')
        return HttpResponse(html)

    2. django还提供了一个更加简便的方式, 直接将模板渲染成字符串和包装成HttpResponse对象一步到位完成。示例代码如下:

    from django.shortcuts import render
    
    def book_list(request):
        return render(request, 'list.html')

    模板查找路径

    在项目的settings.py文件中, 有一个TEMPLATES配置, 这个配置包含了模板引擎的配置, 模板查找路径的配置, 模板上下文配置等。模板路径可以在两个地方配置。

    1. DIRS: 这是一个列表, 在这个列表中可以存放所有的模板路径, 以后在视图中使用render或者render_to_string渲染模板的时候, 会在这个列表的路居中查找模板

    2. APP_DIRS: 默认为True, 这个设置为True后, 会在INSTALLED_APPS的安装了的APP下的templates文件夹中查找模板。

    3. 查找顺序: 比如代码render('list.html')。先会DIRS在这个列表中依次查找路径下有没有这个模板,如果有,就返回。如果DIR列表中所有的路径都没有找到, 那么会先检查当前这个视图所处的app是否已经安装, 如果已经安装了, 那么就先在当前这个app下的templates文件夹中查找模板, 如果没有找到, 那么会在其他已经安装了的app中查找。如果所有路径下都没有找到,那么会抛出一个TemplatesDoesNotExist 的异常

    模板变量

    DTL模板语法

    模板中可以包含变量, Django在渲染模板的时候, 可以传递变量对应的值去进行替换。变量的命名规范和Python非常类似, 只能是阿拉伯数字和英文字符以及下划线的组合, 不能出现标点符号等特殊字符。变量需要通过视图函数渲染, 视图函数是在使用render或者render_to_string的时候可以传递一个context的参数, 这个参数是一个字典类型。以后在模板中的变量就从这个字典中读取值的。示例代码如下:

    # profile.html模板代码
    <p>{{ username }}</p>
    
    # views.py代码
    def profile(request):
        return render(request, 'profile.html', context={'username':'zhangjie'})

    模板中的变量也支持点(.)的形式。在出现了点的情况, 比如person.username, 模板是按照以下方式进行解析的:

    1. 如果person是一个字典, 那么就会查找这个字典的username这个key对应的值

    2. 如果person是一个对象, 那么就会查找这个对象的username属性, 或者是username这个方法

    3. 如果出现的是person.1, 会判断persons是否是一个列表或者元组或者任意可以通过下标访问的对象, 如果是的话就获取这个列表的第一个值。如果不是就获取到的是一个空的字符串

    不能通过中括号的形式访问字典和列表中的值, 比如dict['key']和list[1]是不支持的!

    因为使用点(.)语法获取对象的时候, 可以获取这个对象的属性, 如果这个对象是一个字典, 也可以获取这个字典的值, 因此不能在字典中定义字典本身就有的属性当做key, 否则字典的那个属性将变成字典中的key了。

    常用标签

    if标签

    if标签相当于Python中的if语句, 有elseelif相对应, 但是所有的标签都需要用标签符号({%%})进行包裹。if标签中可以使用==、!=、<、<=、>、>=、in、not in、is、is not等判断运算符。示例代码如下

    {% if '张三' in persons %}
        <p>张三</p>
    {% else %}
        <p>李刚</p>
    {% endif %}

    for...in...标签

    for...in...类似于Python中的for...in...。可以遍历列表、元组、字符串、字典等一切可以遍历的对象。示例代码如下:

    {% for person in persons %}
        <p>{{ person.name }}</p>
    {% endfor %}

    如果想要反向遍历, 那么在遍历的时候就加上一个reversed。示例代码如下:

    {% for person in persons reversed %}
        <p>{{ person.name }}</p>
    {% endfor %}

    遍历字典的时候, 需要使用itemskeysvalues等方法。在DTL中, 执行一个方法不能使用圆括号的形式。遍历字典的代码如下:

    {% for key, value in persons.item %}
        <p>key:{{ key }}</p>
        <p>value:{{ value }}</p>
    {% endfor %}

    for循环中, DTL提供了一些变量可供使用。这些变量如下:

    • forloop.counter: 当前循环的下标, 以1作为起始值

    • forloop.counter0: 当前循环的下标, 以0作为起始值

    • forloop.revcounter: 当前循环的反向下标值。比如列表有5个元素, 那么第一次遍历这个属性是等于5, 第二次是4, 依次类推。并且是以1作为最后一个元素的下标

    • forloop.revcounter0: 类似于forloop.counter。不同的是最后一个元素的下标是从0开始

    • forloop.first: 是否第一次遍历

    • forloop.last: 是否是最后一次遍历

    • forloop.parentloop: 如果有多个循环嵌套, 那么这个属性代表的是上一级的for循环 

    模板中的for...in...标签没有continue和break语句, 这一点和Python中有很大的不同, 一定要记清楚!

    for...in...empty标签

    这个标签使用跟for...in是一样的。只不过是在遍历的对象如果没有元素的情况下, 会执行empty中的内容。示例代码如下:

    {% for person in persons reversed %}
        <p>{{ person.name }}</p>
    {% empty %}
        <p>{{ 暂时还没有人 }}</p>
    {% endfor %}

    with标签

    在模板中定义变量。有时候一个变量访问的时候比较复杂, 那么可以先把这个变量缓存到一个变量上, 以后就可以直接使用这个遍历就可以了。示例代码如下:

    context = {
        'person': ['张三', '李四']
    }
    
    {% with lisi=person.1 %}
        <p>{{ lisi }}</p>
    {% endwith %}

    有几点需要强烈注意:

    • with语句中定义的变量, 只能在{% with %}{% endwith %}中使用, 不能在这个标签外面使用。

    • 定义变量的时候, 不能再等号左右两边留有空格。比如{% with lisi = person.1 %}是错误的

    • 还有另外一种写法也同样支持

    {% with person.1 as lisi %}
        <p>{{ lisi }}</p>
    {% endwith %}

    url标签

    在模板中, 我们经常要写一些url, 比如某个a标签中需要定义href属性。 当然如果通过硬编码的方式直接将这个url写死在里面也是可以的。但是这样对于以后项目维护可能不是一件好事。因此建议使用这种反转的方式来实现, 类似于django中的reverse一样。示例代码如下:

    <a href="{% url 'book:list' %}">图书列表页面</a>

    如果url反转的时候需要传递参数, 那么可以在后面传递。但是参数分位置参数和关键字参数。位置参数和关键字参数不能同时使用。示例代码如下:

    # path部分
    path('detail/<book_id>/', view.book_detail, name='detail')
    
    # url反转, 使用位置参数
    <a href='{% url "book:detail" 1 %}'>图书详情页面</a>
    
    # url反转, 使用关键字参数
    <a href="{% url 'book:detail' book_id=1 %}">图书详情页面</a>

    如果想要在使用url标签反转的时候要传递查询字符串的参数, 那么必须要手动在后面添加。示例代码如下:

    <a href='{% url "book:detail" 1 %}?page=1'>图书详情页面</a>

    如果需要传递多个参数, 那么通过空格的方式进行分隔。示例代码如下:

    <a href="{% url 'book:detail' book_id=1 page=2 %}">图书详情页面</a>

    spaceless标签

    移除html标签中的空白字符。包括空格、tab键、换行等。示例代码如下:

    {% spaceless %}
        <p>
            <a href='foo/'>foo</a>
        </p>
    {% endspaceless %}

    那么在渲染完成后, 会变成以下的代码:

    <p><a href='foo/'>foo</a></p>

    spaceless只会移除html标签之间的空白字符, 而不会移除标签与文本之间的空白字符。

    {% spaceless %}
        <strong>
            Hello 
        </strong>
    {% endspaceless %}

    这个将不会移除strong中的空白字符

    autoescape标签

    开启和关闭这个标签内元素的自动转义功能。自动转义是可以将一些特殊的字符。比如<转义成html语法能识别的字符, 比如<会被转义成&lt;, 而>会被转义成&gt;。模板中默认是已经开启了自动转义的。autoescape的示例代码如下

    # 传递的上下文信息
    context = {
        'info':'<a href="www.baidu.com">百度</a>',
    }
    
    # 模板中关闭自动转义
    {% autoescape on %}
        {{ info }}
    {% endautoescape %}

    那么就会显示百度的一个超链接。如果把on换成off, 那么就会显示成一个普通的字符串。示例代码如下:

    {% autoescape off%}
        {{ info }}
    {% endautoescape %}

    verbatim标签

    默认在DTL模板中是会去解析哪些特殊字符的。比如{%%}以及{{之间}}等。如果你再某个代码片段中不想使用DTL的解析引擎。那么可以把这个代码片段放在verbatim标签中。示例代码如下:

    {% verbatim %}
        {{if dying}}Still alive.{{/if}}
    {% endverbatim%}

    更多标签请参考官方文档: https://docs.djangoproject.com/en/2.0/ref/templates/builtins

    常用过滤器

    在模板中, 有是有需要对一些数据进行处理以后才能使用。一般在Python中我们是通过函数的形式来完成的。而在模板中, 则是通过过滤器来实现的。过滤器使用的是|来使用。比如使用add过滤器, 那么示例代码如下:

    {{ value|add:"2" }}

    add

    将传进来的参数添加到原来的值上面。这个过滤器会尝试将参数转换成整形然后进行相加。如果转换成整形的过程中失败了, 那么就会将参数进行拼接。如果是字符串, 那么就会拼接成字符串。如果是列表, 那么就会拼接成一个列表。示例代码如下:

    {{ value|add:"2" }}

    如果value是等于4, 那么结果将是6, 如果value是等于一个普通的字符串, 比如abc, 那么结果将是abc2add过滤器的源码如下:

    def add(value, arg):
        """Add thr arg to the value."""
        try:
            return int(value) + int(arg)
        except (ValueError, TypeError):
            try:
                return value+arg
            exceptreturn ''

    cut

    移除值中所有指定字符串, 类似于Python中的replace(arg, "")。示例代码如下:

    {{ value|cut:" "}}

    以上示例将会移除value中所有的空格字符串。cut过滤器的源代码如下:

    def cut(value, arg):
        """ Remove all values of arg from the given string."""
        safe = isinstance(value, SafeData)
        value = value.replace(arg, '')
        if safe and arg != ';':
            return mark_safe(value)
        return value

    date

    将一个日期按照指定的格式, 格式化成字符串。示例代码如下:

    # 数据
    context = {
        'birthday': datetime.now(),
    }
    
    # 模板
    {{ birthday|date: "Y/m/d" }}

    那么将会输出2018/02/01。其中Y代表的是四位数字的年份。m代表的是两位数字的月份。d代表的是两位数字的日。还有更多时间格式化的方式。见如下表

    格式字符描述示例
    Y 四位数字的年份 2018
    m 两位数字的月份 01-12
    n 月份, 1-9之前没有0前缀 1-12
    d 两位数字的天 01-31
    j 天, 但是1-9前面没有0前缀 1-31
    g 小时, 12小时格式的, 1-9之前没有0前缀 1-12
    h 小时, 12小时格式的, 1-9之前有0前缀 01-12
    G 小时, 24小时格式的, 1-9之前没有0前缀 1-23
    H 小时, 24小时格式的, 1-9之前有前缀 01-23
    i 分钟, 1-9之前有0前缀 00-59
    s 秒, 1-9之前有0前缀 00-59

    default

    如果值被评估为False。比如[]""None{}等这些在if判断中为False的值, 都会使用default过滤器提供的默认值。示例代码如下:

    {{ value|default:'nothing' }}

    如果value是等于一个空的字符串。比如"", 那么以上代码将会输出nothing

    default_if_none

    如果值是None, 那么将会使用default_if_none提供的默认值。这个和default有区别, default是所有评估为False的都会使用默认值。而default_if_none则只有这个值是等于None的时候才会使用默认值。示例代码如下:

    {{ value|default:"nothing" }}

    如果value是等于""也即空字符串, 那么以上将会输出空字符串。如果value是一个None值,以上代码才会输出nothing

    first

    返回列表/元组/字符串中的第一个元素。示例代码如下:

    {{ value|first }}

    如果value是等于['a', 'b', 'c'], 那么输出将会是a

    last

    返回列表/元组/字符串的最后一个元素。示例代码如下:

    {{ value|last }}

    如果value是等于['a', 'b', 'c'], 那么输出将会是c

    floatformat

    使用四舍五入的方式格式化一个浮点类型。如果这个过滤器没有传递任何参数。那么只会在小数点后保留一个小数, 如果小数后面全是0, 那么只会保留整数。当然也可以传递一个参数, 标识具体要保留几个小数。

    1. 如果没有传递参数

    value模板代码输出
    34.23234 {{ value|floatformat}} 34.2
    34.000 {{ value|floatformat }} 34
    34.260 {{ value|floatformat }} 34.3

    2. 如果传递参数

    value模板代码输出
    34.23234 {{ value|floatformat:3}} 34.232
    34.00000 {{value|floatformat:3}} 34.000
    34.26000 {{value|floatformat:3}} 34.260

    join

    类似于Python中的join, 将列表/元组/字符串用指定的字符进行拼接。示例代码如下:

    {{ value|join '/'}}

    如果value是等于['a', 'b', 'c'], 那么上面代码将输出a/b/c

    length

    获取一个列表/元组/字符串/字典的长度。示例代码如下:

    {{ value|length }}

    如果value是等于['a', 'b', 'c'], 那么上面代码将输出3。如果valueNone, 那么以上将返回0

    lower

    将值中所有的字符全部转换成小写。示例代码如下: 

    {{ value|lower }}

    upper

    将值中所有的字符全部转换成小写。示例代码如下:

    {{ value|upper }}

    如果value是等于Hello World。那么以上代码将输出HELLO WOLRD

    random

    在被给的列表/字符串/元组中随机选择一个值。示例代码如下:

    {{ value|random }}

    如果value是等于['a', 'b', 'c'], 那么以上代码会在列表中随机选择一个

    safe

    标记一个字符串是安全的。也即会关掉这个字符串的自动转义。示例代码如下:

    {{ value|safe }}

    如果value是一个不包含任何特殊字符的字符串, 比如<a>这种, 那么以上代码将会把字符串正常输入。如果value是一串html代码, 那么以上代码将会把这个html代码渲染到浏览器中

    slice

    类似于Python中的切片操作。示例代码如下:

    {{ some_list|slice:'2:'}}

    以上代码将会给some_list2开始做切片操作

    stringtags

    删除字符串中所有的html标签。示例代码如下:

    {{ value|stringtags }}

    如果value<strong>hello world</strong>, 那么以上代码将会输出hello world

    truncatechars

    如果给定的字符串长度超过了过滤器指定的长度。那么将会进行切割, 并且会拼接三个点来作为省略号。示例代码如下:

    {{ value|truncatechars:5 }}

    如果value等于北京欢迎您~, 那么输出结果是北京...

    truncatechars_html

    类似于truncatechars, 只不过是不会切割html标签。示例代码如下:

    {{ value|truncatechars_html }}

    如果value等于<p>北京欢迎您~</p>, 那么输出结果是<p>北京...</p>

    自定义过滤器

    自定义过滤器介绍

    虽然DTL给我们内置了许多好用的过滤器。但是有些时候还是不能满足我们的需求。因此Django给我们提供了一个接口, 可以让我们自定义过滤器, 实现自己的需求

    模板过滤器必须要放在app中, 并且这个app丙戌要在INSTALLED_APPS中进行安装。然后在这个app下面创建一个Python包叫做templatetags。再在这个包下面创建一个python文件。比如app的名字叫做book, 那么项目结构如下:

    - book
        - views.py
        - urls.py
        - models.py
        -templatetags
            - my_filter.py

    在创建了存储过滤器的文件后, 接下来就是在这个文件中写过滤器了。过滤器实际上就是python中的一个函数, 只不过把这个函数注册到模板库中, 以后就可以使用这个函数了。但是这个函数的参数有限制, 第一个参数必须是这个过滤器需要处理的值, 第二个参数可有可无, 如果有, 那么就意味着在模板中可以传递参数。并且过滤器的函数最多只能有两个参数。在写完过滤器后, 再使用django.template.Library对象注册进去。示例代码如下:

    from django import template
    
    # 创建模板库对象
    register = template.Library()
    
    # 过滤器函数
    def mycut(value, mystr):
        return value.replace(mystr)
    
    # 将函数注册到模板库中
    register.filter('mycut', mycut)

    以后想要在模板中使用这个过滤器, 就要在模板中load以下这个过滤器所在的模块的名字(也就是这个python文件的名字)。示例代码如下:

    {% load my_filter %}

    自定义时间过滤器

    有时候经常会在朋友圈、微博中可以看到一条信息发表的时间, 并不是具体的时间, 而是距离现在多久。比如刚刚1分钟前等。这个功能DTL是没有内置这样的过滤器的, 因此我们可以自定义一个这样的过滤器。示例代码如下:

    # time_filter.py文件
    from datetime import datetime
    from django import template
    
    register = template.Library()
    
    def time_since(value):
        if isinstance(value, datetime):
            now = datetime.now()
            timestamp = (now-value).total_seconds()
            if timestamp < 60:
                return '刚刚'
               elif timestamp < 60*60:
                minutes = int(timestamp/60)
                return '%s分钟前'%minutes
            elif timestamp < 60 * 60 * 24:
                hours = int(timstamp/(60*60))
                return '%s小时前'%hours
            elif timestamp < 60 * 60 * 24 * 30:
                days = int(timestamp/(60*60*24))
                return '%s天前'%days
            else:
                return value.strftime('%Y/%m/%d %H:%M')
        else:
            return value
    
    register.filter('time_since', time_since)

    在模板中使用的示例代码如下:

    {% load time_filter %}
    ...
    {% value|time_since %}
    ...

    为了更加方便的将函数注册到模板库中当做过滤器。也可以使用装饰器来讲一个函数包装成过滤器。示例代码如下:

    from django import template
    
    register = template.Library()
    
    @register.filter(name='mycut')
    def mycut(value, mystr):
        return value.replace(mystr, '')

    模板结构优化

    引入模板

    有时候一些代码在许多模板中都用到的。如果我们每次重复的去拷贝代码那肯定不符合项目的规范。一般我们可以把这些重复的代码抽取出来。就类似于Python中的函数一样, 以后想要使用这些代码的时候, 就通过include包含进来。这个标签就是include。示例代码如下:

    # header.html
    <p>我是header</p>
    
    # footer.html
    <p>我是footer</p>
    
    # main.html
    {% include 'header.html' %}
    <p>我是main内容</p>
    {% include 'footer.html' %}

    include标签寻找路径的方式, 也是跟render渲染模板的函数是一样的

    默认include标签包含模板, 会自动的使用主模板中的上下文, 也即是自动的使用主模板中的变量。如果想要传入一些其他的参数, 那么可以使用with语句, 示例代码如下:

    # header.html
    <p>用户名: {{username}}</p>
        
    # main.html
    {% include 'header.html' with username='feather' %}

    模板继承

    在前端页面开发中。有些代码是需要重复使用的。这种情况可以使用include标签来实现。也可以使用另一种比较强大的方式来实现, 那就是模板继承。模板继承类似于Python中的类, 在父类中定义可以先定义一些变量和方法, 然后在子类中实现。模板继承也可以在父模板中定义好一些子模板需要用到的代码, 然后子模板直接继承就可以了。并且因为子模板肯定有自己不同的代码, 因此可以在父模板中定义一个block接口, 然后子模板再去实现。以下是父模板的代码:

    {% load static %}
    <!DOCTYPE html>
    <html lang='en'>
    <head>
        <link rel="stylesheet" href="{% static 'style.css' %}"/>
        <title>{% block title%}我的站点{% endblock %}</title>
    </head>
    
    <body>
        <div id="sidebar">
            {% block sidebar %}
            <ul>
                <li><a href="/">首页</a></li>
                <li><a href="/blog/">博客</a></li>
            </ul>
            {% endblock %}
        </div>
        <div id="content">
            {% block content %}{% endblock %}
        </div>
    </body>
    </html>

    这个模板, 我们取名为base.html, 定义好一个简单的html骨架, 然后定义好两个block接口, 让子模板来根据具体需求来实现。子模板然后通过extends标签来实现, 示例代码如下:

    {% extends 'base.html' %}
    
    {% block title %}博客列表{% endblock %}
    
    {% block content %}
        {% for entry in blog_entries %}
            <h2>{{ entry.title }}</h2>
            <p>{{ entry.body }}</p>
        {% endfor %}
    {% endblock %}

    注意:

    1. extends标签必须放在模板的第一行。

    2. 子模板的代码必须放在block中, 否则不会被渲染

    如果在某个block中需要使用父模板的内容, 那么可以使用{{block.super}}来继承。比如上例, {% block title %}, 如果想要使用父模板的title, 那么可以在子模板的title block中使用{{block.super}}来实现

    在定义block的时候, 除了在block开始的地方定义这个block的名字, 还可以在block结束的时候定义名字。比如{% block title %}{% endblock title %}。这在大型项目模板中显得尤其有用, 能让你快速的看到block包含在那里

    加载静态文件

    在一个网页中。不仅仅只有一个html的骨架, 还需要css样式文件, js执行文件以及一些图片等。因此在DTL中加载静态文件是一个必须要解决的问题。在DTL中, 使用static标签, 首先需要{% load static %}。加载静态文件的步骤 如下:

    1. 首先确保django.contrib.staticfiles已经添加到settings.INSTALLED_APPS

    2. 确保在settings.py中设置STATIC_URL

    3. 在已经安装了的app中创建一个文件夹叫做static, 然后在这个static文件夹下创建了一个当前app的名字的文件夹, 再把静态文件放到这个文件夹下。例如你的appbook, 有一个静态文件叫做app.png, 那么路径为book/static/book/aa.png(为什么在app下创建一个static文件夹, 还需要在这个static下创建一个同app名字的文件夹呢?原因是如果直接把静态文件放到static文件夹下, 那么在模板加载静态文件的时候就是使用aa.png, 如果多个在多个app之间有同名的静态文件, 这时候就很容易产生混淆。而在static文件夹下创建一个同名app的文件夹, 在模板中加载的时候就是使用book/aa.png, 这样就可以避免混淆了)

    4. 如果有一些静态文件是不和任何app挂钩的。那么可以在setting.py中添加STATICFILES_DIRS, 以后DTL就会在这个列表中查找静态文件。比如说可以设置:

    STATICFILES_DIRS = [
        os.path.join(BASE_DIR, 'static')
    ]

    5. 在模板中使用load标签加载static标签。比如要加载在项目的static文件夹下的style.css的文件, 那么示例代码如下:

    {% load static %}
    <link rel="styleheet", href="{% static "style.css" %}">

    6. 如果不想每次在模板中加载静态文件都使用load加载static标签, 那么可以在settings.py中的TEMPLATES/OPTIONS添加'builtins':['django.templatetags.static'], 这样以后在模板中就可以直接使用static标签, 而不用手动的load

    7. 如果没有在settings.INSTALLED_APPS中添加django.contrib.staticfiles。那么我们就需要手动的将请求静态文件的url与静态文件的路径进行映射了。示例代码如下:

    from django.conf import settings
    from django.conf.urls.static import static
    
    urlpatterns = [
        # 其他的url映射
    ]+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
  • 相关阅读:
    教你作一份高水准的简历
    python并发
    阻塞,非阻塞,同步,异步
    python三层架构
    paramiko与ssh
    python-进程
    生产者消费者模型
    python-线程
    python-socket
    python-mysql
  • 原文地址:https://www.cnblogs.com/featherwit/p/13450761.html
Copyright © 2020-2023  润新知