python进阶篇(一)

深拷贝与浅拷贝

关于缓存重用规则,具体可看下表吗, 比如同一个字符串,如果之前创建过,那么后面所有索引都是指向同一个缓存

然后关于深浅拷贝,共同点是都创建了一个新的对象,但是如果对元组只能采用copy.copy如下


l1 = [1, 2, 3]
l2 = l1[:]

l1 == l2
True
l1 is l2
False
# 对于元组不适用,索引指向的是同一块地址
t1 = (1, 2, 3)
t2 = tuple(t1)

t1 == t2
True
t1 is t2
True

另外关于循环引用的问题。因为深度拷贝是采用递归的方式,如果涉及到循环引用就会导致栈溢出。所以深度拷贝函数 deepcopy 中会维护一个字典,记录已经拷贝的对象与其 ID。拷贝过程中,如果字典里已经存储了将要拷贝的对象,则会从字典直接返回

赋值与传参

关于变量指向某个对象,这种指向就类似于指针指向,如果对象是不可变的,那么当这个不可变对象尝试“改变”的时候,因为其不可变性,python会开辟一个新空间来重新指向。而如果是可变对象,直接就可以在原地更新,所以指向并没有发生改变。需要注意的是,Python 里的变量可以被删除,但是对象无法被删除。del l 删除了 l 这个变量,从此以后你无法访问 l,但是对象[1, 2, 3]仍然存在。Python 程序运行时,其自带的垃圾回收系统会跟踪每个对象的引用。如果[1, 2, 3]除了 l 外,还在其他地方被引用,那就不会被回收,反之则会被回收。


l = [1, 2, 3]
del l

关于python的函数参数传递,实质是对象传递,就是把整个对象的索引传进去,但是形参的值只是一个copy值,这一点要注意。

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]

装饰器

python中一切皆是对象,所以函数其实也是对象(funtion obj)。闭包,是指内外层函数嵌套之间的作用域,也叫闭包作用域。闭包意义在于把闭包作用域与内层函数绑定在一起。如下面的例子。

装饰器其实就是闭包的语法糖,让我们的代码看上去更加优雅与简洁。实现闭包的方式有以下两种。第一种是用函数实现,带参数的装饰器。

我们可以对比一下语法糖, greet = repeat(4)(greet) 与之间在greet函数上面加@, 明显后者更加简洁优雅。

# 带有参数的装饰器实现
def repeat(num):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            # 自定义参数可以使得装饰器更加灵活
            for i in range(num):
                print('wrapper of decorator')
                func(*args, **kwargs)
    # 返回的一定是funtion obj
        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

第二种是用类来实现装饰器,也叫类装饰器。 调用流程如下, example = Count( example ), 得到的其实是类实例,以后每次调用example函数,其实都是调用类实例,所以要实现call, 并且在call里面去调用真正调用的函数。这一点跟上面实现的装饰器不一样的是,类装饰器是直接在call魔法函数里面完成调用,而不是返回funtion obj。


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

...

从上面的例子我们可以看出,装饰器的特点其实就是让我们可以在需求变动的时候,尽量不用修改原有的函数。在实际工作中,如果你怀疑某些函数的耗时过长,导致整个系统的 latency(延迟)增加,所以想在线上测试某些函数的执行时间,那么,装饰器就是一种很常用的手段。


import time
import functools

def log_execution_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        res = func(*args, **kwargs)
        end = time.perf_counter()
        print('{} took {} ms'.format(func.__name__, (end - start) * 1000))
        return res
    return wrapper
    
@log_execution_time
def calculate_similarity(items):
    ...

这里,装饰器 log_execution_time 记录某个函数的运行时间,并返回其执行结果。如果你想计算任何函数的执行时间,在这个函数上方加上@log_execution_time即可。 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值