Day4 PythonWeb全栈课程课堂内容
1. __getattr__和__getattribute__魔法函数
- __getattr__是当类调用一个不存在的属性时才会调用getattr魔法函数,他传入的值item就是你这个调用的不存在的属性。
- __getattribute__则是无条件的优先执行,所以如果不是特殊情况最好不要用__getattribute__。
from datetime import date
class User:
def __init__(self, name, birthday):
self.name = name
self.birthday = birthday
if __name__ == '__main__':
user = User('Sam', date(year=2020, month=12, day=20))
print(user.name) # Sam
print(user.birthday) # 2020-12-20
print(user.age)
'''
print(user.age)
AttributeError: 'User' object has no attribute 'age'
没有你要调取的属性。
'''
- 使用__getattr__之后。
from datetime import date
class User:
def __init__(self, name, birthday):
self.name = name
self.birthday = birthday
def __getattr__(self, item):
print(item)
return 'atter not find'
if __name__ == '__main__':
user = User('Sam', date(year=2020, month=12, day=20))
print(user.name) # Sam
print(user.birthday) # 2020-12-20
print(user.age)
'''
age
atter not find
'''
- __getattribute__的优先级高于__getatter__和__init__,所以建议不要使用__getattribute__魔法函数。
from datetime import date
class User:
def __init__(self, name, birthday):
self.name = name
self.birthday = birthday
def __getattr__(self, item):
print(item)
return 'atter not find'
def __getattribute__(self, item):
return 'Janice'
if __name__ == '__main__':
user = User('Sam', date(year=2020, month=12, day=20))
print(user.name)
print(user.birthday)
print(user.age)
'''
Janice
Janice
Janice
'''
2. 属性描述符
- 属性描述符介绍
- 属性描述符是一个强大的通用协议。它是properties, methods, static methods, class methods 和super()的调用原理。
class User:
def __init__(self, age):
self.age = age
def get_age(self):
return (str(self.age) + '岁')
def set_age(self, age):
if not isinstance(age, int):
raise TypeError('Type Error')
self.age = age
user = User(18)
print(user.age)
print(user.set_age(17))
print(user.get_age())
'''
18
None
17岁
'''
- 很明显set_age方法,主要是用于检测self.age这个属性。
- 如果在类中含有相当多的属性时,需要检测多个属性,同样需要用到多个set_age()这样的方法去调用,造成了很大的麻烦。
- 所以我们用了以下方法。
class IntFielf(object):
'''
实现 __get__ __set__ __delete__ 实现任意一个方法就可以被称为属性描述符
'''
# __get__ 获得值,取值
def __get__(self, instance, owner):
# 取值的时候
print('__get__')
# print(instance) # <__main__.User object at 0x00000000022AAEF0>
# print(owner) # <class '__main__.User'>
return self.age
# __set__ 设置值,赋值
def __set__(self, instance, value):
# 给属性赋值的时候
print('__set__')
# print(instance) # <__main__.User object at 0x000000000296A7B8>
# print(value) # 30
# 判断整型
if not isinstance(value, int):
raise TypeError('Type Error')
self.age = value
class NoneDataIntField():
# 非属性描述符,只能获取
def __get__(self, instance, owner):
return self.owner
class User:
age = IntFielf() # 类的实例化,属性描述符,实现IntFielf其中的两个方法。
id_card = IntFielf()
user = User()
user.age = 30 # __set__
user.age # __get__
# user.age = '30'
'''
Traceback (most recent call last):
File "D:/Python/venv/Python-web全栈开发课堂练习文件/Day4/属性描述符.py", line 54, in <module>
user.age = '30'
File "D:/Python/venv/Python-web全栈开发课堂练习文件/Day4/属性描述符.py", line 44, in __set__
raise TypeError('Type Error')
TypeError: Type Error
'''
-
属性描述符协议
- 属性描述符是实现了特定协议的类,只要实现了
__get__
,__set__
和__delete__
三个方法中的任意一个,这个类就是描述符,它能实现对多个属性运用相同存取逻辑的一种方式,通俗来说就是:创建一个实例,作为另一个类的类属性。
- 属性描述符是实现了特定协议的类,只要实现了
-
注意
- 如果一个对象同时定义了和__set__方法,它被称做数据描述符(data descriptor)。
- 只定义__get__方法的对象则被称为非数据描述符(non-data descriptor)。
-
使用类方法创建描述符
- 定义一个IntField类为描述符类
- 创建IntField类的实例,作为另一个User类的属性
class IntField(object): def __set__(self, instance, value): print("__set__") def __get__(self, instance, owner): print("__get__") def __delete__(self, instance): print("__delete__") class User(object): age = IntField() ls = User() ls.age ls.age = 30 del ls.age
-
使用属性类型创建描述符
- 除了使用类当作一个属性描述符,我们之前学习的 property(),就是可以轻松地为任意属性创建可用的描述符。创建 property() 的语法是 property(fget=None, fset=None, fdel=None, doc=None)
-
描述符查找顺序
- 当为数据描述符时,
__get__
优先级高于__dict__
- 当为非数据描述符时,
__dict__
优先级高于__get__
- 当为数据描述符时,
3. 元类
-
元类介绍
- 元类实际上就是创建类的类
-
实现如下:
- 定义创建类的函数
create_class
- 如果给
create_class
传的参数为user
,则创建User
类
- 定义创建类的函数
def creat_class(name):
if name == 'user':
class User:
def __str__(self):
return 'user'
return User
elif name == 'student':
class Student:
def __str__(self):
return 'student'
return Student
myclass = creat_class('user')
user = myclass()
print(user)
print(type(user))
'''
user
<class '__main__.creat_class.<locals>.User'>
'''
# 动态创建类,方法比较麻烦
-
type()创建元类
- type(name, bases, attr) -> a new type
- 第一个参数:name表示类名称,字符串类型
- 第二个参数:bases表示继承对象(父类),元组类型,单元素使用逗号
- 第三个参数:attr表示属性,这里可以填写类属性、类方式、静态方法,采用字典格式,key为属性名,value为属性值
# 最基本的属性 User = type("User", (), {'name': 'Sam', 'info': info}) obj = User() # print(obj) # <__main__.User object at 0x0000000009FD6E10> obj是一个实例对象 # print(type(obj)) # <class '__main__.User'>
# 继承类 class BaseClass(object): def demo(self): return "base class" # 方法 def info(self): return self.name # 魔法方法 def __str__(self): return "__str__" User = type("User", (BaseClass,), {'name': 'Sam', 'info': info, '__str__': __str__}) obj = User() print(obj) print(obj.name) print(obj.info()) print(obj.demo())
4. metaclass属性
-
metaclass的英文直译过来就是元类,这既是一个概念也可以认为是Python当中的一个关键字,不管怎么理解,对它的内核含义并没有什么影响。我们可以不必纠结,就认为它是类的类的意思即可。在这个用法当中,支持我们自己定义一个类,使得它是后面某一个类的元类。
-
之前使用type动态创建类的时候,我们传入了类名,和父类的tuple以及属性的dict。在metaclass用法当中,其实核心相差不大,只是表现形式有所区别。我们来看一个例子即可:
class AddInfo(type): # 定义一个元类
def __new__(cls, name, bases, attr): # 和type()的属性一模一样
attr['info'] = 'add by metaclass'
return super().__new__(cls, name, bases, attr)
class Test(metaclass=AddInfo):
pass
test = Test()
print(test.info) # add by metaclass
print(Test.info) # add by metaclass
-
在这个例子当中,我们首先创建了一个类叫做AddInfo,这是我们定义的一个元类。由于我们希望通过它来实现元类的功能,所以我们需要它继承type类。我们在之前的文章当中说过,在Python面向对象当中,所有的类的根本来源就是type。也就是说Python当中的每一个类都是type的实例。
-
我们在这个类当中重载了__new__方法,我们在__new__方法当中传入了四个参数。眼尖一点的小伙伴一定已经看出来了,这个函数的四个参数,正是我们调用type创建类的时候传入的参数。其实我们调用type的方法来创建类的时候,就是调用的__new__这个函数完成的,这两种写法对应的逻辑是完全一样的。
-
我们之后又创建了一个新的类叫做Test,这个当中没有任何逻辑,直接pass。但是我们在创建类的时候指定了一个参数metaclass=AddInfo,这里这个参数其实就是指定的这个类的元类,也就是指定这个类的创建逻辑。虽然我们用代码写了类的定义,但是在实际执行的时候,这个类是以metaclass为元类创建的。
-
扩展类功能
-
上面这段就是元类的基本用法了,其实本质上和我们之前介绍的type的动态类创建是一样的,只不过展现的形式不同。那么我们就有一个问题要问了,我们使用元类究竟能够做什么呢?
-
这里有一个经典的例子,我们都知道Python原生的list是没有’add’这个方法的。假设我们习惯了Java当中list的使用,习惯用add来为它添加元素。我们希望创建一个新的类,在这个新的类当中,我们可以通过add来添加函数。通过元类可以很方便地使用这一点。
class ListMeta(type): def __new__(cls, name, bases, attrs): # 在类属性当中添加了add函数 # 通过匿名函数映射到append函数上 attrs['add'] = lambda self, value: self.append(value) return super().__new__(cls, name, bases, attrs) class MyList(list, metaclass=ListMeta): pass li = MyList() # li = [] li.add(3) print(li) # [3]
-
控制实例的创建
-
我们创建了三种游戏的类和一个工厂类,我们重载了工厂类的
__new__
函数。使得我们可以根据实例化时传入的参数返回不同类型的实例。
class Last_of_us:
def play(self):
print('the Last Of Us is really funny')
class Uncharted:
def play(self):
print('the Uncharted is really funny')
class PSGame:
def play(self):
print('PS has many games')
class GameFactory:
games = {'last_of_us': Last_of_us, 'uncharted': Uncharted}
def __new__(cls, name):
if name in cls.games:
return cls.games[name]()
else:
return PSGame()
uncharted = GameFactory('uncharted')
last_of_us = GameFactory('last_of_us')
-
假设这个需求完成得很好顺利上线了,但是运行了一段时间之后我们发现下游有的时候为了偷懒会不通过工厂类来创建实例,而是直接对需要的类做实例化。原本这没有问题,但是现在产品想要在工厂类当中加上一些埋点,统计出访问我们工厂的访问量。所以我们需要限制这些游戏类不能直接实例化,必须要通过工厂返回实例。
-
那么这个功能我们怎么实现呢?
-
我们分析一下问题就会发现,这一次不是需要我们在创建实例的时候做动态的添加,而是直接限制一些类不允许直接调用进行创建。限制的方法比较常用的一种就是抛出异常,所以我们希望可以给这些类加上一个逻辑,实例化类的时候传入一个参数,表明是否是通过工厂类进行的,如果不是,则抛出异常。
-
这里,我们需要用到另外一个默认函数,叫做
__call__
,它是允许将类实例当做函数调用。我们通过类名来实例化,其实也是一个调用逻辑。这个__call__
的逻辑并不难写,我们随手就来:class Demo(object): def __call__(self, *args, **kwargs): return 'avb' d = Demo() print(d())
class NoInstance(type): def __call__(self, *args, **kwargs): if len(args) == 0 or args[0] != 'factory': raise TypeError("Can't instantiate directly") class Last_of_us(metaclass=NoInstance): def play(self): print('the Last Of Us is really funny') class Uncharted(metaclass=NoInstance): def play(self): print('the Uncharted is really funny') class PSGame(metaclass=NoInstance): def play(self): print('PS has many games') class GameFactory: games = {'last_of_us': Last_of_us, 'uncharted': Uncharted} def __new__(cls, name): if name in cls.games: return cls.games[name]('factory') else: return PSGame()
5. Python迭代器
# 迭代:通过for循环,循环一个可循环的对象
# 可迭代对象 Iterable list, tuple, set, dict
from collections import Iterable, Iterator
# 范围 定义 __iter__方法
print(isinstance(list(), Iterable)) # True
# 迭代器
# 可以作用于next()hanshu iter()
print(isinstance(list(), Iterator)) # False
li = [1, 2, 3, 4]
'''
print(next(li))
Traceback (most recent call last):
File "D:/Python/venv/Python-web全栈开发课堂练习文件/Day4/Python迭代器.py", line 20, in <module>
print(next(li))
TypeError: 'list' object is not an iterator
列表不是迭代器
'''
# 将列表变成迭代器
it = li.__iter__()
print(it) # <list_iterator object at 0x000000000217DE10>
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
print(next(it)) # 4
'''
print(next(it)) 迭代结束
Traceback (most recent call last):
File "D:/Python/venv/Python-web全栈开发课堂练习文件/Day4/Python迭代器.py", line 30, in <module>
print(next(it))
StopIteration
'''
dct = {"name": 'Sam', 'city': 'Ningbo'}
d_iter = iter(dct) # <dict_keyiterator object at 0x0000000002989368>
while True:
try:
print(next(d_iter))
except StopIteration:
break
'''
name
city
'''
6. 生成器
import os
import psutil
# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss / 1024. / 1024
print('{} memory used: {} MB'.format(hint, memory))
def test_iterator():
show_memory_info('initing iterator')
list_1 = [i for i in range(10000000)]
# print(list_1)
show_memory_info('after iterator initiated')
print(sum(list_1))
show_memory_info('after sum called')
def test_generator():
show_memory_info('initing generator')
list_2 = (i for i in range(10000000))
# print(list_2)
show_memory_info('after generator initiated')
print(sum(list_2))
show_memory_info('after sum called')
test_iterator()
test_generator()
'''
initing iterator memory used: 27.28125 MB
after iterator initiated memory used: 66.2421875 MB
499999500000
after sum called memory used: 66.2421875 MB
initing generator memory used: 27.65234375 MB
after generator initiated memory used: 27.65234375 MB
499999500000
after sum called memory used: 27.65234375 MB
生成器的占用内存比列表推导式小
'''
-
声明一个迭代器很简单,[i for i in range(100000000)]就可以生成一个包含一亿元素的列表。每个元素在生成后都会保存到内存中,你通过代码可以看到,它们占用了巨量的内存,内存不够的话就会出现 OOM 错误。
-
不过,我们并不需要在内存中同时保存这么多东西,比如对元素求和,我们只需要知道每个元素在相加的那一刻是多少就行了,用完就可以扔掉了。
-
于是,生成器的概念应运而生,在你调用 next() 函数的时候,才会生成下一个变量。生成器在 Python 的写法是用小括号括起来,(i for i in range(100000000)),即初始化了一个生成器。
-
这样一来,你可以清晰地看到,生成器并不会像迭代器一样占用大量内存,只有在被使用的时候才会调用。而且生成器在初始化的时候,并不需要运行一次生成操作,相比于 test_iterator() ,test_generator() 函数节省了一次生成一亿个元素的过程,因此耗时明显比迭代器短。
-
为什么要有生成器
- 列表所有数据都在内存中,如果有海量数据的话会非常消耗内存。
- 比如说:我们仅仅需要访问前面几个元素,但后面绝大多元素占用的内存就会浪费了。
- 那么生成器就是在循环的过程中根据算法不断推算出后续的元素,这样就不用创建整个完整的列表,从而节省大量的空间。
- 总而言之,就是当我们想要使用庞大数据,又想让它占用的空间少,那就使用生成器。
-
生成器基本生成方式
g = (x for x in range(10))
# 元组推导式(生成器)
print(g) # <generator object <genexpr> at 0x0000000009F8AEB8>
print(next(g))
print(next(g))
- 生成器的基本使用
def demo():
yield 1
print(next(demo())) # 1
print(next(demo())) # 1
d = demo()
print(next(d))
'''
print(next(d))
Traceback (most recent call last):
File "D:/Python/venv/Python-web全栈开发课堂练习文件/Day4/生成器.py", line 68, in <module>
print(next(d))
StopIteration
'''
- 生成器的案例1
def demo(n):
a = 0
b = 1
counter = 0
while True:
if counter > n:
return
yield b
a, b = b, a+b
counter += 1
f = demo(10)
print(next(f))
print(next(f))
- 生成器的案例2
# 给定一个list和指定的数字,求这个数字在list中的位置
# 常规操作
def find_list_index(li, num):
for i in li:
if i == num:
return li.index(num)
print(find_list_index([1, 3, 9, 89, 99], 9))
# 当列表内有两个相同的值(思维推导)
def find_list_index(li, num):
result = []
for i in li:
if i == num:
result.append(li.index())
return result
print(find_list_index([1, 3, 9, 89, 99, 9], 9)) # [2, 2]并未解决
# 当列表内有两个相同的值(思维推导)
def find_list_index(li, num):
result = []
for k, v in enumerate(li):
if v == num:
result.append(k)
return result
print(find_list_index([1, 3, 9, 89, 99, 9], 9)) #[2, 5]
# 直接用生成器
def find_list_index(li, num):
for k, v in enumerate(li):
if v == num:
yield k
print(list(find_list_index([1, 3, 9, 89, 99, 9], 9))) #[2, 5]
- 课堂文件
# 读取大文件,文件300G,文件比较特殊,一行分隔符 {|}
def readlines(f,newline): # f 文件,newline 分隔符
buf = "" # 空字符串
while True:
while newline in buf: # 第一不能进来
pos = buf.index(newline)
yield buf[:pos]
buf = buf[pos + len(newline):]
chunk = f.read(4096*10) # 每次读取量
if not chunk:
yield buf
break
buf += chunk
with open('demo.txt') as f:
for line in readlines(f,"{|}"):
print(line)