分类: Python 基础

  • 部署本地DeepSeek大模型时,怎么选择合适的模型文件?

    问题是什么?

    大家都知道deepseek开源了大模型,可能想本地部署试试效果如何,去各个大模型平台看了一遍,这时候部分人可能会很奇怪,为什么这些平台官方推荐的deepseek大模型都是“DeepSeek-R1-Distill-Qwen” 这种?deepseek和Qwen有什么关系?什么是Distill?

    什么是DeepSeek,Distill,Qwen?

    • DeepSeek-R1,是幻方量化旗下AI公司深度求索(DeepSeek)研发的推理模型 。DeepSeek-R1的强项之一是具有很强的推理能力。
      • 数据搜索能力
    • Qwen,阿里巴巴的大模型,强项是具有较为丰富的参数。
      • 丰富的数据
    • Distill,蒸馏这个过程,就是结合两者的优势,组合成一个新的大模型。
      • 强大的数据集 + 强大的推理计算能力

     

    综上,DeepSeek-R1-Distill-Qwen 类似一种果树的嫁接技术,将deepseek的推理能力嫁接到Qwen的数据里。

    怎么选择适合自己硬件大模型?

    • DeepSeek-R1-Distill-Qwen-32B-IQ3_M.gguf, 14.81GB
    • DeepSeek-R1-Distill-Qwen-7B-f16.gguf, 15.24GB

    以这两个模型为例,它们大小相近,但大多数情况下,2优于1。

    • 1具有320亿参数,IQ3_XS代表了一种平衡性能与效率的量化策略,量化过程可能会导致一定的精度损失,但是它仍然保留了大量的原始信息。
    • 2只有基于70亿参数,没有经过裁剪或量化处理。理论上可以提供最接近于原始训练模型的性能,特别是在准确性和细节处理方面。但是由于参数量较少,它可能在理解和生成复杂文本时不如32B版本。

    这其实是个复杂、需要综合考虑的问题。一般而言,越接近原始大模型的版本最好,但是普通用户受限于显卡算力,只能选择修剪后的版本。

    16GB显存的显卡,一般选择7b,14b参数,跑32b会有压力。因此如果你是最新的5090显卡,就可以更大参数的。

     

     

     

  • 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. 分公司可以跟着总公司一起下班,也可以把当天的工作全部做完之后再下班—这叫守护进程设置。

     

  • 什么是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

     

     

    文件操作

    文件创建

    文件写入

    目录读取