一些python的高级方法(闭包、装饰器、多线程详解)

news2024/11/29 19:31:18

目录

闭包

装饰器

普通用法

多层装饰器

设计模式

单例模式

工厂模式

多线程

基础使用

得到当前的线程

守护线程

线程阻塞join方法

线程锁 Lock

递归锁对象RLock


闭包

例如要实现一个存钱的功能,可以这么做

account_amount = 0

def atm(num,deposit = True):
    global account_amount
    if deposit:
        account_amount  += num
    else:
        account_amount -= num
    print(account_amount)

这样是运用了外部变量构建函数实现,但是这样的缺点是,外部变量容易被篡改

那么我们可以运用闭包

def card(account_num=0):
    def ATM(num,deposit=True):
        nonlocal account_num
        if deposit:
            account_num += num
        else:
            account_num -= num
        print(account_num)
    return ATM

1.什么是闭包
        定义双层嵌套函数,内层函数可以访问外层函数的变量
        将内存函数作为外层函数的返回,此内层函数就是闭包函数
2.闭包的好处和缺点

  • 优点:不定义全局变量,也可以让函数持续访问和修改一个外部变量
  • 优点:闭包函数引用的外部变量,是外层函数的内部变量。作用域封闭难以被误操作修改
  • 缺点:额外的内存占用

3.nonlocal关键字的作用
在闭包函数(内部函数中)想要修改外部函数的变量值,需要用nonlocal声明这个外部变量

装饰器

普通用法

可以参考这篇博客

python装饰器详解_谦虚且进步的博客-CSDN博客

 装饰器本质上是一个Python函数(其实就是闭包),它可以让其他函数在不需要做任何代码变动的前提下增加额外功能

一般写法:

def func1():
    print('我是func1')

def func2(func):
    def inner():
        print('我是func2开始')
        func()
        print('我是func2结束')
    return inner

a = func2(func1)
a()

语法糖写法

def func2(func):
    def inner():
        print('我是func2开始')
        func()
        print('我是func2结束')
    return inner

@func2
def func1():
    print('我是func1')

func1()

多层装饰器

def func2(func):
    def inner():
        print('我是func2开始')
        func()
        print('我是func2结束')
    return inner

def func3(func):
    def inner():
        print('func3开始')
        func()
        print('func3结束')
    return inner

@func3
@func2
def func1():
    print('我是func1')
func1()
# func3开始
# 我是func2开始
# 我是func1
# 我是func2结束
# func3结

相当于

func3(func2(func1))

设计模式

单例模式

对于一个这样的类

class tools:
    pass

如果我在很多地方需要使用它,一般会这么做

a = tools()
b = tools()
print(a)
print(b)
# <__main__.tools object at 0x00000210AA9D7710>
# <__main__.tools object at 0x00000210AA9DD208>

可以看到不同的实例,内存是不一样的,也就是说这两个实例是独立的。

但是如果这是一个工具类,我们在不同的地方使用同一个实例就可以了

tool = tools()
a=tool
b=tool

print(a)
print(b)
# <__main__.tools object at 0x00000210AA9DD278>
# <__main__.tools object at 0x00000210AA9DD278>

一般开发中,会把下面的代码放在一个单独的文件,然后别的地方要用的时候直接import调用tool就可以了

class tools:
    pass

tool = tools()

工厂模式

多线程

基础使用

python多线程的基础使用: 

import threading
thread_obj = threading.Thread([group [, target [, name [, args [,kwarngs[,daemon=None]]]]])
# 
# group:暂时无用,未来功能的预留参数
# target:执行的目标任务名
# args:以元组或者列表的方式给执行任务传参
# kwargs:以字典方式给执行任务传参
# name:线程名,一般不用设置
# daemon 参数将显式地设置该线程是否为守护模式

#启动线程,让线程开始工作
thread_obj.start()

通过小案例来体会多线程 

import threading
import time
def func1():
    while 1:
        print(111)
        time.sleep(1)

def func2():
    while 1:
        print(222)
        time.sleep(1.5)

thread_1 = threading.Thread(target=func1)
thread_2 = threading.Thread(target=func2)

thread_1.start()
thread_2.start()

当函数需要传递参数时,就可以用到args,kwargs了,需要注意的是args必须是元组或者列表(默认元组),kwargs必须是字典 

import threading
import time
def func1(msg):
    while 1:
        print(msg)
        time.sleep(1)

def func2(msg1,msg2):
    while 1:
        print(msg1)
        print(msg2)
        time.sleep(1.5)

thread_1 = threading.Thread(target=func1,args=(111,))
thread_2 = threading.Thread(target=func2,kwargs={'msg1':222,'msg2':333})

thread_1.start()
thread_2.start()
#

得到当前的线程

import threading
import threading
import time
def func1(msg):
    while 1:
        time.sleep(1)
        print(threading.currentThread())

thread_1 = threading.Thread(target=func1,args=[111],name='lalala')
thread_1.start()

可以使用threading.currentThread()得到当前的进程,如果指定了线程名字,会显示进程名字

守护线程

启动python时,会生成一个主线程,我们可以通过之前的方法生成子线程,当所有子线程都结束了,主线程才会结束

threading提供了一个daemon参数,默认是False,如果是True,则表示主线程不会等待这个线程,等别的子线程结束就直接结束主线程 

例如

import threading
import time
def func1():
    for i in range(5):
        time.sleep(1)
        print(threading.currentThread())

def func2():
    while 1:
        print(222)
        time.sleep(1.5)

thread_1 = threading.Thread(target=func1,name='lalala')
thread_2 = threading.Thread(target=func2,daemon=True)

thread_1.start()
thread_2.start()

func1内的代码执行五次后就会结束运行 

线程阻塞join方法

join(timeout=None)

  • 等待,直到线程终结。这会阻塞调用这个方法的线程,直到被调用 join() 的线程终结 -- 不管是正常终结还是抛出未处理异常 -- 或者直到发生超时,超时选项是可选的。
  • 当 timeout 参数存在而且不是 None 时,它应该是一个用于指定操作超时的以秒为单位的浮点数或者分数。因为 join() 总是返回 None ,所以你一定要在 join() 后调用 is_alive() 才能判断是否发生超时 -- 如果线程仍然存活,则 join() 超时。
  • 当 timeout 参数不存在或者是 None ,这个操作会阻塞直到线程终结。
  • A thread can be joined many times.
  • 如果尝试加入当前线程会导致死锁, join() 会引起 RuntimeError 异常。如果尝试 join() 一个尚未开始的线程,也会抛出相同的异常

相当于给进程增加阻塞,需要等待timeout时间

我们分成三种情况分析这个功能 

1、等待时间小于执行时间

def target():
    time.sleep(7)
    print("线程{}已退出".format(current_thread().name))

thread01 = Thread(target=target,name="1")
thread01.start()

thread01.join(timeout=5)
print(11111)

在这里,五秒之后输出11111,再经过2秒后输出 线程1已退出

相当于这里join阻碍了主线程,设置主线程需要等待我的thread01  5秒才能运行

2、等待时间大于执行时间

def target():
    time.sleep(5)
    print("线程{}已退出".format(current_thread().name))

thread01 = Thread(target=target,name="1")
thread01.start()

thread01.join(timeout=7)
print(11111)

再这里,五秒之后同时输出两个

也就是说join函数在timeout时间结束或者执行时间结束都会返回

3、设置dasman

def target():
    time.sleep(5)
    print("线程{}已退出".format(current_thread().name))

thread01 = Thread(target=target,daemon=True,name="1")
thread01.start()

thread01.join(timeout=3)
print(11111)

 3秒后输入11111,不输出 其他

说明join可以无视守护线程的限制进行阻碍

线程锁 Lock

实现原始锁对象的类。一旦一个线程获得一个锁,会阻塞随后尝试获得锁的线程,直到它被释放;任何线程都可以释放它。

原始锁处于 "锁定" 或者 "非锁定" 两种状态之一。它被创建时为非锁定状态。它有两个基本方法, acquire() 和 release() 。当状态为非锁定时, acquire() 将状态改为 锁定 并立即返回。当状态是锁定时, acquire() 将阻塞至其他线程调用 release() 将其改为非锁定状态,然后 acquire() 调用重置其为锁定状态并返回。 release() 只在锁定状态下调用; 它将状态改为非锁定并立即返回。如果尝试释放一个非锁定的锁,则会引发 RuntimeError  异常。

当多个线程同时处理数据时,可能会发生错误,其实就是通过锁的方式 对数据进行保护

 我们通过一个例子查看这个的作用

import time
import threading

lock = threading.Lock()
cash = 1000

def func1(money):
    global cash
    if cash>money:
        time.sleep(1)
        cash -= money
        print('ok')
        print(cash)
    else:
        print('no')

th1 = threading.Thread(target=func1,args=[600])
th2 = threading.Thread(target=func1,args=[600])

# ok
# ok
# -200
# -200

由于两个线程同时开始,都会通过  cash>money的判断,从而使得同时减去600,输出负数

def func1(money):
    global cash
    lock.acquire()
    if cash>money:
        time.sleep(1)
        cash -= money
        print('ok')
        print(cash)
    else:
        print('no')
    lock.release()

th1 = threading.Thread(target=func1,args=[600])
th2 = threading.Thread(target=func1,args=[600])
#
th1.start()
th2.start()

# ok
# 400
# no

增加线程锁之后,在th1调用时,不允许th2调用,起到了保护数据的作用

下面这篇文章说到,锁保证了python语句的 原子性 ,我觉得很有道理,可以参考一下

关于Python中最基本的锁的理解 - 知乎

递归锁对象RLock

重入锁是一个可以被同一个线程多次获取的同步基元组件。在内部,它在基元锁的锁定/非锁定状态上附加了 "所属线程" 和 "递归等级" 的概念。在锁定状态下,某些线程拥有锁 ; 在非锁定状态下, 没有线程拥有它。

若要锁定锁,线程调用其 acquire() 方法;一旦线程拥有了锁,方法将返回。若要解锁,线程调用 release() 方法。 acquire()/release() 对可以嵌套;只有最终 release() (最外面一对的 release() ) 将锁解开,才能让其他线程继续处理 acquire() 阻塞

 上面的Lock是不能被重复访问的,否则会陷入死锁状态

例如

import threading
lock = threading.Lock()

print(22222)
lock.acquire()
lock.acquire()
print(1111)
lock.release()
lock.release()

输出22222只会就陷入了死锁状态,但是换成RLock就可以

我们可以看一下Rlock的源码

[threading]
class _RLock:

    def __init__(self):
        self._block = _allocate_lock() # _thread模块中定义一个锁对象的方法
        self._owner = None # 用来标记哪个线程获取了锁
        self._count = 0 # 计数器

    def acquire(self, blocking=True, timeout=-1):
        me = get_ident()
        if self._owner == me:
            self._count += 1
            return 1
        rc = self._block.acquire(blocking, timeout)
        if rc:
            self._owner = me
            self._count = 1
        return rc

    def release(self):
        if self._owner != get_ident():
            raise RuntimeError("cannot release un-acquired lock")
        self._count = count = self._count - 1
        if not count:
            self._owner = None
            self._block.release()

 这里的 me代表方法的调用者,如果调用者就是锁的拥有者,则计数器加1,否则进行阻塞自己(也可能是获得锁)

可以看到,在重复得到锁的过程中,实际上只有第一次调用了acquire()方法,后面只进行了加1操作,解开锁的过程类似

但是需要注意的是,acquire()后必须搭配release(),若两者数量不相同,则仍然会陷入锁死

GIL锁

全局解释器锁(英语:Global Interpreter Lock,缩写GIL)
是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行
即便在多核心处理器上,使用GIL的解释器也只允许同一时间执行一个线程。
 

 也就是线程在运行的时候获得GIL,当它到达IO时释放GIL

那为什么有GIL锁这个东西呢,下面这个视频讲的很好,可参考

Python速度慢的罪魁祸首,全局解释器锁GIL_哔哩哔哩_bilibili

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/792788.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux学成之路(基础篇(二十三)MySQL服务(中)

目录 MySQL服务之SQL语句 一、SQL语句类型 一、 DDL语句 二、DML语句 三、DCL语句 四、DQL 语句 二、 数据库操作 一、查看 二、创建 三、进入 四、删除数据库 五、更改数据库名称 六、更改字符集 三、数据表管理 一、数据类型 一、数值类型 TINYINT SMALLINT…

组件间通信案例练习

1.实现父传子 App.vue <template><div class"app"><tab-control :titles["衣服","鞋子","裤子"]></tab-control><tab-control :titles["流行","最新","优选","数码&q…

网络安全 Day08-Linux文件属性知识

Linux 文件属性知识 1. 查看文件属性2. 文件属性知识 1. 查看文件属性 语法&#xff1a;ls -lhi&#xff08;l-长格式 h-人类可读&#xff09;结果[rootlocalhost ~] ls -lhi total 11M 202312369 drwxr-xr-x. 31 root root 4.0K Jul 30 2023 1 134317954 -rw-------. 1 roo…

SpringBoot实战(二十二)集成 Sleuth、Zipkin

目录 一、简介1.Sleuth2.Zipkin 二、搭建 zipkin-server1.jar包启动2.docker启动3.自己搭建**Maven依赖**添加启动类注解 4.页面截图 三、搭建 sleuth-zipkin1.Maven 依赖2.yaml配置3.代码实现DemoController.javaDemoFeignClient.java 4.测试 一、简介 1.Sleuth 官方文档&am…

安全技术-大数据平台安全防护技术

一、大数据基本概念及背景 1.1大数据发展的背景-数据爆炸 伴随着互联⽹、物联⽹、电⼦商务、社交媒体、现代物流、⽹络⾦融等⾏业的发展&#xff0c;全球数据总量正呈⼏何级数增长&#xff0c;过去⼏年时间产⽣的数据总量超过了⼈类历史上的数据总和&#xff0c;预计2020年全…

json的序列化与反序列化

目录 json的下载 json的序列化 json的反序列化 备注json开源项目github地址&#xff1a;https://github.com/nlohmann/json 备注开发环境&#xff1a;vscode通过ssh连接虚拟机中的ubuntu&#xff0c;ubuntu-20.04.3-desktop-amd64.iso json的下载 git clone https://githu…

iptables的备份和还原

iptables的备份和还原 1、写在命令行当中的都是临时设置 2、把规则配置写在服务的文件当中&#xff0c;形成永久有效 备份&#xff1a;把iptables里面所有的配置都保存在/opt/ky30.bak中 iptables-save > /opt/ky30.bak 例&#xff1a; 默认配置文件在/etc/sysconfig/ip…

C++ const与指针

const与指针 1.const在C语言和C中的区别 &#xff08;1&#xff09;在C语言中 #include<stdio.h> int main() {const int n 10; int arr[n] { 1,2 }; //errorreturn 0; }在C语言中&#xff0c;const修饰的变量是只读变量&#xff08;常变量&#xff09;&#xff0c…

假如三国有网络安全攻防演练

东汉末年&#xff0c;天下三分&#xff0c;而奠定这一局势的战役就是历史上赫赫有名的“赤壁大战”。 作为我国以少胜多的经典战例&#xff0c;赤壁之战汇聚了三国最顶级的名臣武将&#xff0c;期间各种计谋、策略环环相扣&#xff0c;高潮迭起&#xff0c;攻守双方你方唱罢我…

【Java|golang】860. 柠檬水找零

在柠檬水摊上&#xff0c;每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品&#xff0c;&#xff08;按账单 bills 支付的顺序&#xff09;一次购买一杯。 每位顾客只买一杯柠檬水&#xff0c;然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零&#xff0c;…

AtcoderABC235场

A - Rotate A - Rotate 题目大意 题目要求给定一个3位的整数abc&#xff0c;其中每个数字都不是0。计算abc bca cab的结果。 思路分析 将a、b和c按照题目要求的顺序连接起来&#xff0c;得到字符串abc、bca和cab。将这三个字符串转换为整数&#xff0c;分别表示为变量abc…

【后端面经】微服务构架 (1-3) | 熔断:抖抖抖不停?微服务熔断策略让你的系统稳如泰山!

文章目录 一、前置知识1、什么是熔断?2、什么是限流?3、什么是降级?4、怎么判断微服务出现了问题?A、指标有哪些?B、阈值如何选择?C、超过阈值之后,要不要持续一段时间才触发熔断?5、服务恢复正常二、面试环节1、面试准备2、面试基本思路三、总结 在微服务构架中…

ModuleNotFoundError: No module named ‘transformers.models.mmbt‘

使用simpletransformers时出现ModuleNotFoundError: No module named transformers.models.mmbt’错误。 1. 报错代码&#xff1a; from simpletransformers.classification import ClassificationModel 2. 报错结果 3. 报错原因 transformers和simpletransformers版本的问…

【C语言进阶篇】数组指针都学过了那么函数指针你了解嘛?

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言&#x1f4ac; 函数指针&#x1f4ad; 函数名 和 &函数&#x1f4ad; 函数指针的定义&am…

蕨型叶分形

目录 要点 基本语句 EraseMode 习题 1 设置颜色 2 旋转蕨型叶图 3 枝干 4 塞平斯基三角形 要点 蕨型叶是通过一个点的反复变换产生的&#xff0c;假设x是一个含有两个分量的向量&#xff0c;可以用来表示平面内的一个点&#xff0c;则可以用Axb的形式对其进行变换。 基本…

衡器关于检定分度值e和实际分度值d的一点认识

在看衡器认证的文档时有两个概念很容易混淆d(actual scale interval)和e(verification scale interval)&#xff0c;国内文档上翻译为实际分度值和检定分度值。 实际分度值(d)就是称能显示的最小刻度或最小能显示的值跨度&#xff0c;可以理解为可读精度&#xff0c;例如电子秤…

[虚幻引擎] UE里面监控每帧循环里面 C++ 函数的性能, 函数耗时,函数效率,函数执行时间

在使用C开发UE引擎&#xff0c;有时候需要监控函数的执行的执行效率&#xff0c;这个时候有两种方式可以使用。 1. 执行代码耗时时间 double ThisTime 0;{SCOPE_SECONDS_COUNTER(ThisTime);// ...// 一串代码// ...}UE_LOG(LogTemp, Log, TEXT("Stats::Broadcast %.2f&qu…

【Spring Boot】Web开发 — 数据验证

Web开发 — 数据验证 对于应用系统而言&#xff0c;任何客户端传入的数据都不是绝对安全有效的&#xff0c;这就要求我们在服务端接收到数据时也对数据的有效性进行验证&#xff0c;以确保传入的数据安全正确。接下来介绍Spring Boot是如何实现数据验证的。 1.Hibernate Vali…

mysql(三)用户权限管理

目录 前言 一、概述 二、用户权限类型 三、用户赋权 四、权限删除 五、删除用户 前言 为什么要设置用户权限&#xff1f; MySQL设置用户管理权限的主要目的是为了确保数据库的安全性和数据的机密性。以下是一些原因&#xff1a; 1. 安全性&#xff1a;MySQL是一个开源的关系型…

多线程案例 | 单例模式、阻塞队列、定时器、线程池

多线程案例 1、案例一&#xff1a;线程安全的单例模式 单例模式 单例模式是设计模式的一种 什么是设计模式&#xff1f; 设计模式好比象棋中的 “棋谱”&#xff0c;红方当头炮&#xff0c;黑方马来跳&#xff0c;针对红方的一些走法&#xff0c;黑方应招的时候有一些固定的…