Python 笔记

Python核心技术

Go 和 Python 的主要区别

特性 Go Python
类型系统 静态类型 动态类型
语法 简洁、强制使用花括号 灵活、使用缩进
性能 高效、编译型语言 较慢、解释型语言
并发模型 原生支持 goroutine 和 channel 多线程(受 GIL 限制)和多进程
生态系统 标准库强大,第三方库较少 标准库丰富,第三方库庞大
应用场景 微服务、云计算、系统编程 数据分析、机器学习、Web 开发
开发工具 内置工具链,IDE 支持较少 工具丰富,IDE 支持广泛

进程和线程

简述线程,进程,协程的区别?

线程:

操作系统能够进行运算调度的最小单位。 它包含在进程之中,是进程的实际运作单位。 一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程,每一条线程并行执行不同的任务。

进程:

对一堆资源的整合。 比如说QQ就是一个进程。
目的:最大限度的利用CPU,节省时间。

从操作系统角度来讲,进程是资源分配单元,线程是执行单元,多个线程可以共享所在进程的资源。

协程:

从程序运行角度出发,是由用户(程序)控制和调度的一个过程。

多线程并不会充分调用两个CPU,而是会在一个CPU上充分运转;
而多进程则是会完全调用两个CPU,同时执行;


并行:多个处理器同时处理多个任务

并发:一个处理器同时交替处理多个任务


简述线程同步和异步的区别?

同步:指一个线程需要主动等待上一个线程执行完之后才开始执行。

异步:指一个线程不需要主动等待上一个线程执行完之后就开始执行。

区别关键是 需不需要主动等待


进程的理论

进程是指一个程序在一个数据集上的动态执行过程(程序执行过程的抽象)

进程包含

  • 程序:我们通过程序来描述一个进程所要执行的内容以及如何执行

  • 数据集:数据集代表程序在执行过程中所需要的资源

  • 进程控制块:用于描述进程的外部特征,记录进程的执行过程,系统可以用来控制/管理进程,也是操作系统感知进程存在的唯一标识

进程运行的的三种状态:

在这里插入图片描述

  1. 进程执行过程中出现 IO,进入阻塞状态(操作系统强行剥夺CPU)
  2. 进程占用 CPU 时间过长/出现一个优先级更高的进程,进入就绪状态(CPU被剥夺)
  3. 就绪态的进程被分配到 CPU,进入运行状态
  4. 阻塞态的进程出现有效输入,进入就绪态,等待操作系统分配CPU

此外,程序员能直接控制的只有阻塞状态,减少阻塞,使进程尽可能的保持在就绪状态,提高效率


python 开启进程

开启进程:开启进程就是将父进程里面串行执行的程序放到子进程里,实现并发执行,这个过程中,会将父进程数据拷贝一份到子进程。

运行角度:是 2 个进程

注意:子进程内的初始数据与父进程的一样,如果子进程被创建或者被运行了,那么子进程里面数据更改对父进程无影响,2个进程是存在运行的

方式一:通过调用multiprocessing模块下面的Process类方法

from multiprocessing import Process
 
def func(name):
    print('%s is running...' % name)
    print('%s is ending...' % name)
 
 
if __name__ == '__main__':
    p = Process(target=func, args=('子进程',))  
	# 如果只有一个参数,args括号内一定要加逗号,确保以元组的形式传入
	# 这一步:只是在向操作系统发我要开启一个子进程的信号(具体开子进程的操作是由操作系统来完成的)
    p.start() 
	# 只是主进程给操作系统发送建立子进程的请求,并非立刻建立子进程
    print('主进程')

运行结果:主进程
		子进程 is running...
		子进程 is ending...

方式二:借助process类,自定义一个类(继承Process),从而创造一个对象

定义process类的子类,并重写该类的run()方法,run()方法的中写进程需要完成的任务。

from multiprocessing import Process

class MyProcess(Process):     # 继承Process类
    def run(self):  # run名字是固定的,不能更改
        print("%s is running" % self.name)  # 默认函数对象有name方法 ,结果为:Myprocess-1
        print('%s is done' % self.name)

if __name__ == '__main__':
    obj = MyProcess()
    obj.start()  # 本质上是在调用父类的start方法,而start方法下会触发run方法
    print('主进程')
运行结果:主进程
		MyProcess-1 is running
		MyProcess-1 is done

为什么开启进程要在main内执行?

由于在windows系统下,子进程是通过导入模块的方式拿到父进程的代码,如果没有main会一直开启子进程,

而子进程的申请是需要开辟内存以及申请pid等的。


python 开启线程

方式一:通过thread类直接创建

import threading

def foo(n):
    print('%s'%n)

def bar(n):
    print('%s'%n)

print('33')   # 主线程

t1 = threading.Thread(target=foo, args=(1,))    # 线程一
t2 = threading.Thread(target=bar, args=(2,))    # 线程二
t1.start()
t2.start()
# 创建线程,第一个参数是函数名字(不加括号,加括号就执行函数了)。
# 第二个参数是要传给函数的参数,以元组的形式。
# 创建线程之后, obj.start()启动线程。
执行结果:33
		1
		2		  

方式二:继承 Thread 类:

定义Thread类的子类,并重写该类的run()方法,run()方法的中写线程需要完成的任务。

import threading

# 定义MyThread类,其继承自threading.Thread这个父类
class MyThread(threading.Thread):
   def __init__(self):
     threading.Thread.__init__(self)
   def run(self):
     print("start t1")
     print("end t1")

t1=MyThread()  # 对类进行实例化
t1.start()   # 启动线程
print("主线程")
运行结果:start t1
		end t1
		主线程

补充:Thread 类的一些常用方法

  • join(): 在子线程完成之前,主线程将一直被阻塞
  • setDaemon(True) 方法: 将进程声明为守护线程,必须在start()方法调用之前

线程之间如何通信?

方式一:threading.Event 事件

Python提供了非常简单的通信机制 Threading.Event,通用的条件变量(event.isSet==False/True)。多个线程可以等待某个事件的发生,在事件发生后,所有的线程都会被激活

关于Event的使用,就四个函数:

event = threading.Event()

  • event.isSet() 返回 event 的状态值。
  • event.wait() 等待接收 event 的状态值,决定是否阻塞程序执行。如果 event.isSet==False,将阻塞线程。
  • event.set() 设置 event 的状态值为 True,使所有设置该 event 事件的线程执行。
  • event.clear() 重置 event 的状态值为 False,使得所有该 event 事件都处于待命状态。

举个例子:

import time
import threading

class MyThread(threading.Thread):
    def __init__(self, name, event):
        super().__init__()
        self.name = name
        self.event = event

    def run(self):
        print('Thread: {} start at {}'.format(self.name, time.ctime(time.time())))
        # 等待event.set()后,才能往下执行
        self.event.wait()
        print('Thread: {} finish at {}'.format(self.name, time.ctime(time.time())))

threads = []
event = threading.Event()

# 定义三个线程,使用event事件
[threads.append(MyThread(str(i), event)) for i in range(1, 4)]

# 重置event,使得event.wait()起到阻塞作用
event.clear()

# 启动所有线程
[t.start() for t in threads]

print('等待5s...')
time.sleep(5)

print('唤醒所有线程...')
event.set()     
执行结果:Thread: 1 start at Fri Jul 12 15:05:50 2019
		Thread: 2 start at Fri Jul 12 15:05:50 2019
		Thread: 3 start at Fri Jul 12 15:05:50 2019
		等待5s...
		唤醒所有线程...
		Thread: 3 finish at Fri Jul 12 15:05:55 2019
		Thread: 2 finish at Fri Jul 12 15:05:55 2019
		Thread: 1 finish at Fri Jul 12 15:05:55 2019

可见在所有线程都启动 start() 后,并不会执行完,而是都在self.event.wait()阻塞了,需要通过event.set()来给所有线程发送执行指令才能往下执行。

方式二:threading.Condition条件

Condition 和 Event 是类似的,并没有多大区别。

Condition 需要掌握几个函数:

cond = threading.Condition() 创建一个 cond 条件 , 默认锁为 Rlock.

  • cond.acquire() 类似lock.acquire()

  • cond.release() 类似lock.release()

  • cond.wait() 等待指定触发,同时会释放锁,直到被 notify 才重新占有琐。

  • cond.notify() 发送指定,触发执行一个线程

  • cond.notifyAll() 发送指定,触发执行所有线程

举个生产消费的例子:

import threading
import time
from random import randint

class Producer(threading.Thread):
    def run(self):
        global L
        while True:
            val=randint(0,100)
            print('生产者',self.name,":Append"+str(val), L)
            if lock_con.acquire():
                L.append(val)
                lock_con.notify()
                lock_con.release()
            time.sleep(3)
            
class Consumer(threading.Thread):
    def run(self):
        global L
        while True:
                lock_con.acquire()
                if len(L)==0:
                    lock_con.wait()
                print('消费者',self.name,":Delete"+str(L[0]),L)
                del L[0]
                lock_con.release()
                time.sleep(0.25)

if __name__=="__main__":

    L=[]
    lock_con=threading.Condition()
    threads=[]
    for i in range(5):
        threads.append(Producer())
    threads.append(Consumer())
    for t in threads:
        t.start()
    for t in threads:
        t.join()
执行结果:生产者 Thread-1 :Append48 []
		生产者 Thread-2 :Append9 [48]
		生产者 Thread-3 :Append73 [48, 9]
		生产者 Thread-4 :Append11 [48, 9, 73]
		生产者 Thread-5 :Append94 [48, 9, 73, 11]
		消费者 Thread-6 :Delete48 [48, 9, 73, 11, 94]
		消费者 Thread-6 :Delete9 [9, 73, 11, 94]
		消费者 Thread-6 :Delete73 [73, 11, 94]
		消费者 Thread-6 :Delete11 [11, 94]
		消费者 Thread-6 :Delete94 [94]

可见通过cond来通信,阻塞自己,并使对方执行。

方式三:queue.Queue队列

从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。

创建一个被多个线程共享的 Queue 对象,这些线程通过使用put()get() 操作来向队列中添加或者删除元素。

队列需要掌握的函数:

q = Queue(maxsize=0) maxsize默认为0,不受限。

  • q.get() 阻塞程序,等待队列消息。

  • q.get(timeout=5.0) 获取消息,设置超时时间。

  • q.put() 发送消息。

  • q.join() 等待所有的消息都被消费完。

举一个老师点名的例子:

from threading import Thread
from queue import Queue
import time

class Student(Thread):
    def __init__(self, name, queue):
        super().__init__()
        self.name = name
        self.queue = queue

    def run(self):
        while True:
            # 阻塞程序,时刻监听老师,接收消息
            msg = self.queue.get()
            # 一旦发现点到自己名字,就赶紧答到
            if msg == self.name:
                print("{}:到!".format(self.name))

class Teacher:
    def __init__(self, queue):
        self.queue = queue

    def call(self, student_name):
        print("老师:{}来了没?".format(student_name))
        # 发送消息,要点谁的名
        self.queue.put(student_name)

queue = Queue()
teacher = Teacher(queue=queue)
s1 = Student(name="小明", queue=queue)
s2 = Student(name="小亮", queue=queue)
s1.start()
s2.start()

print('开始点名~')
teacher.call('小明')
time.sleep(1)
teacher.call('小亮')

总结:

学习了以上三种通信方法,我们很容易就能发现EventCondition 是 threading 模块原生提供的模块,原理简单,功能单一,它能发送 TrueFalse 的指令,所以只能适用于某些简单的场景中。

Queue则是比较高级的模块,它可能发送任何类型的消息,包括字符串、字典等。其内部实现其实也引用了Condition模块(譬如putget函数的阻塞),正是其对Condition进行了功能扩展,所以功能更加丰富,更能满足实际应用。


进程之间如何通信?

方式一:Queue队列,用于多个进程间实现通信

一个进程向 Queue 中放入数据,另一个进程从 Queue 中读取数据。

multiprocessing 模块下的 Queue 和 queue 模块下的 Queue 基本类似,它们都提供了 qsize()empty()full()put()put_nowait()get()get_nowait() 等方法。

区别只是 multiprocessing 模块下的 Queue 为进程提供服务,而 queue 模块下的 Queue 为线程提供服务。

from queue import Queue    #  为线程提供服务
from multiprocessing import Queue   # 为进程提供服务

举个例子:

import multiprocessing

def fun(que):
    print('(%s) 进程开始放入数据...' % multiprocessing.current_process().pid)
    que.put('Python')    # 向 Queue 中放入数据

if __name__ == '__main__':

    que = multiprocessing.Queue()    # 创建进程通信的Queue
    p = multiprocessing.Process(target=fun, args=(que,))  # 创建子进程
    p.start()  # 启动子进程
    print('(%s) 进程开始取出数据...' % multiprocessing.current_process().pid)   # 先走主进程
    print(que.get())  # 从 Queue 中读取数据
执行结果:(6364) 进程开始取出数据...
		(12716) 进程开始放入数据...
		Python

方式二:Pipe管道,用于两个进程的通信

使用 Pipe 实现进程通信,程序会调用 multiprocessing.Pipe() 函数来创建一个管道,该函数会返回两个 PipeConnection 对象,代表管道的两个连接端,用于连接通信的两个进程。

PipeConnection 对象包含如下常用方法:

  • send(obj):发送一个 obj 给管道的另一端,另一端使用 recv() 方法接收。
  • recv():接收另一端通过 send() 方法发送过来的数据。
  • fileno():关于连接所使用的文件描述器。
  • close():关闭连接。
  • poll([timeout]):返回连接中是否还有数据可以读取。
  • send_bytes(buffer[, offset[, size]]):发送字节数据。
  • recv_bytes([maxlength]):接收通过 send_bytes() 方法发迭的数据,maxlength 指定最多接收的字节数。
  • recv_bytes_into(buffer[, offset]):功能与 recv_bytes() 方法类似,只是该方法将接收到的数据放在 buffer 中。

举一个例子:

import multiprocessing

def f(conn):
    print('(%s) 进程开始发送数据...' % multiprocessing.current_process().pid)
    # 使用conn发送数据
    conn.send('Python')

if __name__ == '__main__':

    parent_conn, child_conn = multiprocessing.Pipe()    # 创建Pipe,该函数返回两个PipeConnection对象
    p = multiprocessing.Process(target=f, args=(child_conn, ))    # 创建子进程
    p.start()
    print('(%s) 进程开始接收数据...' % multiprocessing.current_process().pid)
    # 通过conn读取数据
    print(parent_conn.recv())  # Python
    p.join()
执行结果:(13796) 进程开始接收数据...
		(14792) 进程开始发送数据...
		Python

什么时候用多线程?什么时候用多进程?

多线程使用场景:IO 密集型, 比如 Web服务器请求处理、数据库连接操作等。
多进程使用场景:CPU 密集型, 如科学计算/机器学习训练、视频转码/图像处理等。


python 的 GIL

同一个时刻只允许一个线程可以使用 cpu

全局解释器锁,Guido van Rossum(吉多·范罗苏姆)创建 python 时就只考虑到单核 cpu,解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁, 于是有了 GIL 这把超级大锁。因为cpython解析只允许拥有 GIL 全局解析器锁才能运行程序,这样就保证了保证同一个时刻只允许一个线程可以使用 cpu。也就是说多线程并不是真正意义上的同时执行。

如何解决 GIL 锁的问题呢?

最流行的方法是使用多进程方法,其中使用多个进程而不是线程。每个 Python 进程都有自己的 Python 解释器内存空间,因此 GIL 不会成为问题。


Socked 通信

socket 是在应用层和传输层之间的一个抽象层,socket 本质是编程接口(API),它把 TCP/IP 层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。TCP/IP 只是一个协议栈,必须要具体实现,同时还要提供对外的操作接口(API),这就是 Socket 接口。通过Socket,我们才能使用 TCP/IP 协议。

socket 通信流程如图:
在这里插入图片描述

根据上图顺序写出代码:

说明:建立2个py文件,原则上应该是在2台电脑上各自建立1个py文件,分别是 server.py 和 client.py 。由于现在只有一台电脑,所以建立2个py文件。

server.py:

import socket

sk = socket.socket()
sk.bind(('127.0.0.1',8000))   # 通过sk中的bind()方法,绑定地址和端口,参数必须是元组
sk.listen(3)   # 设置访问程序的最大排队人数
conn, address = sk.accept()
inp = input('请输入需要传输的内容:')
conn.send(bytes(inp,'utf8'))   # 传输的内容一定是byte类型

client.py

import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8000))
data = sk.recv(1024)
print(str(data,'utf8'))  # str()转换

普通方法,类方法,静态方法区别

  • 普通方法,保存在类中,执行时通过对象调用
  • 静态方法,保存在类中,需要加装饰器(@staticmethod),执行通过类直接调用。不需要创建对象,可直接通过类调用,相当于函数
  • 类方法,保存在类中,需要加装饰器(@classmethod),执行通过类直接调用。不需要创建对象,可直接通过类调用,相当于函数
class Dog:
    
    dogbook = {
   
   '黄色': 30, '黑色': 20, '白色': 0}   # 类变量

    def __init__(self, name, color, weight):
        self.name = name       # 实例变量
        self.color = color     # 实例变量
        self.weight = weight   # 实例变量

    '''
        实例方法
            必须将self作为第一个参数
            可以访问实例变量,不能访问类变量
            只能通过实例名访问
    '''
    def bark(self):
        print('{}叫了一声'.format(self.name))   # 大黄叫了一声
        # print(dogbook)   # name 'dogbook' is not defined

    '''
        类方法
            必须将cls作为第一个参数
            可以访问类变量,不能访问实例变量
            可以通过实例名或者类名访问
    '''
    @classmethod
    def dog_num(cls):
        num = 0
        # print(self.name)  # name 'self' is not defined
        for v in cls.dogbook.values():
            num += v
        print(num)   # 50

    '''
        静态方法:
            不强制传入self,cls
            不能访问类变量,也不能访问实例变量
            可以通过实例名或类名访问
        静态方法和普通函数没有区别,可以放在类外面。
        之所以放在这里,是因为此方法和这个类有关联
    '''
    @staticmethod
    def total_weights(dogs_weight_list):
        total = 0
        for i in dogs_weight_list:
            total += i
        print(total)   # 30


Dog('大黄', '黄色', 10).bark()
Dog('大黄', '黄色', 10).dog_num()
Dog.total_weights([10,20])

静态方法是类中的函数,不需要实例。当某个方法不需要用到对象中的任何资源,将这个方法改为一个静态方法, 加一个@staticmethod。加上之后, 这个方法就和普通的函数没有什么区别了, 只不过写在了一个类中, 可以使用这个类的对象调用,也可以使用类直接调用。

类方法是将类本身作为对象进行操作的方法。它和静态方法的区别在于:不管这个方式是从实例调用还是从类调用,它都用第一个参数把类传递过来。


@property,@setter 实现成员变量的 get, set 属性?

@property装饰器: 将一个方法的调用方式变成“属性调用”。

通常用在属性的 get 方法,set 方法,通过设置@property可以实现实例成员变量的直接访问,又保留了参数的检查。

class Student(object):
 
    @property
    def score(self):
        return self.__score   
    
    @score.setter
    def score(self,value):
        if value>=0 and value <= 100:
            self.__score = value #还记得__score吗?前面加一个双下划线,表示private私有属性
        else:
            raise ValueError('score must between 0 ~ 100!')
            
    
s = Student()
s.score = 90   # 设置分数
print(s.score)

@property 修饰函数score(getter),将score函数变成score属性输出,此外,@property 本身又创建了另一个装饰器@score.setter,负责把一个 setter 函数变成属性赋值,于是,我们虽然看到了类Student内部定义了两个函数score,对,没错,都是score,但是,他们却被不同的装饰器装饰,getter函数被@property修饰,setter函数被@property创建的函数的setter装饰器@score.setter修饰,因此,我们可以直接用s.score=90来代替s.set_socre(90),达到给score属性赋值的效果,简单粗暴,精益求精,

我们上面创建了@property另一个装饰器函数@xxx.setter,对私有属性__score进行输入值的判断,如果,我们不创建装饰器@xxx.setter可以吗?如果不创建的话,属性xxx就是一个只读属性,意味着不能被修改,


python 是如何实现多态的?

多态:一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)

class Animal():
    def __init__(self, name):
        self.name = name

    def talk(self):  # 抽象方法,仅由约定定义
        print(self.name, '叫')  # 当子类没有重写talk方法的时候调用

    def animal_talk(obj):  # 多态
        obj.talk()

class Cat(Animal):
    def talk(self):
        print('%s: 喵喵喵!' % self.name)  # 重写talk方法

class Dog(Animal):
    def talk(self):
        print('%s: 汪汪汪!' % self.name)


a = Dog('小狗')
Animal.animal_talk(a)  # 多态调用
b = Cat('小猫')
Animal.animal_talk(b)
c = Animal('111')
Animal.animal_talk(c)
执行结果:小狗: 汪汪汪!
		小猫: 喵喵喵!
		111 叫

单元测试,单例模式

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。在python中指一个类。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个对象实例。(详见面试总结一)


python 如何实现单例模式?

什么是单例模式(Singleton)?

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。

单例模式优点:

  • 由于单例模式要求全局内只有一个实例,所以可以节省很多内存空间
  • 全局只有一个接入点,可以更好的进行数据同步,避免多重占用
  • 单例可以常驻内存,减少系统开销

单例模式应用:

  • 生成唯一序列号
  • 访问全局复用的唯一资源,如磁盘,总线等
  • 数据库连接池
  • 网站计数器

实现单例的方式?

  • 全局变量:不直接调用 Config() ,而使用同一个全局变量
  • 重写 __new__ 方法:重写 __new__来保证每次调用 Config() 都会返回同一个对象, __new__ 为对象分配空间,返回对象的引用
  • 使用 metaclass:metaclass 重写__call__ 方法来保证每次调用 Config() 都会返回同一个对象
  • 使用装饰器:使用装饰器来保证每次调用 Config() 都会返回同一个对象

原理:在真正调用 Config() 之前进行一些拦截操作,来保证返回的对象都是同一个:

具体实现:

  1. 全局变量

    # config.py
    from dataclasses import dataclass
    
    @dataclass
    class Config:
         SQLALCHEMY_DB_URI  = SQLALCHEMY_DB_URI
    
    config  = Config(SQLALCHEMY_DB_URI = "mysql://")
    

    通过使用全局变量,我们在所有需要引用配置的地方,都使用 from config import config 来导入,这样就达到了全局唯一的目的。

  2. 重写__new__方法

    class Singleton(object):
        _instance = None   # _instance 作为类属性,保证了所有对象的实例都是同一个
    
        def __new__(cls, *args, **kwargs):  
            if cls._instance is None:
                cls._instance = super().__new__(cls)  # 为对象分配空间
            return cls._instance   # 返回对象的引用
    
    a = Singleton()    # 创建对象时,new方法会被自动调用
    b = Singleton()
    print(a)
    print(b)
    
    执行结果:<__main__.Singleton object at 0x0000023A88E0F2B0>
    		<__main__.Singleton object at 0x0000023A88E0F2B0>
    

    每次实例化一个对象时,都会先调用 __new__() 创建一个对象,再调用 __init__() 函数初始化数据。因而,在 new 函数中判断 Singleton类 是否已经实例化过,如果不是,调用父类的 new 函数创建实例;否则返回之前创建的实例。

  3. 使用metaclass元类

    class SigletonMetaClass(type):
        _instance = None
    
        def __new__(cls, *args, **kwargs):
            return super().__new__(cls, *args, **kwargs)
    
        def __call__(self, *args, **kwargs):
            if self._instance is None:
                self._instance = super().__call__(*args, **kwargs)
            return self._instance
    
    class Singleton(metaclass=SigletonMetaClass):
        def __new__(cls, *args, **kwargs):
            return super().__new__(cls)
        
    a = Singleton()
    b = Singleton()
    print(a)
    print(b)
    
    执行结果:<__main__.Singleton object at 0x000001A11E4AC908>
    		<__main__.Singleton object at 0x000001A11E4AC908>
    

    因此,用元类实现单例时仍需按照三步骤:1. 拦截 2. 判断是否已经创建过对象 3. 返回对象。与上个方法相比,区别在于拦截的地点不同。


装饰器与闭包

  • 装饰器:用于修改或增强函数行为,本质上是闭包的应用。
  • 闭包:捕获并保存外部作用域的变量,用于保留状态或封装数据。

什么是装饰器?如何实现一个装饰器?

装饰器是一种用于修改或增强函数行为的函数。它接受一个函数作为参数,并返回一个新的函数

实现装饰器

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Before function execution")
        result = func(*args, **kwargs)
        print("After function execution")
        return result
    return wrapper

@my_decorator
def greet(name):
    print(f"Hello, {
     
     name}!")

greet("Alice")
# 输出:
# Before function execution
# Hello, Alice!
# After function execution

解释
my_decorator 是一个装饰器函数,接受 func 作为参数。
wrapper 是新的函数,用于包装 func
@my_decorator 是装饰器的语法糖,等价于 greet = my_decorator(greet)


什么是闭包?闭包的作用是什么?

闭包是一个函数,它捕获并保存了其外部作用域的变量,即使外部作用域已经结束。

闭包的作用

  • 保留函数的状态。
  • 实现数据隐藏和封装。

实现闭包

def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

closure = outer_function(
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值