文章大部分参考:https://www.nowcoder.com/discuss/426117497918660608
什么是Python?
Python是一种编程语言,
赋值、深拷贝、浅拷贝
赋值:对象的赋值就是简单的引用。 赋值操作不会开辟新的空间,只是复制了对象的引用
如:
a = [1, 2, 3] b = a
此时a和b完全相同,修改a即相当于对b进行修改
拷贝本质上是在说“新变量”和“原变量”是不是共享同一块内存数据
浅拷贝
浅拷贝有三种形式:切片操作、工厂函数、copy模块中的copy函数
只复制第一层对象,里面嵌套的子对象依然共享。也就是说只复制第一层,更深层的数据依然共享。
例:
import copy a = [[1,2],[3,4]] b = copy.copy(a)
此时a is b,将得到False
外层列表已经复制了,但a和b不是同一个对象
但判断a[0] is b[0]将得到True
这说明:内层列表还是同一个对象,并没有真正复制
深拷贝
深拷贝,是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。因此,新对象和原对象没有任何关联。
import copy a = [[1,2],[3,4]] b = copy.deepcopy(a)
现在a is b是False,且a[0] is b[0]也是False
说明:内层列表也复制了,完全独立
解释型语言和编译型语言
编译型语言:
通过专门的编译器,把所有源代码一次转换为特定平台的可执行文件,然后再运行。一次编译,多次运行。如C、C++
优点:一次编译,多次运行;脱离编译环境,并且运行效率高。 缺点:依靠编译器、跨平台性差、可移植性差。
- 编译时根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。
- 编译之后如果需要修改就需要整个模块重新编译。
解释型语言:
由专门的解释器,根据需要将部分源代码临时转换成特定平台的机器码,然后执行。边解释边执行。比如Python, PHP.
优点:
- 跨平台性好,通过不同的解释器,将相同的源代码解释成不同平台下的机器码。 缺点:
- 一边执行一边转换,效率不高。
混合型语言:
如Java, C#。兼具执行效率和跨平台性,不直接编译成机器码,需要先编译成中间码(java是编译成字节码),需要中间语言运行库类似java虚拟机(JVM),边解释边运行。
is和== 的区别
==是比较操作符,只是判断对象的值(value)是否一致,而is判断的是对象之间的身份(内存地址)是否一致。对象的身份可以通过id()方法来查看
os和sys的区别
os模块是Python标准库中提供的与操作系统交互的模块,提供了访问操作系统底层的接口,里面有很多操作系统的函数。
sys模块负责程序与Python解释器的交互。
可变对象和不可变对象
在Python中,一切皆有对象,对象必有的三个属性:地址、类型、值
可变对象
- 当对象的值发生变化,但内存地址没有改变时,则说明是可变类型
- python里的可变对象有:列表、字典、集合
- 引用可变对象时,会创建新的内存地址,当可变对象值发生改变时,原内存地址不会改变
- 引用传递主函数向调用函数传递的参数是可变类型时,实际上是将实参的引用传入了调用函数,对引用的操作等于其指定的对象进行操作
不可变对象
- 当对象的值发生变化,但内存地址也发生改变时,则说明是不可变类型
- python里的不可变对象有:元组、字符串、数值
- python在引用不可变对象时,会寻找该对象是否被创建过,若该对象已创建,则变量会直接引用该对象,不会再申请新的内存空间。
- 值传递主函数向调用函数传递的参数是不可变类型时,实际上只是将实参的拷贝(即临时副本)传递给了被调用函数,并不是实参本身,这样被调函数不能直接修改主调函数中变量的值
注意:如果直接将list2 = list1,那么list1和list2的地址会是相同的。只是换了不同的名称而已。
* arg和** kwarg作用
允许在调用函数的时候传入多个实参
*arg会把位置参数转化为tuple,**kwarg会把关键字参数转化为dict
with如何使用
with所求值的对象必须有一个enter()方法,一个exit()方法
with后面的语句被求值后,返回对象的__enter__()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的exit()方法
__new__和__init__
__init__ 为初始化方法,__new__方法是真正的构造函数。
__new__是实例创建时被调用,它的任务是创建并返回该实例,是静态方法。
__init__是实例创建之后调用的,然后设置对象属性的一些初始值。
__new__方法在__init__方法之前被调用,并且__new__方法的返回值将传递给__init__方法作为第一个参数,最后__init__给这个实例设置一些参数
装饰器
Python的装饰器本质上是一个嵌套函数,它接受被装饰的函数(func)作为参数,并返回一个包装过的函数。
本质上是一个“接受函数并返回新函数“的函数
作用:不修改原函数代码的情况下,给函数额外加功能
Python装饰器广泛应用于缓存、权限校验(如django中的@login_required和@permission_required装饰器)、性能测试(比如统计一段程序的运行时间)和插入日志等应用场景
比如有个函数:
def func():
print("我是原函数")
现在想:
- 调用前打印“开始”
- 调用后打印“结束” 但:
- 不想改 func 内部代码 于是:
def decorator(fn):
def wrapper():
print("开始")
fn()
print("结束")
return wrapper
使用:
func = decorator(func) func()
@语法糖: 为了写着方便:
@decorator
def func():
print("hello")
等价于:
def func():
print("hello")
func = decorator(func)
迭代器和生成器
可迭代对象
python中一个非常强大的功能,它可以访问容器(字符串、列表、元祖、集合、字典、range)。 迭代是通过for循环遍历对象中的每一个元素,将元素取出来的过程。所以:容器都是可迭代对象
可迭代对象除了包含常见的序列,还包括迭代器
迭代器
- 迭代器是一个可以记住遍历的位置的对象
- 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
- 迭代器有两个基本方法:
iter()和next() - 字符串,列表或元组对象都可用于创建迭代器
list=[1,2,3,4] it = iter(list) print(next(it))
生成器
- 在python中,使用了
yield的函数被称为生成器 - 跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作。
- 在调用生成器运行的过程中,每次遇到
yield时函数会暂停并保存当前所有的运行信息,返回yield的值,并在下一次执行next()方法时从当前位置继续运行 - 在调用生成器函数时,函数体并不执行,只返回一个生成器对象
- 在对生成器对象使用next方法或者遍历的时候,生成器函数体才真正执行
def gen():
print("开始")
yield 1
print("继续")
yield 2
print("结束")
yield 3
执行:
g = gen() print(next(g)) print(next(g)) print(next(g))
输出:
开始 1 继续 2 结束 3
yield:
return + 记住返回位置
普通return:
返回后函数直接结束
yield:
- 返回值
- 函数没有结束
- 下次还能从原位置继续
filter map reduce
filter函数用于过滤序列,接收一个函数和一个序列,把函数作用于序列的每个元素上,然后根据返回值是True还是False决定保留还是丢弃该元素
mylist = list(range(10)) list(filter(lambda x: x % 2 == 1, mylist)) [1, 3, 5, 7, 9]
map函数传入一个函数和一个序列,并把函数作用到序列的每个元素上,返回一个可迭代对象。
list(map(lambda x: x % 2, mylist)) [1, 0, 1, 0, 1, 0, 1, 0, 1]
reduce函数用于递归计算,同样需要传入一个函数和一个序列,并把函数和序列元素的计算结果与下一个元素进行计算。
from functools import reduce reduce(lambda x, y: x + y, range(101)) 5050
全局变量 局部变量 闭包
在函数定义的变量为全局变量。全局变量可以在函数中直接进行访问,但是在修改全局变量时,为了避免与局部变量产生混淆,需要先加上global声明,然后再修改。
闭包是Python编程一个非常重要的概念,如果一个外函数中定义一个内函数,且内函数体内引用到了体外的变量,这时外函数通过return返回内函数的引用时,会把定义时涉及到的外部引用变量和内函数打包成一个整体(闭包) 返回。
闭包=函数+它记住的外部变量
例:
def outer():
x = 10
def inner():
print(x)
return inner
执行:
f = outer()
注意:
这里outer已经执行结束了
正常来说:
x = 10
这个变量应该没了。 但:
f()
还能输出10 这就是闭包
lst =[lambda x: x*i for i in range(4)] res = [m(2) for m in lst] print(res)
预期的结果为:[0,2,4,6]
实际输出为:[6,6,6,6]
这个为什么叫闭包?
因为
lambda x:x*i
这个lambda用到了自己函数体外的变量i
即内函数:lambda,外部作用域:for循环所在作用域
匿名函数
匿名函数的关键字为lambda,表现形式为lambda 参数:返回值,lambda后面的参数就是函数的形参,冒号后面的表达式就是返回值。
lambda表达式的意义:
- 对于只有一行的函数,使用此方式可以省去定义函数的过程,使代码简洁明了;
- 对于不需要重复使用的函数,此方式可以在用完之后,立即释放,提高程序执行性能
垃圾回收机制
引用计数(主要手段)+标记清除(辅助)+分代回收(辅助)
1、引用计数
在Python中,使用了引用计数这一技术来实现内存管理,一个对象被创建完成后就有一个变量指向这个对象,那么就这个对象的引用计数为1,以后如果有其他变量指向这个对象,其引用计数也会相应增加,如果一个变量不再执行这个对象,那么这个对象的引用计数减1。如果一个对象没有任何变量指向这个对象,也即引用计数为0,那么这个对象就会被python回收。
优点:
- 高效。
- 运行期没有停顿。可以类比一下Ruby的垃圾回收机制,也就是实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
- 对象有确定的生命周期。
- 易于实现。 缺点:
- 维护引用计数消耗资源,维护引用计数的次数和引用赋值成正比。
- 无法解决循环引用的问题。比如现在有两个对象分别为
a和b,a指向了b,b又指向了a,那么他们两的引用计数永远都不会为0。也即永远得不到回收。 2、标记清除
针对循环引用的情况,python引入标记清除算法。
标记清除算法是一种基于追踪回收技术实现的垃圾回收算法。它分为两个阶段
- 第一阶段是标记阶段,GC会把所有的活动对象打上标记
- 第二阶段是把那些没有标记的对象非活动对象进行回收。 3、分代回收
分代回收是建立在标记清除技术基础之上的。是一种以时间换空间的操作方式。
Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一“代”, Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾回收机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
协程
协程本质上是:
一个可以主动暂停、以后再继续执行的函数
普通函数:
- 一路执行到底
- 不能中途挂起 协程:
- 执行到一半可以暂停
- 之后还能从原位置恢复
协程的核心作用:
提高IO密集型程序的执行效率
常见的IO操作:
- 网络请求
- 数据库查询
协程的思想:
等待的时候,让CPU去执行别的任务
如何定义协程? Python使用:
async def
定义协程函数
例:
async def test():
print("hello")
调用async函数不会立即执行,而是返回一个协程对象
await的作用
await 某个耗时操作
表示:
当前协程先暂停,去执行别的协程
asyncio
asyncio是Python的协程调度框架
作用:
- 管理协程
- 调度协程
- 控制暂停与恢复
asyncio.run()
作用:
asyncio.run(main())
用于:
- 启动事件循环
- 执行协程
协程通常是单线程的任务切换,不是多个线程同时运行

评论区
评论加载中...