分类: 编程

  • 2025年GitHub 镜像可用站点收集

    镜像是什么

    镜像原意是光学里指的物体在镜面中所成之像。引申到计算机网络上,镜像通常用于为相同信息内容提供不同的源,特别是在下载量大的时候提供了一种可靠的网络连接。制作镜像是一种文件同步的过程。

    什么是镜像网站

    镜像网站,即把一个互联网上的网站数据“拷贝”到本地服务器,并保持本地服务器数据的同步更新,因此也称为“复制网络站点” 。它和主站并没有太大差别,或者可算是为主站作的后备措施。

    有了镜像网站的好处是:如果不能对主站作正常访问(如某个服务器宕机),但仍能通过其它服务器正常浏览。

    镜像 访问 链接 可用
    GitHub镜像站014 直接 https://bgithub.xyz 可用
    GitHub镜像站013 直接 https://kkgithub.com 可用
    GitHub镜像站012 直接 https://gitclone.com 可用
    GitHub镜像站011 直接 https://github.hscsec.cn 不可用
    GitHub镜像站010 直接 https://git.homegu.com 不可用
    GitHub镜像站009 直接 https://github.ur1.fun 可用
    GitHub镜像站008 直接 https://git.homegu.com 不可用
    GitHub镜像站007 文件加速 https://moeyy.cn/gh-proxy/ 可用
    GitHub镜像站006 文件加速 https://ghp.ci/ 可用
    GitHub镜像站005 文件加速 https://gh-proxy.com/ 可用
    GitHub镜像站004 文件加速 https://ghproxy.net/ 可用
    GitHub镜像站003 文件加速 https://ghproxy.homeboyc.cn/ 可用
    GitHub镜像站002 文件加速 https://ghproxy.com/ 不可用
    GitHub镜像站001 文件加速 http://toolwa.com/github/ 可用
  • ChatGPT具备真正的推理能力吗?

    前言

    最近苹果公司工程师 Iman Mirzadeh发表一篇论文“GSM-Symbolic: Understanding the Limitations of Mathematical Reasoning in Large Language Models”的论文,质疑OpenAI o1的推理能力。

    什么是GSM-Symbolic?

    OpenAI 2021 年提出的 GSM8K(Grade School Math 8K)小学数学题数据集已成为评估 LLM 数学推理能力的流行基准。尽管它包含了详细的解决方案的简单数学问题,适合使用思维链(CoT)提示等技术,但它只提供了一个固定问题集上的单一指标。论文作者提出了 GSM-Symbolic,这是一个增强的基准,它使用符号模板生成 GSM8K 问题的多样化变体。这使得研究者能够对 LLM 在各种设置中的性能进行更细致和可靠的评估,超越了单点准确度指标。

    简而简之,GSM8K是包含一堆了鸡兔同笼问题的试卷,让GPT去进行考试,最后对GPT的成绩进行打分。而GSM-Symbolic是一个更为完备的GSM8K,具备更为丰富的试题,更为完善的考试标准,更专业的阅卷老师。

    论文质疑了什么?

    论文作者使用GSM-Symbolic对大模型进行测试,评估其在同一个问题的不同变体上的的回答效果。

    鸡兔同笼问题

    中国古代的数学书籍《孙子算经》,里面就有关于鸡兔同笼问题的描述,在《孙子算经》里,鸡兔同笼问题被叫做雉兔同笼问题。原文是:今有雉、兔同笼,上有三十五头,下有九十四足。问:雉、兔各几何?翻译一下就是:鸡、兔在同一个笼子里,上面有35个头,下面有94只脚。问:鸡、兔各多少只?

    回到问题,Iman Mirzadeh的论文质疑了什么?他测试发现:GPT能很好地回答“鸡、兔在同一个笼子里,上面有35个头,下面有94只脚。问:鸡、兔各多少只?” 但是当这个问题发生调整,但本质上仍然是同一个问题的情况下,GPT的表现会下降。

    鸡兔同笼问题的变体:

    1. 鸡、兔在同一个笼子里,上面有100个头,下面有160只脚。问:鸡、兔各多少只?(改变数字)
    2. 鸡、兔在同一个房间里,经过观察发现它们有100个头和160只脚,那么鸡兔各有多少只(改变描述)
    3. 更多的变体
      1. 增加更多的描述文字
      2. 替换问题中的主题名称

    这些变体不同,但是本质上仍然是一个问题,而GPT的回答效果确不尽相同,所以问题来了:如果GPT具备真正的推理能力,那么它应该能像人类一样能面对这些变体。

    因此Iman Mirzadeh质疑GPT并不具备真正的推理和计算能力,而只是一种复杂的匹配能力。

    质疑的哲学和伦理

    OpenAI:“好像牛顿建立经典力学,爱意斯坦建立相对论,OpenAI登上了山顶,看到了的绝美风景,ChatGPT就是目前的最最佳范式。”

    Apple:“为什么我的试验似乎表明你的范式有问题?你是不是没有登上山顶?我沿着你的路走,会不会掉进坑里? 你想害我不成?”

    对普通用户的影响

    尽管论文里提到的问题确实存在,但是ChatGPT在实际使用里已经足够惊艳众人,特别是在中国这样一个极度注重实用主义的地方,所以这个质疑或许能帮助我们更好的使用GPT。

    优化你的提问用词,精准简洁地描述问题,避免过多无用词的干扰……

  • GPT语言大模型个人使用体验排名。

    2024年更新(忘了几月份)

    排名

    1. GPT-4o:  by OpenAI/Microsoft,ChatGPT开创者
    2. Claude: by Anthropic,在编程领域非常突出,特别是在Cursor内使用时。
    3. Qwen: by 阿里巴巴
    4. Llama: by Meta
    5. Gemini: by Google
    6. Other

    上述五种之外的国内外大模型,使用体验都非常一般,包括马斯克的Grok,抖音的豆包,Kimi,文心一言等等,计算能力和理解能力都较为普通。

    总结

    1. ChatGPT付费版本最好,但是收费,费用还不低,而且并不是有钱就能用,还需要注意IP的稳定性,避免使用脏线路,避免频繁更换IP,否则有可能被限制使用。
    2. Claude的付费版也不错,且注册时需要国外的手机号。免费版也还行。
    3. 通义千问,国内最佳。

     

    2025年5月更新

    时间来到2025年5月份。

    • deepseek 已经天下皆知,效果也确实非常不错。在中文环境使用,如果不知道选择哪个,deepseek会是一个不差的选择。
    • 谷歌 Gemini 表现很好,进步很大。大家可以多试试,官网在这 https://gemini.google.com/
    • Chatgpt 较为平庸。以前是带头大哥,现在只能说是仍然位居前列。
  • 当前文档词语A出现1000次, 词语B出现10次, 词A的置信度一定比B更高吗?

    # 前言

    TFIDF值不仅仅取决于一个词在当前文档中的出现次数(即词频TF),还受到该词在整个文档集合中的分布情况(即逆文档频率IDF)的影响。具体来说,TFIDF值由两个部分组成:

    1. **词频 (Term Frequency, TF)**:这个词在当前文档中出现的频率。
    2. **逆文档频率 (Inverse Document Frequency, IDF)**:这个词在整个文档集合中的稀缺性。

    # 词频 (TF)

     

    **定义**:某个词在当前文档中出现的次数除以文档中总词数。
    **公式**:
    \[ \text{TF}(t) = \frac{\text{词t在文档中出现的次数}}{\text{文档中总词数}} \]

    # 逆文档频率 (IDF)

    **定义**:衡量一个词在整个文档集合中的普遍重要性。如果一个词在很多文档中都出现,那么它的IDF值会较低;反之,如果一个词只在少数文档中出现,那么它的IDF值会较高。
    **公式**:
    \[ \text{IDF}(t) = \log\left(\frac{\text{总文档数}}{\text{包含词t的文档数} + 1}\right) \]

    # TFIDF值

    **定义**:将词频和逆文档频率结合起来,计算出一个词在当前文档中的重要性。
    **公式**:
    \[ \text{TFIDF}(t) = \text{TF}(t) \times \text{IDF}(t) \]

    # 例子

    假设我们有两个词 `a` 和 `b`,它们在当前文档中的出现次数如下:

    词 `a` 在当前文档中出现10次。
    词 `b` 在当前文档中出现1000次。

    但是,在整个文档集合中,这两个词的分布情况不同:

    词 `a` 出现在大多数文档中(例如80%的文档)。
    词 `b` 只出现在少数文档中(例如5%的文档)。

    ## 计算过程

    假设总文档数为1000个文档:

    **词 `a` 的IDF**:
    \[ \text{IDF}(a) = \log\left(\frac{1000}{0.8 \times 1000 + 1}\right) \approx \log(1.25) \approx 0.0969 \]

    **词 `b` 的IDF**:
    \[ \text{IDF}(b) = \log\left(\frac{1000}{0.05 \times 1000 + 1}\right) \approx \log(19.6078) \approx 1.292 \]

    假设当前文档中有1000个词:

    **词 `a` 的TF**:
    \[ \text{TF}(a) = \frac{10}{1000} = 0.01 \]

    **词 `b` 的TF**:
    \[ \text{TF}(b) = \frac{1000}{1000} = 1.0 \]

    ## 计算TFIDF值

    **词 `a` 的TFIDF**:
    \[ \text{TFIDF}(a) = 0.01 \times 0.0969 \approx 0.000969 \]

    **词 `b` 的TFIDF**:
    \[ \text{TFIDF}(b) = 1.0 \times 1.292 \approx 1.292 \]

    # 结论

    • 即使词 `a` 在当前文档中出现次数较少(10次),但由于它在大多数文档中都出现,其IDF值很低,导致最终的TFIDF值也很低。相反,词 `b` 尽管在当前文档中出现次数非常多(1000次),但由于它只在少数文档中出现,其IDF值很高,因此最终的TFIDF值也较高。
    • 这说明,TFIDF值不仅取决于词在当前文档中的出现频率,还取决于这个词在整个文档集合中的稀缺性。一个词在当前文档中出现次数多并不意味着它的TFIDF值就高,还需要考虑它在整个文档集合中的分布情况。
    • 所以,训练模型时使用的语料素材非常重要。
  • MySql binlog 日志

    前言

     

    日志是mysql数据库的重要组成部分,记录着数据库运行期间各种状态信息。mysql日志主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。作为开发,我们重点需要关注的是二进制日志(binlog)和事务日志(包括redo log和undo log),本文接下来会详细介绍这三种日志。

     

    binlog

    binlog用于记录数据库执行的写入性操作(不包括查询)信息,以二进制的形式保存在磁盘中。binlog是mysql的逻辑日志,并且由Server层进行记录,使用任何存储引擎的mysql数据库都会记录binlog日志。

    • 逻辑日志:可以简单理解为记录的就是sql语句。
    • 物理日志:因为mysql数据最终是保存在数据页中的,物理日志记录的就是数据页变更。

    binlog是通过追加的方式进行写入的,可以通过max_binlog_size参数设置每个binlog文件的大小,当文件大小达到给定值之后,会生成新的文件来保存日志。

    binlog使用场景

    在实际应用中,binlog的主要使用场景有两个,分别是主从复制和数据恢复。

    • 主从复制:在Master端开启binlog,然后将binlog发送到各个Slave端,Slave端重放binlog从而达到主从数据一致。
    • 数据恢复:通过使用mysqlbinlog工具来恢复数据。

    binlog刷盘时机

    对于InnoDB存储引擎而言,只有在事务提交时才会记录biglog,此时记录还在内存中,那么biglog是什么时候刷到磁盘中的呢?mysql通过sync_binlog参数控制biglog的刷盘时机,取值范围是0-N:

    • 0:不去强制要求,由系统自行判断何时写入磁盘;
    • 1:每次commit的时候都要将binlog写入磁盘;
    • N:每N个事务,才会将binlog写入磁盘。

    从上面可以看出,sync_binlog最安全的是设置是1,这也是MySQL 5.7.7之后版本的默认值。但是设置一个大一些的值可以提升数据库性能,因此实际情况下也可以将值适当调大,牺牲一定的一致性来获取更好的性能。

    binlog日志格式

    binlog日志有三种格式,分别为 STATMENT、ROW和MIXED。

    在 MySQL 5.7.7之前,默认的格式是STATEMENT,MySQL 5.7.7之后,默认值是ROW。日志格式通过binlog-format指定。

    STATMENT

    基于SQL语句的复制(statement-based replication, SBR),每一条会修改数据的sql语句会记录到binlog中。

    • 优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO, 从而提高了性能;
    • 缺点:在某些情况下会导致主从数据不一致,比如执行sysdate()、slepp()等。

    ROW

    基于行的复制(row-based replication, RBR),不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了。

    • 优点:不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题;
    • 缺点:会产生大量的日志,尤其是alter table的时候会让日志暴涨

    MIXED

    基于STATMENT和ROW两种模式的混合复制(mixed-based replication, MBR),一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog.

    redo log

     

     

    为什么需要redo log

    我们都知道,事务的四大特性里面有一个是持久性,具体来说就是只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来的状态。那么mysql是如何保证一致性的呢?最简单的做法是在每次事务提交的时候,将该事务涉及修改的数据页全部刷新到磁盘中。但是这么做会有严重的性能问题,主要体现在两个方面:

    • 因为Innodb是以页为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,这个时候将完整的数据页刷到磁盘的话,太浪费资源了!
    • 一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机IO写入性能太差!

    因此mysql设计了redo log,具体来说就是只记录事务对数据页做了哪些修改,这样就能完美地解决性能问题了 (相对而言文件更小并且是顺序IO)。

    redo log基本概念

    redo log包括两部分:一个是内存中的日志缓冲(redo log buffer),另一个是磁盘上的日志文件(redo log file)。mysql每执行一条DML语句,先将记录写入redo log buffer,后续某个时间点再一次性将多个操作记录写到redo log file。这种先写日志,再写磁盘的技术就是MySQL里经常说到的WAL(Write-Ahead Logging) 技术。

    在计算机操作系统中,用户空间(user space)下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统内核空间(kernel space)缓冲区(OS Buffer)。因此,redo log buffer写入redo log file实际上是先写入OS Buffer,然后再通过系统调用fsync()将其刷到redo log file中,过程如下:

    mysql支持三种将redo log buffer写入redo log file的时机,可以通过innodb_flush_log_at_trx_commit参数配置,各参数值含义如下:

    redo log记录形式

    前面说过,redo log实际上记录数据页的变更,而这种变更记录是没必要全部保存,因此redo log实现上采用了大小固定,循环写入的方式,当写到结尾时,会回到开头循环写日志。如下图:

     

    同时我们很容易得知,在innodb中,既有redo log需要刷盘,还有数据页也需要刷盘,redo log存在的意义主要就是降低对数据页刷盘的要求。在上图中,write pos表示redo log当前记录的LSN(逻辑序列号)位置,check point表示数据页更改记录刷盘后对应redo log所处的LSN(逻辑序列号)位置。

    write pos到check point之间的部分是redo log空着的部分,用于记录新的记录;check point到write pos之间是redo log待落盘的数据页更改记录。当write pos追上check point时,会先推动check point向前移动,空出位置再记录新的日志。

    启动innodb的时候,不管上次是正常关闭还是异常关闭,总是会进行恢复操作。因为redo log记录的是数据页的物理变化,因此恢复的时候速度比逻辑日志(如binlog)要快很多。

    重启innodb时,首先会检查磁盘中数据页的LSN,如果数据页的LSN小于日志中的LSN,则会从checkpoint开始恢复。

    还有一种情况,在宕机前正处于checkpoint的刷盘过程,且数据页的刷盘进度超过了日志页的刷盘进度,此时会出现数据页中记录的LSN大于日志中的LSN,这时超出日志进度的部分将不会重做,因为这本身就表示已经做过的事情,无需再重做。

    redo log与binlog区别

    由binlog和redo log的区别可知:binlog日志只用于归档,只依靠binlog是没有crash-safe能力的。但只有redo log也不行,因为redo log是InnoDB特有的,且日志上的记录落盘后会被覆盖掉。因此需要binlog和redo log二者同时记录,才能保证当数据库发生宕机重启时,数据不会丢失。

    undo log

    数据库事务四大特性中有一个是原子性,具体来说就是 原子性是指对数据库的一系列操作,要么全部成功,要么全部失败,不可能出现部分成功的情况。

    实际上,原子性底层就是通过undo log实现的。undo log主要记录了数据的逻辑变化,比如一条INSERT语句,对应一条DELETE的undo log,对于每个UPDATE语句,对应一条相反的UPDATE的undo log,这样在发生错误时,就能回滚到事务之前的数据状态。

    同时,undo log也是MVCC(多版本并发控制)实现的关键.

    本文整理于网络.

  • 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)