Python 笔记

Python基础---面试题汇总 前言 本文只涉及Python相关的面试题,面向中高级Python开发,太基本的题目不收录。 更希望通过代码演示,原理探究等来深入讲解某一知识点,做到融会贯通。 另外部分演示代码有兴趣的可以找我拿。 语言基础篇 Python的基本数据类型 Python3 中有六个标准的数据类型: Number(数字)(包括整型、浮点型、复数、布尔型等) String(字符串) List(列表) Tuple(元组) Set(集合) Dictionary(字典) Python3 的六个标准数据类型... 阅读详情

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('小亮')

总结:

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

而Queue则是比较高级的模块,它可能发送任何类型的消息,包括字符串、字典等。其内部实现其实也引用了Condition模块(譬如put和get函数的阻塞),正是其对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(
Pytorch面试题面经 pytorch面经 阅读详情

相关推荐

如何使用python pyautogui完成从印象笔记到Obsidian的笔记迁移

这篇文章通过使用python的pyautogui 命令,完成了从印象笔记到Obsidian的文本内容批量迁移。最近因为工作需要,要把印象笔记里的笔记迁移到Obsidian软件里。印象笔记本身是有python的API的。

qq_33328585的博客 1082

面试 | 30个热门PyTorch面试题助你轻松通过机器学习/深度学习面试

本文涵盖 PyTorch 的多个方面的热门面试题,包括初始化权重和偏差、定义神经网络模型、设实现自定义损失函数、模型压缩加速技巧、求函数导数、torch.no_grad的作用以及张量钩子的使用和在张量上实现钩子等内容。

Vampire_2017的博客 1万+

个人笔记工具软件的Python实现

Python是1989年由Guido van Rossum发明的一种高级编程语言,以其简洁明了的语法而闻名于世。其主要特点是易于学习和阅读,具有丰富的标准库和第三方库,支持多范式编程,特别是在数据科学、人工智能等领域得到广泛应用。另外,Python是开源的,并且拥有强大的社区支持。PyQt5 是一个跨平台的 C++ 应用程序框架,使用 Python 进行绑定。它是著名的 Qt 应用程序框架的一个端口,由 Riverbank Computing 在 1998 年首先开发。

weixin_42560991的博客 1189

给麒麟KylinOS服务器加把锁:手把手配置符合等保要求的密码强度策略

本文详细介绍了如何在麒麟KylinOS服务器上配置符合等保要求的密码强度策略,包括pwquality.conf和login.defs的核心参数设置、等保合规的进阶配置技巧以及企业环境中的平衡艺术。通过实战案例和测试方法,帮助管理员构建坚固的密码防线,确保系统安全。

weixin_33749242的博客 419

从FinFET到NanoSheet:一文看懂TSMC制程技术的架构革命与性能飞跃

本文深入解析了TSMC从FinFET到NanoSheet的制程技术架构革命。FinFET通过三维立体结构解决了短沟道效应,而NanoSheet采用环绕栅极设计,实现了更强的静电控制与设计灵活性,结合EUV光刻技术,共同推动了芯片性能与能效的飞跃,为下一代AI与高性能计算奠定基石。

i3j4k5的博客 288

Qt QChart实战:从零打造一个实时温度监控仪表盘(附完整源码)

本文详细介绍了如何使用Qt QChart模块从零构建一个工业级实时温度监控仪表盘。通过实战案例,展示了波形图的绘制、坐标轴配置、数据流模拟等关键技术,并提供了完整的源码实现,帮助开发者快速掌握Qt在数据可视化领域的应用。

weixin_42537919的博客 243

机器人系统能用MQTT5.0代替ROS2吗?

更为灵活和友好的方案是通过把ROS(Robot Operating System)与MQTT(Message Queuing Telemetry Transport)协议相结合的解决方案,利用MQTT的轻量级、跨平台和广泛适用性特点,扩展ROS系统的通信能力,或将非ROS系统接入ROS网络。

QzmAIoT 2162

Doorbell机制在PCI与MSI中断中的高效应用

本文深入解析了Doorbell机制在PCIe设备与CPU间通信中的核心作用。作为一种高效的硬件通知机制,它通过内存映射I/O(MMIO)实现软件对硬件的轻量级触发,常与MSI中断协同工作,构建高效的生产者-消费者模型。文章以AMD GPU的Ring Buffer命令提交为例,阐述了该机制如何显著降低CPU开销、提升系统吞吐量,并对比了其与传统中断方式的性能优势。

ee345的博客 608

自动驾驶仿真基础:使用Veristand自定义CAN报文的Checksum和Counter

文章目录前言一、关于Checksum的组成形式二、自定义的Checksum方法一1、实现的主要流程图如下:2、方法一的主要算法参考三、自定义的Checksum方法二1、软件及插件下载;2、Custom device的使用;4、Veristand调用Custom Device;5、Veristand发送信号值;6、部署到实时机; 前言 1、现在很多的车厂对于车辆的安全机制非常重视,车厂通过对发送的CAN数据进行加密而形成一种保护机制,而Checksum和Counter是其中的一种保护介质,当CAN报文数据的

zataji的博客 1万+

AMD SDP 优快云 系列文章设计

摘要 本文档规划了7篇AMD SDP协议解析系列文章的技术博客设计方案。基于《Scalable Data Port (SDP) Specification》1.5.0版本(122页),面向SoC/NoC领域的RTL设计与验证工程师,构建从基础概念到验证实现的知识体系。 系列文章严格遵循协议规范边界,采用五类证据标签区分内容来源(规范原文、勘误、验证推导等),禁止补充未定义的实现细节。包含端口控制、事务传输、一致性协议等核心内容,最终落实到验证平台设计与覆盖率模型。 每篇文章采用统一模板:明确规范依据、问题导

元直的博客 160

【华为OD机试真题E卷】474、推荐多样性 | 机试真题+思路参考+代码解析(E卷复用)(C++、Java、Py)

题目描述 >推荐多样性需要从多个列表中选择元素,一次性要返回 N 屏数据(窗口数量),每屏展示 K 个元素(窗口大小),选择策略:   各个列表元素需要做穿插处理,即先从第一个列表中为每屏选择一个元素,再从第二个列表中为每屏选择一个元素,依次类推   每个列表的元素尽量均分为 N 份,如果不够 N 个,也要全部分配完,参考样例图:

KJ.JK 4434

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

上一篇: MySQL 笔记
下一篇: 操作系统 笔记
sumatch
博客等级 码龄8年 251粉丝 59原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值