一、property
1. 函数的作用
在接触python时最开始接触的代码,取长方形的长和宽,定义一个长方形类,然后设置长方形的长宽属性,通过实例化的方式调用长和宽,像如下代码一样。
class Rectangle(object):
def __init__(self):
self.width =10
self.height=20
r=Rectangle()
print(r.width,r.height)
此时输出结果为10 20
但是这样在实际使用中会产生一个严重的问题,__init__
中定义的属性是可变的,换句话说,是使用一个系统的所有开发人员在知道属性名的情况下,可以进行随意的更改(尽管可能是在无意识的情况下),但这很容易造成严重的后果。
对于这种情况,我们可以使用python的内置函数property来解决。
property 可以用于以下场景:
将方法定义为属性
: 例如,您可以将一个计算属性定义为只读属性,以便其他代码可以访问该属性的值,但不能修改该属性的值。控制属性的访问
: 例如,您可以将一个密码属性定义为只写属性,以便其他代码只能设置该属性的值,但不能读取该属性的值。验证属性值
: 例如,您可以将一个年龄属性定义为只写属性,并验证该属性的值必须大于 0。缓存属性值
: 例如,您可以将一个数据库连接属性定义为只读属性,并缓存该属性的值,以避免重复创建数据库连接。删除属性值
2. property函数
在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:
s = Student()
s.score = 9999
这显然不合逻辑,任何人都可以对该变量进行修改,且修改的内容不受限制。
为了限制score的范围,可以通过一个set_score()
方法来设置成绩,再通过一个get_score()
来获取成绩,这样,在set_score()方法里,就可以检查参数:
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
现在,对任意的Student实例进行操作,就不能随心所欲地设置score了:
>>> s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。
有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的Python程序员来说,这是必须要做到的!
还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
@property
的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property
就可以了,此时,@property
本身又创建了另一个装饰器@score.setter
(注意:setter是score的方法),负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
注意到这个神奇的@property
,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter
和setter
方法来实现的。
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@birth.deleter
def birth(self):
del self._birth
@property
def age(self):
return 2015 - self._birth
上面的birth是可读写删除属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。
要特别注意:属性的方法名不要和实例变量重名。例如,以下的代码是错误的:
class Student(object):
# 方法名称和实例变量均为birth:
@property
def birth(self):
return self.birth
#将self.birth修改为self._birth,或者修改def birth 为 def birthday
这是因为调用s.birth
时,首先转换为方法调用,在执行return self.birth
时,又视为访问self的属性,于是又转换为方法调用self.birth()
,造成无限递归,最终导致栈溢出报错RecursionError。
二、functools.lru_cache
1. 函数的作用
functools.lru_cache()
可以用于以下场景:
- 计算密集型函数: 对于需要进行大量计算的函数,使用 functools.lru_cache() 可以缓存计算结果,减少重复计算,提高效率。
- 频繁调用的函数: 对于频繁调用的函数,使用 functools.lru_cache() 可以缓存函数的返回值,减少重复调用,提高效率。
- 需要避免重复计算的函数: 对于需要避免重复计算的函数,使用 functools.lru_cache() 可以缓存函数的返回值,避免重复计算。
以下是一些使用 functools.lru_cache() 的示例:
- 计算斐波那契数列: 计算斐波那契数列需要进行大量的计算,使用 functools.lru_cache() 可以缓存计算结果,减少重复计算,提高效率。
- 数据库查询: 数据库查询通常需要进行大量的 I/O 操作,使用 functools.lru_cache() 可以缓存查询结果,减少重复查询,提高效率。
- 文件读取: 文件读取通常需要进行大量的 I/O 操作,使用 functools.lru_cache() 可以缓存读取结果,减少重复读取,提高效率。
2. 函数的使用
@functools.lru_cache(maxsize=128, typed=False)
一个为函数提供缓存功能
的装饰器,缓存 maxsize
组传入参数,在下次以相同参数调用时直接返回上一次的结果。用以节约高开销或I/O函数的调用时间。
由于使用了字典存储缓存,所以该函数的固定参数和关键字参数必须是可哈希的。
不同模式的参数可能被视为不同从而产生多个缓存项,例如, f(a=1, b=2) 和 f(b=2, a=1) 因其参数顺序不同,可能会被缓存两次。
如果指定了 user_function
,它必须是一个可调用对象。 这允许 lru_cache 装饰器被直接应用于一个用户自定义函数,让 maxsize 保持其默认值 128
:
@lru_cache
def count_vowels(sentence):
sentence = sentence.casefold()
return sum(sentence.count(vowel) for vowel in 'aeiou')
-
如果 maxsize 设为 None,LRU 特性将被禁用且缓存可无限增长。
-
如果 typed 设置为true,不同类型的函数参数将被分别缓存。例如, f(3) 和 f(3.0) 将被视为不同而分别缓存。
为了衡量缓存的有效性以便调整 maxsize 形参,被装饰的函数带有一个 cache_info() 函数。当调用 cache_info() 函数时,返回一个具名元组,包含命中次数 hits,未命中次数 misses ,最大缓存数量 maxsize 和 当前缓存大小 currsize。在多线程环境中,命中数与未命中数是不完全准确的。
该装饰器也提供了一个用于清理/使缓存失效的函数 cache_clear()
。
原始的未经装饰的函数可以通过 __wrapped__
属性访问。它可以用于检查、绕过缓存,或使用不同的缓存再次装饰原始函数。
“最久未使用算法”(LRU)缓存 在“最近的调用是即将到来的调用的最佳预测因子”时性能最好(比如,新闻服务器上最受欢迎的文章倾向于每天更改)。 “缓存大小限制”参数保证缓存不会在长时间运行的进程比如说网站服务器上无限制的增加自身的大小。
静态 Web 内容的 LRU 缓存示例:
@lru_cache(maxsize=32)
def get_pep(num):
'Retrieve text of a Python Enhancement Proposal'
resource = 'http://www.python.org/dev/peps/pep-%04d/' % num
try:
with urllib.request.urlopen(resource) as s:
return s.read()
except urllib.error.HTTPError:
return 'Not Found'
>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
... pep = get_pep(n)
... print(n, len(pep))
>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
以下是使用缓存通过 动态规划 计算 斐波那契数列 的例子。
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)```