《Python核心技术与实战》笔记2

2020/12/19 09:15
阅读数 75

Python对象的比较、拷贝

'==' VS 'is'

  • '=='操作符比较对象之间的值是否相等

  • 'is'操作符比较的是对象的身份标识是否相等,即它们是否是同一个对象,是否指向同一个内存地址

  • 每个对象的身份标识,都能通过函数 id(object) 获得。因此,'is'操作符,相当于比较对象之间的 ID 是否相等

  • a = 10
    b = 10 
    a == b
    True
    id(a)
    4427562448
    id(b)
    4427562448
    a is b
    True
    
  • 需要注意,对于整型数字来说,以上a is b为 True 的结论,只适用于 -5 到 256 范围内的数字

    • 出于对性能优化的考虑,Python 内部会对 -5 到 256 的整型维持一个数组,起到一个缓存的作用。这样,每次你试图创建一个 -5 到 256 范围内的整型数字时,Python 都会从这个数组中返回相对应的引用,而不是重新开辟一块新的内存空间
  • 比较操作符'is'的速度效率,通常要优于'=='。因为'is'操作符不能被重载

浅拷贝和深度拷贝

copy.copy()

  • 对于可变序列,通过:完成浅拷贝

  • 需要注意的是,对于元组,使用 tuple() 或者切片操作符':'不会创建一份浅拷贝,相反,它会返回一个指向相同元组的引用

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

  • 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)]
    

copy.deepcopy()

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)]
  • 深度拷贝也不是完美的,往往也会带来一系列问题。如果被拷贝对象中存在指向自身的引用,那么程序很容易陷入无限循环:

  • import copy
    x = [1]
    x.append(x)
     
    x
    [1, [...]]
     
    y = copy.deepcopy(x)
    y
    [1, [...]]
    

值传递,引用传递or其他

重要

Python 变量及其赋值

  • Python 的数据类型,例如整型(int)、字符串(string)、元组等等,是不可变的。所以,a = a + 1,并不是让 a 的值增加 1,而是表示重新创建了一个新的值为 2 的对象,并让 a 指向它。

  • 需要注意的是,Python 里的变量可以被删除,但是对象无法被删除。比如下面的代码:

    • l = [1, 2, 3]
      del l
      
    • del l 删除了 l 这个变量,从此以后你无法访问 l,但是对象 [1, 2, 3] 仍然存在。Python 程序运行时,其自带的垃圾回收系统会跟踪每个对象的引用。如果 [1, 2, 3] 除了 l 外,还在其他地方被引用,那就不会被回收,反之则会被回收。

    • 变量的赋值,只是表示让变量指向了某个对象,并不表示拷贝对象给变量;而一个对象,可以被多个变量所指向。
    • 可变对象(列表,字典,集合等等)的改变,会影响所有指向该对象的变量。
    • 对于不可变对象(字符串,整型,元祖等等),所有指向该对象的变量的值总是一样的,也不会改变。但是通过某些操作(+= 等等)更新不可变对象的值时,会返回一个新的对象。
    • 变量可以被删除,但是对象无法被删除。

Python 函数的参数传递

“Remember that arguments are passed by assignment in Python. Since assignment just creates references to objects, there’s no alias between an argument name in the caller and callee, and so no call-by-reference per Se.”

Python 的参数传递是赋值传递 (pass by assignment),或者叫作对象的引用传递(pass by object reference)。Python 里所有的数据类型都是对象,所以参数传递时,只是让新变量与原变量指向相同的对象而已,并不存在值传递或是引用传递一说。

def my_func1(b):
	b = 2
 
a = 1
my_func1(a)
a
1

def my_func2(b):
	b = 2
	return b
 
a = 1
a = my_func2(a)
a
2

def my_func3(l2):
	l2.append(4)
 
l1 = [1, 2, 3]
my_func3(l1)
l1
[1, 2, 3, 4]

def my_func4(l2):
	l2 = l2 + [4]
 
l1 = [1, 2, 3]
my_func4(l1)
l1
[1, 2, 3]

def my_func5(l2):
	l2 = l2 + [4]
	return l2
 
l1 = [1, 2, 3]
l1 = my_func5(l1)
l1
[1, 2, 3, 4]

装饰器

#简单
def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper
 
@my_decorator
def greet():
    print('hello world')
 
greet()

# 带参数
def my_decorator(func):
    def wrapper(message):
        print('wrapper of decorator')
        func(message)
    return wrapper
 
@my_decorator
def greet(message):
    print(message)
 
greet('hello world')
 
# 输出
wrapper of decorator
hello world

# 通常情况下,我们会把*args和**kwargs,作为装饰器内部函数 wrapper() 的参数。*args和**kwargs,表示接受任意数量和类型的参数
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'

装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。

Decorators is to modify the behavior of the function through a wrapper so we don’t have to actually modify the function.

而实际工作中,装饰器通常运用在身份认证、日志记录、输入合理性检查以及缓存等多个领域中。

深入理解迭代器和生成器

列表(list: [0, 1, 2]),元组(tuple: (0, 1, 2)),字典(dict: {0:0, 1:1, 2:2}),集合(set: set([0, 1, 2]))都是容器。所有的容器都是可迭代的(iterable)。

迭代器(iterator)提供了一个 next 的方法。调用这个方法后,你要么得到这个容器的下一个对象,要么得到一个 StopIteration 的错误。

可迭代对象,通过 iter() 函数返回一个迭代器,再通过 next() 函数就可以实现遍历。for in 语句将这个过程隐式化。

协程

并发Futures 和 Asyncio

  • 并发,通过线程和任务之间互相切换的方式实现,但同一时刻,只允许有一个线程或任务执行。
  • 而并行,则是指多个进程完全同步同时的执行。

GIL(全局解释器锁Global Interpreter Lock)

  • 每一个 Python 线程,在 CPython 解释器中执行时,都会先锁住自己的线程,阻止别的线程执行。

  • CPython 引进 GIL 其实主要就是这么两个原因:

    • 一是设计者为了规避类似于内存管理这样的复杂的竞争风险问题(race condition);
    • 二是因为 CPython 大量使用 C 语言库,但大部分 C 语言库都不是原生线程安全的(线程安全会降低性能和增加复杂度)。
  • GIL 的设计,主要是为了方便 CPython 解释器层面的编写者,而不是 Python 应用层面的程序员。作为 Python 的使用者,我们还是需要 lock 等工具,来确保线程安全。

  • n = 0
    lock = threading.Lock()
     
    def foo():
        global n
        with lock:
            n += 1
    

垃圾回收机制

Python 中一切皆对象;

垃圾回收是 Python 自带的机制,用于自动释放不会再用到的内存空间;

import sys
a = []
 
# 两次引用,一次来自 a,一次来自 getrefcount
print(sys.getrefcount(a))
 
def func(a):
    # 四次引用,a,python 的函数调用栈,函数参数,和 getrefcount
    print(sys.getrefcount(a))
 
func(a)
 
# 两次引用,一次来自 a,一次来自 getrefcount,函数 func 调用已经不存在
print(sys.getrefcount(a))
 
########## 输出 ##########
 
2
4
2
# 手动回收
import gc
a = []
del a
gc.collect()
  • Python 使用标记清除(mark-sweep)算法和分代收集(generational),来启用针对循环引用的自动垃圾回收;
  • 引用计数是其中最简单的实现,不过切记,这只是充分非必要条件,因为循环引用需要通过不可达判定,来确定是否可以回收;
  • 调试内存泄漏方面, objgraph 是很好的可视化分析工具;

加餐

装饰器的作用与意义,在于其可以通过自定义的函数或类,在不改变原函数的基础上,改变原函数的一些功能。

Decorators is to modify the behavior of the function through a wrapper so we don’t have to actually modify the function.

  • 代码更加简洁;
  • 逻辑更加清晰;
  • 程序的层次化、分离化更加明显。
展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部