继续今天的python学习,记录课堂重点。
'''
我们说到python是一门动态语言,可以在程序运行的过程中给类添加属性和方法,但我们有时候想限制它,让它
和其他静态语言一样,我们就要用到__slots__这样一个属性
'''
# 举例:
class Person(object):
__slots__ = ("name") # __slots__限制我们只能添加名字为name的属性,在添加其他属性时会报错
p1 = Person()
p1.name = "Jany"
print(p1.name)
p1.age = 19
print(p1.age)
运行结果:
Traceback (most recent call last):
Jany
File "C:/PycharmProjects/第二章/07___slots__的用法.py", line 14, in <module>
p1.age = 19
AttributeError: 'Person' object has no attribute 'age'
可以看到,我们无法添加age这一属性。
接下来谈谈生成器
我们在存储数据的时候难免会遇到存储大量数据的时候,为了避免大量数据占用过多的内存空间,python中的生成器就可以解决这样的问题。
生成器有两种生成的方法,第一种就是将列表表达式中的“方括号”变为“圆括号”。
a = [i*2 for i in range(10)]
print(a)
运行结果:
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
从结果可以看出,python中已经生成了有10个元素的列表,那么如果我们将10换为10000000会是什么样的结果呢?
读者自己测试(会很卡),那么在这10000000个元素当中,我们并不是每次都要用这么多的数据,浪费了大量的内存空间,于是我们用到了生成器。
a = (i*2 for i in range(10))
print(a)
运行结果:
<generator object <genexpr> at 0x000002420EFF2F48>
从结果可以看出,我们只是创建了一个生成器对象,在我们使用的时候才会变成我们想要的数据,那么我们究竟要如何把生成的数据给取出来呢?
答案就是用next()函数
print(next(a))
print(next(a))
print(next(a))
运行结果:
0
2
4
可以发现每一次调用next()函数都会取出一个数据,其实并不是取出数据,而是每一次调用时生成器会帮我们按照表达式来生成这个数据,这样就避免了内存空间占用过大的问题。
创建生成器的第二种方法是使用“yield”关键词
def test():
for i in range(100):
yield i
a = test()
print(a)
print(next(a))
print(next(a))
print(next(a))
运行结果:
<generator object test at 0x000002549A7C2F48>
0
1
2
注:当函数中有“yield”关键词的时候,此函数不是一个普通的函数,而是一个生成器。
利用__next__()方法也能达到同样的效果:
print(a.__next__())
print(a.__next__())
print(a.__next__())
运行结果:
0
1
2
再来看下面的例子:
def test():
i = 0
while i < 5:
temp = yield i
print(temp)
i += 1
我们定义一个这样的函数,把yield i 的值赋给temp,然后打印出来会是什么样的结果呢?
a = test()
print(a.__next__())
print(a.__next__())
print(a.__next__())
运行结果:
0
None
1
None
2
为什么打印的是None?
当程序运行到yield i 的时候会自动暂停,然后接下来再运行的时候yield i是没有任何值的,所以只是将None赋值给了temp,为了解决这个问题我们引入了一个新的生成器方法:send()
print(a.__next__())
print(a.send("caca"))
print(a.send("wawa"))
print(a.send("gaga"))
运行结果:
0
caca
1
wawa
2
gaga
3
注意:第一次一定不能给生成器传值,否则会报错,读者不妨试试。
第一次除了用__next()__方法外还可以使用send(None)方法实现,send()方法的括号中需要一个参数,所以在这里我们必须要传入None
接下来再看一个例子:
print(a.send(None))
print(a.send("caca"))
print(a.__next__())
print(a.send("wawa"))
print(a.__next__())
运行结果:
0
caca
1
None
2
wawa
3
None
4
当我们在两个send()之间使用__next__()时,temp的值又变成了None,也就是说temp的值并没有被保存。
分析一下,temp在第一次执行yield i之后才开始接受yield i的值,也就是第二行的print(send("caca"))事实上是把“caca”赋值给了上一次的yield i然后再赋值给temp,而本次再执行到yield i时其值又变成了None,所以在下一次调用__next()__时,前一次 yield i 的值就是None
那么我们要如何永久性的保存temp的值呢?这里要将上面定义的函数做一个小小的改动:
def test():
i = 0
while i < 5:
if i == 0:
temp = yield i
print(temp)
else:
yield i
print(temp)
i += 1
我们只给temp赋值一次,接下来我们只执行 yield i
a = test()
print(a.send(None))
print(a.send("caca"))
print(a.__next__())
print(a.send("wawa"))
print(a.__next__())
运行结果:
0
caca
1
caca
2
caca
3
caca
4
这里我们也会发现一点,就是我们在执行第四次print的时候,“wawa”并没有被传入,temp的值永久性的保存了。
接下来看一下生成器如何实现完成多任务,原理就是多个生成器暂停然后再开启
def test1():
while True:
print("----1----")
yield
def test2():
while True:
print("----2----")
yield
t1 = test1()
t2 = test2()
while True:
t1.__next__()
t2.__next__()
运行结果:
请读者自行运行。。。
昨天学习了用函数来当装饰器装饰其他函数,接下来谈一下类当做装饰器。
class Test:
def __init__(self,func):
print("装饰器开始装饰...")
print("The function name is %s" % func.__name__)
self.__func = func
def __call__(self):
print("函数开始调用...")
self.__func()
@Test
def test():
print("函数已被调用...")
test()
运行结果:
装饰器开始装饰...
The function name is test
函数开始调用...
函数已被调用...
如果去掉test()这一行,就会打印出结果的前两行,说明装饰器在函数定义的时候就已经把函数给装饰了,其中@Test这一行相当于 test = Test(test) ,也就是说test指向了Test的实例对象,而其中的self.__func指向了原来的test()函数,所以在执行test()时会调用到__call__方法。