python学习要点(二)

简介:

python学习要点(二)

'==' VS 'is'#
'=='操作符比较对象之间的值是否相等。
'is'操作符比较的是对象的身份标识是否相等,即它们是否是同一个对象,是否指向同一个内存地址。

如:

Copy
a = 10
b = 10

a == b
True

id(a)
4427562448

id(b)
4427562448

a is b
True
Python 会为 10 这个值开辟一块内存,然后变量 a 和 b 同时指向这块内存区域,即 a 和 b 都是指向 10 这个变量,因此 a 和 b 的值相等,id 也相等。

不过,对于整型数字来说,以上a is b为 True 的结论,只适用于 -5 到 256 范围内的数字。这里和java的Integer的缓存有点像,java缓存-127到128。

当我们比较一个变量与一个单例(singleton)时,通常会使用'is'。一个典型的例子,就是检查一个变量是否为 None:

Copy
if a is None:

  ...

if a is not None:

  ...

比较操作符'is'的速度效率,通常要优于'=='。因为'is'操作符不能被重载,而执行a == b相当于是去执行a.eq(b),而 Python 大部分的数据类型都会去重载__eq__这个函数。

浅拷贝和深度拷贝#
浅拷贝#
浅拷贝,是指重新分配一块内存,创建一个新的对象,里面的元素是原对象中子对象的引用。因此,如果原对象中的元素不可变,那倒无所谓;但如果元素可变,浅拷贝通常会带来一些副作用,如下:

Copy
l1 = [[1, 2], (30, 40)]
l2 = list(l1)
l1.append(100)
l1[0].append(3)

l1
[[1, 2, 3], (30, 40), 100]

l2
[[1, 2, 3], (30, 40)]

l1[1] += (50, 60)
l1
[[1, 2, 3], (30, 40, 50, 60), 100]

l2
[[1, 2, 3], (30, 40)]
在这个例子中,因为浅拷贝里的元素是对原对象元素的引用,因此 l2 中的元素和 l1 指向同一个列表和元组对象。

l1[0].append(3),这里表示对 l1 中的第一个列表新增元素 3。因为 l2 是 l1 的浅拷贝,l2 中的第一个元素和 l1 中的第一个元素,共同指向同一个列表,因此 l2 中的第一个列表也会相对应的新增元素 3。

l1[1] += (50, 60),因为元组是不可变的,这里表示对 l1 中的第二个元组拼接,然后重新创建了一个新元组作为 l1 中的第二个元素,而 l2 中没有引用新元组,因此 l2 并不受影响。

深度拷贝#
所谓深度拷贝,是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。因此,新对象和原对象没有任何关联。

Python 中以 copy.deepcopy() 来实现对象的深度拷贝。

Copy
import copy
l1 = [[1, 2], (30, 40)]
l2 = copy.deepcopy(l1)
l1.append(100)
l1[0].append(3)

l1
[[1, 2, 3], (30, 40), 100]

l2
[[1, 2], (30, 40)]
不过,深度拷贝也不是完美的,往往也会带来一系列问题。如果被拷贝对象中存在指向自身的引用,那么程序很容易陷入无限循环:

Copy
import copy
x = [1]
x.append(x)

x
[1, [...]]

y = copy.deepcopy(x)
y
[1, [...]]
这里没有出现 stack overflow 的现象,是因为深度拷贝函数 deepcopy 中会维护一个字典,记录已经拷贝的对象与其 ID。拷贝过程中,如果字典里已经存储了将要拷贝的对象,则会从字典直接返回。

Copy
def deepcopy(x, memo=None, _nil=[]):

"""Deep copy operation on arbitrary Python objects.
    
See the module's __doc__ string for more info.
"""

if memo is None:
    memo = {}
d = id(x) # 查询被拷贝对象 x 的 id
y = memo.get(d, _nil) # 查询字典里是否已经存储了该对象
if y is not _nil:
    return y # 如果字典里已经存储了将要拷贝的对象,则直接返回
    ...    

Python参数传递#
Python 中参数的传递是赋值传递,或者是叫对象的引用传递。这里的赋值或对象的引用传递,不是指向一个具体的内存地址,而是指向一个具体的对象。

如果对象是可变的,当其改变时,所有指向这个对象的变量都会改变。
如果对象不可变,简单的赋值只能改变其中一个变量的值,其余变量则不受影响。
例如:

Copy
def my_func1(b):

b = 2

a = 1
my_func1(a)
a
1
这里的参数传递,使变量 a 和 b 同时指向了 1 这个对象。但当我们执行到 b = 2 时,系统会重新创建一个值为 2 的新对象,并让 b 指向它;而 a 仍然指向 1 这个对象。所以,a 的值不变,仍然为 1。

Copy
def my_func3(l2):

l2.append(4)

l1 = [1, 2, 3]
my_func3(l1)
l1
[1, 2, 3, 4]
这里 l1 和 l2 先是同时指向值为 [1, 2, 3] 的列表。不过,由于列表可变,执行 append() 函数,对其末尾加入新元素 4 时,变量 l1 和 l2 的值也都随之改变了。

Copy
def my_func4(l2):

l2 = l2 + [4]

l1 = [1, 2, 3]
my_func4(l1)
l1
[1, 2, 3]
这里 l2 = l2 + [4],表示创建了一个“末尾加入元素 4“的新列表,并让 l2 指向这个新的对象。这个过程与 l1 无关,因此 l1 的值不变。

装饰器#
首先我们看一个装饰器的简单例子:

Copy
def my_decorator(func):

def wrapper():
    print('wrapper of decorator')
    func()
return wrapper

def greet():

print('hello world')

greet = my_decorator(greet)
greet()

输出

wrapper of decorator
hello world
这段代码中,变量 greet 指向了内部函数 wrapper(),而内部函数 wrapper() 中又会调用原函数 greet(),因此,最后调用 greet() 时,就会先打印'wrapper of decorator',然后输出'hello world'。

my_decorator() 就是一个装饰器,它把真正需要执行的函数 greet() 包裹在其中,并且改变了它的行为。

在python中,可以使用更优雅的方式:

Copy
def my_decorator(func):

def wrapper():
    print('wrapper of decorator')
    func()
return wrapper

@my_decorator
def greet():

print('hello world')

greet()
@my_decorator就相当于前面的greet=my_decorator(greet)语句

通常情况下,我们会把args和kwargs,作为装饰器内部函数 wrapper() 的参数。args和kwargs,表示接受任意数量和类型的参数,因此装饰器就可以写成下面的形式:

Copy
def my_decorator(func):

def wrapper(*args, **kwargs):
    print('wrapper of decorator')
    func(*args, **kwargs)
return wrapper

这样可以让装饰器接受任意的参数。

自定义参数的装饰器#
比如我想要定义一个参数,来表示装饰器内部函数被执行的次数

Copy
def repeat(num):

def my_decorator(func):
    def wrapper(*args, **kwargs):
        for i in range(num):
            print('wrapper of decorator')
            func(*args, **kwargs)
    return wrapper
return my_decorator

@repeat(4)
def greet(message):

print(message)

greet('hello world')

输出:

wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
保留原函数的元信息#
如下:

Copy
greet.__name__

输出

'wrapper'

help(greet)

输出

Help on function wrapper in module __main__:

wrapper(args, *kwargs)
greet() 函数被装饰以后,它的元信息变了。元信息告诉我们“它不再是以前的那个 greet() 函数,而是被 wrapper() 函数取代了”。

因此,可以加上内置的装饰器@functools.wrap,它会帮助保留原函数的元信息。
如下:

Copy
import functools

def my_decorator(func):

@functools.wraps(func)
def wrapper(*args, **kwargs):
    print('wrapper of decorator')
    func(*args, **kwargs)
return wrapper

@my_decorator
def greet(message):

print(message)

greet.__name__

输出

'greet'
类装饰器#
类装饰器主要依赖于函数__call_(),每当你调用一个类的示例时,函数__call__()就会被执行一次。

Copy
class Count:

def __init__(self, func):
    self.func = func
    self.num_calls = 0

def __call__(self, *args, **kwargs):
    self.num_calls += 1
    print('num of calls is: {}'.format(self.num_calls))
    return self.func(*args, **kwargs)

@Count
def example():

print("hello world")

example()

输出

num of calls is: 1
hello world

example()

输出

num of calls is: 2
hello world

装饰器的嵌套#
如:

Copy
@decorator1
@decorator2
@decorator3
def func():

...

等效于:

Copy
decorator1(decorator2(decorator3(func)))
例子:

Copy
import functools

def my_decorator1(func):

@functools.wraps(func)
def wrapper(*args, **kwargs):
    print('execute decorator1')
    func(*args, **kwargs)
return wrapper

def my_decorator2(func):

@functools.wraps(func)
def wrapper(*args, **kwargs):
    print('execute decorator2')
    func(*args, **kwargs)
return wrapper

@my_decorator1
@my_decorator2
def greet(message):

print(message)

greet('hello world')

输出

execute decorator1
execute decorator2
hello world
协程#
协程和多线程的区别,主要在于两点,一是协程为单线程;二是协程由用户决定,在哪些地方交出控制权,切换到下一个任务。

我们先来看一个例子:

Copy
import asyncio

async def crawl_page(url):

print('crawling {}'.format(url))
sleep_time = int(url.split('_')[-1])
await asyncio.sleep(sleep_time)
print('OK {}'.format(url))

async def main(urls):

tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
for task in tasks:
    await task

%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))

输出

crawling url_1
crawling url_2
crawling url_3
crawling url_4
OK url_1
OK url_2
OK url_3
OK url_4
Wall time: 3.99 s
执行协程有多种方法,这里我介绍一下常用的三种:

首先,我们可以通过 await 来调用。await 执行的效果,和 Python 正常执行是一样的,也就是说程序会阻塞在这里,进入被调用的协程函数,执行完毕返回后再继续,而这也是 await 的字面意思。

其次,我们可以通过 asyncio.create_task() 来创建任务。要等所有任务都结束才行,用for task in tasks: await task 即可。

最后,我们需要 asyncio.run 来触发运行。asyncio.run 这个函数是 Python 3.7 之后才有的特性。一个非常好的编程规范是,asyncio.run(main()) 作为主程序的入口函数,在程序运行周期内,只调用一次 asyncio.run。

在上面的例子中,也可以使用await asyncio.gather(*tasks),表示等待所有任务。

Copy
import asyncio

async def crawl_page(url):

print('crawling {}'.format(url))
sleep_time = int(url.split('_')[-1])
await asyncio.sleep(sleep_time)
print('OK {}'.format(url))

async def main(urls):

tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
await asyncio.gather(*tasks)

%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))

输出

crawling url_1
crawling url_2
crawling url_3
crawling url_4
OK url_1
OK url_2
OK url_3
OK url_4
Wall time: 4.01 s
协程中断和异常处理#
Copy
import asyncio

async def worker_1():

await asyncio.sleep(1)
return 1

async def worker_2():

await asyncio.sleep(2)
return 2 / 0

async def worker_3():

await asyncio.sleep(3)
return 3

async def main():

task_1 = asyncio.create_task(worker_1())
task_2 = asyncio.create_task(worker_2())
task_3 = asyncio.create_task(worker_3())

await asyncio.sleep(2)
task_3.cancel()

res = await asyncio.gather(task_1, task_2, task_3, return_exceptions=True)
print(res)

%time asyncio.run(main())

输出

[1, ZeroDivisionError('division by zero'), CancelledError()]
Wall time: 2 s
这个例子中,使用了task_3.cancel()来中断代码,使用了return_exceptions=True来控制输出异常,如果不设置的话,错误就会完整地 throw 到我们这个执行层,从而需要 try except 来捕捉,这也就意味着其他还没被执行的任务会被全部取消掉。

Python 中的垃圾回收机制#
python采用的是引用计数机制为主,标记-清除和分代收集(隔代回收)两种机制为辅的策略。

引用计数法#
引用计数法机制的原理是:每个对象维护一个ob_ref字段,用来记录该对象当前被引用的次数,每当新的引用指向该对象时,它的引用计数ob_ref加1,每当该对象的引用失效时计数ob_ref减1,一旦对象的引用计数为0,该对象立即被回收,对象占用的内存空间将被释放。

它的缺点是它不能解决对象的“循环引用”。

标记清除算法#
对于一个有向图,如果从一个节点出发进行遍历,并标记其经过的所有节点;那么,在遍历结束后,所有没有被标记的节点,我们就称之为不可达节点。显而易见,这些节点的存在是没有任何意义的,自然的,我们就需要对它们进行垃圾回收。

在 Python 的垃圾回收实现中,mark-sweep 使用双向链表维护了一个数据结构,并且只考虑容器类的对象(只有容器类对象才有可能产生循环引用)。

分代收集算法#
Python 将所有对象分为三代。刚刚创立的对象是第 0 代;经过一次垃圾回收后,依然存在的对象,便会依次从上一代挪到下一代。而每一代启动自动垃圾回收的阈值,则是可以单独指定的。当垃圾回收器中新增对象减去删除对象达到相应的阈值时,就会对这一代对象启动垃圾回收。

作者: luozhiyun

出处:https://www.cnblogs.com/luozhiyun/p/12685722.html

相关文章
|
26天前
|
数据库 Python
Python学习的自我理解和想法(18)
这是我在学习Python第18天的总结,内容基于B站千锋教育课程,主要涉及面向对象编程的核心概念。包括:`self`关键字的作用、魔术方法的特点与使用(如构造函数`__init__`和析构函数`__del__`)、类属性与对象属性的区别及修改方式。通过学习,我初步理解了如何利用这些机制实现更灵活的程序设计,但深知目前对Python的理解仍较浅显,欢迎指正交流!
|
12天前
|
安全 数据安全/隐私保护 Python
Python学习的自我理解和想法(27)
本文记录了学习Python第27天的内容,主要介绍了使用Python操作PPTX和PDF的技巧。其中包括通过`python-pptx`库创建PPTX文件的详细步骤,如创建幻灯片对象、选择母版布局、编辑标题与副标题、添加文本框和图片,以及保存文件。此外,还讲解了如何利用`PyPDF2`库为PDF文件加密,涵盖安装库、定义函数、读取文件、设置密码及保存加密文件的过程。文章总结了Python在处理文档时的强大功能,并表达了对读者应用这些技能的期待。
|
28天前
|
数据采集 机器学习/深度学习 自然语言处理
Python学习的自我理解和想法(16)
这是我在B站千锋教育课程中学Python的第16天总结,主要学习了`datetime`和`time`模块的常用功能,包括创建日期、时间,获取当前时间及延迟操作等。同时简要介绍了多个方向的补充库,如网络爬虫、数据分析、机器学习等,并讲解了自定义模块的编写与调用方法。因开学时间有限,内容精简,希望对大家有所帮助!如有不足,欢迎指正。
|
7天前
|
存储 搜索推荐 算法
Python学习的自我理解和想法(28)
本文记录了学习Python第28天的内容——冒泡排序。通过B站千锋教育课程学习,非原创代码。文章详细介绍了冒泡排序的起源、概念、工作原理及多种Python实现方式(普通版、进阶版1和进阶版2)。同时分析了其时间复杂度(最坏、最好、平均情况)与空间复杂度,并探讨了实际应用场景(如小规模数据排序、教学示例)及局限性(如效率低下、不适用于高实时性场景)。最后总结了冒泡排序的意义及其对初学者的重要性。
|
26天前
|
Python
Python学习的自我理解和想法(19)
这是一篇关于Python面向对象学习的总结,基于B站千锋教育课程内容编写。主要涵盖三大特性:封装、继承与多态。详细讲解了继承(包括构造函数继承、多继承)及类方法与静态方法的定义、调用及区别。尽管开学后时间有限,但作者仍对所学内容进行了系统梳理,并分享了自己的理解,欢迎指正交流。
|
13天前
|
Python
Python学习的自我理解和想法(26)
这是一篇关于使用Python操作Word文档的学习总结,基于B站千锋教育课程内容编写。主要介绍了通过`python-docx`库在Word中插入列表(有序与无序)、表格,以及读取docx文件的方法。详细展示了代码示例与结果,涵盖创建文档对象、添加数据、设置样式、保存文件等步骤。虽为开学后时间有限下的简要记录,但仍清晰梳理了核心知识点,有助于初学者掌握自动化办公技巧。不足之处欢迎指正!
|
24天前
|
数据采集 数据挖掘 Python
Python学习的自我理解和想法(22)
本文记录了作者学习Python第22天的内容——正则表达式,基于B站千锋教育课程。文章简要介绍了正则表达式的概念、特点及使用场景(如爬虫、数据清洗等),并通过示例解析了`re.search()`、`re.match()`、拆分、替换和匹配中文等基本语法。正则表达式是文本处理的重要工具,尽管入门较难,但功能强大。作者表示后续会深入讲解其应用,并强调学好正则对爬虫学习的帮助。因时间有限,内容为入门概述,不足之处敬请谅解。
|
20天前
|
索引 Python
Python学习的自我理解和想法(24)
本文记录了学习Python操作Excel的第24天内容,基于B站千锋教育课程。主要介绍openpyxl插件的使用,包括安装、读取与写入Excel文件、插入图表等操作。具体内容涵盖加载工作簿、获取单元格数据、创建和保存工作表,以及通过图表展示数据。因开学时间有限,文章简要概述了各步骤代码实现,适合初学者参考学习。如有不足之处,欢迎指正!
|
26天前
|
设计模式 数据库 Python
Python学习的自我理解和想法(20)
这是我在B站千锋教育课程中学习Python第20天的总结,主要涉及面向对象编程的核心概念。内容包括:私有属性与私有方法的定义、语法及调用方式;多态的含义与实现,强调父类引用指向子类对象的特点;单例设计模式的定义、应用场景及实现步骤。通过学习,我掌握了如何在类中保护数据(私有化)、实现灵活的方法重写(多态)以及确保单一实例(单例模式)。由于开学时间有限,内容简明扼要,如有不足之处,欢迎指正!
|
17天前
|
Python
Python学习的自我理解和想法(25)
这是一篇关于Python操作Word文档(docx)的教程总结,基于B站千锋教育课程学习(非原创代码)。主要内容包括:1) docx库插件安装;2) 创建与编辑Word文档,如添加标题、段落、设置字体样式及保存;3) 向新或现有Word文档插入图片。通过简单示例展示了如何高效使用python-docx库完成文档操作。因开学时间有限,内容精简,后续将更新列表和表格相关内容。欢迎指正交流!

热门文章

最新文章