python中flask如何降低内存?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python中flask如何降低内存?相关的知识,希望对你有一定的参考价值。

参考技术A Dict
在小型程序中,特别是在脚本中,使用Python自带的dict来表示结构信息非常简单方便:

>>> ob = 'x':1, 'y':2, 'z':3

>>> x = ob['x']

>>> ob['y'] = y

由于在Python 3.6中dict的实现采用了一组有序键,因此其结构更为紧凑,更深得人心。但是,让我们看看dict在内容中占用的空间大小:

>>> print(sys.getsizeof(ob))

240

如上所示,dict占用了大量内存,尤其是如果突然虚需要创建大量实例时:

实例数

对象大小

1 000 000

240 Mb

10 000 000

2.40 Gb

100 000 000

24 Gb

类实例

有些人希望将所有东西都封装到类中,他们更喜欢将结构定义为可以通过属性名访问的类:

class Point:

#

def __init__(self, x, y, z):

self.x = x

self.y = y

self.z = z

>>> ob = Point(1,2,3)

>>> x = ob.x

>>> ob.y = y

类实例的结构很有趣:

字段

大小(比特)

PyGC_Head

24

PyObject_HEAD

16

__weakref__

8

__dict__
8

合计:

56

在上表中,__weakref__是该列表的引用,称之为到该对象的弱引用(weak reference);字段__dict__是该类的实例字典的引用,其中包含实例属性的值(注意在64-bit引用平台中占用8字节)。从Python3.3开始,所有类实例的字典的键都存储在共享空间中。这样就减少了内存中实例的大小:

>>> print(sys.getsizeof(ob), sys.getsizeof(ob.__dict__))

56 112

因此,大量类实例在内存中占用的空间少于常规字典(dict):

实例数

大小

1 000 000
168 Mb
10 000 000
1.68 Gb
100 000 000

16.8 Gb

不难看出,由于实例的字典很大,所以实例依然占用了大量内存。

带有__slots__的类实例

为了大幅降低内存中类实例的大小,我们可以考虑干掉__dict__和__weakref__。为此,我们可以借助 __slots__:

class Point:

__slots__ = 'x', 'y', 'z'

def __init__(self, x, y, z):

self.x = x

self.y = y
self.z = z
>>> ob = Point(1,2,3)
>>> print(sys.getsizeof(ob))

64

如此一来,内存中的对象就明显变小了:

字段

大小(比特)

PyGC_Head

24

PyObject_HEAD
16
x
8

y

8
z
8
总计:

64

在类的定义中使用了__slots__以后,大量实例占据的内存就明显减少了:

实例数

大小

1 000 000

64 Mb

10 000 000

640 Mb

100 000 000
6.4 Gb
目前,这是降低类实例占用内存的主要方式。
这种方式减少内存的原理为:在内存中,对象的标题后面存储的是对象的引用(即属性值),访问这些属性值可以使用类字典中的特殊描述符:

>>> pprint(Point.__dict__)

mappingproxy(

....................................

'x': ,

'y': ,

'z': )

为了自动化使用__slots__创建类的过程,你可以使用库namedlist(https://pypi.org/project/namedlist)。namedlist.namedlist函数可以创建带有__slots__的类:

>>> Point = namedlist('Point', ('x', 'y', 'z'))

还有一个包attrs(https://pypi.org/project/attrs),无论使用或不使用__slots__都可以利用这个包自动创建类。

元组

Python还有一个自带的元组(tuple)类型,代表不可修改的数据结构。元组是固定的结构或记录,但它不包含字段名称。你可以利用字段索引访问元组的字段。在创建元组实例时,元组的字段会一次性关联到值对象:

>>> ob = (1,2,3)

>>> x = ob[0]

>>> ob[1] = y # ERROR

元组实例非常紧凑:

>>> print(sys.getsizeof(ob))

72

由于内存中的元组还包含字段数,因此需要占据内存的8个字节,多于带有__slots__的类:

字段

大小(字节)

PyGC_Head

24

PyObject_HEAD

16

ob_size

8

[0]

8

[1]

8

[2]
8
总计:
72
命名元组

由于元组的使用非常广泛,所以终有一天你需要通过名称访问元组。为了满足这种需求,你可以使用模块collections.namedtuple。

namedtuple函数可以自动生成这种类:

>>> Point = namedtuple('Point', ('x', 'y', 'z'))

如上代码创建了元组的子类,其中还定义了通过名称访问字段的描述符。对于上述示例,访问方式如下:
class Point(tuple):
#

@property

def _get_x(self):

return self[0]
@property
def _get_y(self):

return self[1]

@property

def _get_z(self):
return self[2]
#
def __new__(cls, x, y, z):
return tuple.__new__(cls, (x, y, z))
这种类所有的实例所占用的内存与元组完全相同。但大量的实例占用的内存也会稍稍多一些:

实例数

大小
1 000 000
72 Mb

10 000 000

720 Mb

100 000 000

7.2 Gb

记录类:不带循环GC的可变更命名元组

由于元组及其相应的命名元组类能够生成不可修改的对象,因此类似于ob.x的对象值不能再被赋予其他值,所以有时还需要可修改的命名元组。由于Python没有相当于元组且支持赋值的内置类型,因此人们想了许多办法。在这里我们讨论一下记录类(recordclass,https://pypi.org/project/recordclass),它在StackoverFlow上广受好评(https://stackoverflow.com/questions/29290359/existence-of-mutable-named-tuple-in)。

此外,它还可以将对象占用的内存量减少到与元组对象差不多的水平。

recordclass包引入了类型recordclass.mutabletuple,它几乎等价于元组,但它支持赋值。它会创建几乎与namedtuple完全一致的子类,但支持给属性赋新值(而不需要创建新的实例)。recordclass函数与namedtuple函数类似,可以自动创建这些类:

>>>Point = recordclass('Point', ('x', 'y', 'z'))

>>>ob = Point(1, 2, 3)

类实例的结构也类似于tuple,但没有PyGC_Head:

字段

大小(字节)

PyObject_HEAD

16

ob_size

8

x

8

y

8

z
8
总计:
48
在默认情况下,recordclass函数会创建一个类,该类不参与垃圾回收机制。一般来说,namedtuple和recordclass都可以生成表示记录或简单数据结构(即非递归结构)的类。在Python中正确使用这二者不会造成循环引用。因此,recordclass生成的类实例默认情况下不包含PyGC_Head片段(这个片段是支持循环垃圾回收机制的必需字段,或者更准确地说,在创建类的PyTypeObject结构中,flags字段默认情况下不会设置Py_TPFLAGS_HAVE_GC标志)。

大量实例占用的内存量要小于带有__slots__的类实例:

实例数

大小

1 000 000

48 Mb10 000 000

480 Mb

100 000 000
4.8 Gb
dataobject
recordclass库提出的另一个解决方案的基本想法为:内存结构采用与带__slots__的类实例同样的结构,但不参与循环垃圾回收机制。这种类可以通过recordclass.make_dataclass函数生成:
>>> Point = make_dataclass('Point', ('x', 'y', 'z'))

这种方式创建的类默认会生成可修改的实例。

另一种方法是从recordclass.dataobject继承:

class Point(dataobject):

x:int

y:int

z:int

这种方法创建的类实例不会参与循环垃圾回收机制。内存中实例的结构与带有__slots__的类相同,但没有PyGC_Head:

字段

大小(字节)

PyObject_HEAD

16

ob_size

8

x

8

y

8

z

8

总计:

48

>>> ob = Point(1,2,3)

>>> print(sys.getsizeof(ob))

40

如果想访问字段,则需要使用特殊的描述符来表示从对象开头算起的偏移量,其位置位于类字典内:

mappingproxy('__new__': ,

.......................................

'x': ,

'y': ,

'z': )

大量实例占用的内存量在CPython实现中是最小的:

实例数

大小

1 000 000

40 Mb

10 000 000

400 Mb

100 000 000

4.0 Gb

Cython

还有一个基于Cython(https://cython.org/)的方案。该方案的优点是字段可以使用C语言的原子类型。访问字段的描述符可以通过纯Python创建。例如:

cdef class Python:

cdef public int x, y, z

def __init__(self, x, y, z):

self.x = x

self.y = y

self.z = z

本例中实例占用的内存更小:

>>> ob = Point(1,2,3)

>>> print(sys.getsizeof(ob))

32

内存结构如下:

字段

大小(字节)

如何通过 Python 中的 Flask 传递内存中的 Azure Blob

【中文标题】如何通过 Python 中的 Flask 传递内存中的 Azure Blob【英文标题】:How to pass an Azure Blob in memory through Flask in Python 【发布时间】:2021-06-09 17:50:05 【问题描述】:

我在 python 和一般编程方面都是新手。我在这里的 Terraform Private Registry API 已经取得了相当大的进展。

我的情况是,我从 Azure Blob 存储中提取一个 zip,代码托管在托管标识 azure Web 应用程序中,并且它具有对该 blob 的权限。

本质上发生的事情是我将它拉下来,然后通过 send_file 传递出去。

但是,我不得不将它写入一个临时目录,该目录仍然存在。

所以最终,如果没有清理任务,这将填满垃圾文件。

我试图弄清楚如何在内存中完成这一切,或者使用临时文件和上下文,但它不起作用。当它到达 send_file 部分时,即使我把它放在 With 中,它也会传递垃圾或者说文件路径不存在。

感觉这应该是很有可能的,所以我希望有人可以指导我,甚至只是告诉我。我花了几个小时试图找出 iobytes 样式的流方法,但它有点超出我的能力,所以希望能得到帮助。

我什至会选择一种自动清理目录的方法,但据我了解,在 Flask 函数的 return 部分之后,你已经完成了,在它触发之后什么都没有。

@app.route('/v1/modules/<namespace>/<name>/<provider>/<version>/local.zip', methods=['GET'])
def downloadfile(namespace, name,provider,version):
    bloburl = f'azblobstoragehost/azcontainer/v1/modules/namespace/name/provider/version/local.zip'
    token_credential = DefaultAzureCredential()
    blob_client = BlobClient.from_blob_url(bloburl, credential=token_credential)  
    download_stream = blob_client.download_blob()
    # create a temporary directory using the context manager
    #f = tempfile.TemporaryDirectory(dir = "temp")
    f = tempfile.mkdtemp(dir = "temp") #this one works
    with open(f'f/local.zip', "wb") as my_blob:
        download_stream = blob_client.download_blob()
        my_blob.write(download_stream.readall())
    return send_file(f'my_blob.name')

【问题讨论】:

不需要先下载blob;除了流式传输 blob 内容之外,您还可以将 uri 返回到 blob,这允许内容直接从 blob 流式传输到客户端。有关更多信息,请参阅我的回答 here。这个答案不在 python 中,但至少应该给你一个视角。 谢谢@DavidMakogon 我会试一试的。我正在使用 Azure AD 托管标识,它可以使用该方法吗? 您应该能够生成一个共享访问签名,以提供对特定 blob 或容器的临时访问权限。有这方面的文档,所以我建议试一试。 【参考方案1】:

感谢我付出了一些努力! 我有点担心使用 sas 以防它被窥探? 实际上有没有办法像我说的那样传递文件,或者没有什么好担心的,sas 不能被窥探和使用?

【讨论】:

以上是关于python中flask如何降低内存?的主要内容,如果未能解决你的问题,请参考以下文章

flask如何使模板返回大文件,又不消耗大量内存

[Python]Flask内存马学习

[Python]Flask内存马学习

在使用 Flask 的 python 中,如何写出一个对象以供下载?

使用Flask结合python实现多台服务的内存监控

Python Flask 应用程序中的“无法在静态 TLS 块中分配内存”