Python进阶 - 2

关于装饰器

首先,一切皆对象,函数也是对象,可以在函数内定义函数,在函数中return函数,可以将函数作为参数传递。

另外,可以用装饰器(decorator)装饰函数,此前写过装饰器的文章,这里不错一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from functools import wraps

def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction

@a_new_decorator
def a_function_requiring_decoration():
print("I am the function which needs some decoration to "

print(a_function_requiring_decoration.__name__)
# Output: a_function_requiring_decoration

使用这个库的wraps,可以修改name的属性为被装饰的函数,不然默认是为实现装饰的内部函数的函数名的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated

@decorator_name
def func():
return("Function is running")

can_run = True
print(func())
# Output: Function is running

can_run = False
print(func())
# Output: Function will not run

注意:@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。

多在授权、日志等方面可以使用装饰器,或者经常看到的计算函数执行时间的装饰器应用。

带参数的装饰器以及在函数中嵌入装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from functools import wraps

def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打开logfile,并写入内容
with open(logfile, 'a') as opened_file:
# 现在将日志打到指定的logfile
opened_file.write(log_string + '\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator

@logit()
def myfunc1():
pass

myfunc1()
# Output: myfunc1 was called
# 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串

@logit(logfile='func2.log')
def myfunc2():
pass

myfunc2()
# Output: myfunc2 was called
# 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串

关键在于嵌套。

另外,装饰器可以用类实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from functools import wraps

class logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile

def __call__(self, func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打开logfile并写入
with open(self.logfile, 'a') as opened_file:
# 现在将日志打到指定的文件
opened_file.write(log_string + '\n')
# 现在,发送一个通知
self.notify()
return func(*args, **kwargs)
return wrapped_function

def notify(self):
# logit只打日志,不做别的
pass

用法一致。

1
2
3
@logit()
def myfunc1():
pass

但是看起来好像更简洁~

可变和不可变类型的延伸

1
2
3
4
5
6
7
8
9
10
11
12
def add_to(num, target=[]):
target.append(num)
return target

add_to(1)
# Output: [1]

add_to(2)
# Output: [1, 2]

add_to(3)
# Output: [1, 2, 3]

可变的参数导致这个问题,多次调用此函数,target变量保存了之前的值。处理此问题的方法:

1
2
3
4
5
def add_to(element, target=None):
if target is None:
target = []
target.append(element)
return target

None通常是个好选择。

在Python中当函数被定义时,默认参数只会运算一次,而不是每次被调用时都会重新运算。你应该永远不要定义可变类型的默认参数,除非你知道你正在做什么。

另一个常见的困惑是Python在闭包(或在周围全局作用域(surrounding global scope))中 绑定变量的方式。

1
2
3
4
5
6
7
8
9
10
11
12
def create_multipliers():
return [lambda x : i * x for i in range(5)]

for multiplier in create_multipliers():
print(multiplier(2))

# 输出
8
8
8
8
8

原因在于,Python的闭包是迟绑定。这意味着闭包中用到的变量的值,是在内部函数被调用时查询得到的。这里不论任何返回的函数是如何被调用的,i的值是调用时在周围作用域中查询到的。接着,循环完成,i的值最终变成了4。

最一般的解决方案可以说是有点取巧(hack)。由于Python拥有在前文提到的为函数默认参数 赋值的行为(参见 可变默认参数 ),您可以创建一个立即绑定参数的闭包,像下面这样:

1
2
def create_multipliers():
return [lambda x, i=i : i * x for i in range(5)]

有时您就想要闭包有如此表现,迟绑定在很多情况下是不错的。不幸的是,循环创建独特的函数是一种会使它们出差错的情况.

如何删除当前目录下所有的.pyc字节码文件

1
find . -type f -name "*.py[co]" -delete -or -type d -name "__pycache__" -delete

关于slots

在Python中,每个类都有实例属性。默认情况下Python用一个字典来保存一个对象的实例属性。这非常有用,因为它允许我们在运行时去设置任意的新属性。
然而,对于有着已知属性的小类来说,它可能是个瓶颈。这个字典浪费了很多内存。Python不能在对象创建时直接分配一个固定量的内存来保存所有的属性。因此如果你创建许多对象(我指的是成千上万个),它会消耗掉很多内存。
不过还是有一个方法来规避这个问题。这个方法需要使用slots来告诉Python不要使用字典,而且只给一个固定集合的属性分配空间。