Python递归错误和序列化器
#python #flask #调试 #recursionerror

我最近完成了我的第一个Blask-react-Reaction应用程序,这是一个名为“ mytunes”的Spotify Clone应用程序,允许用户创建唯一的播放列表,添加我的PostgreSQL数据库中的歌曲,然后通过链接到Spotify Web-Browser播放这些歌曲。我觉得自己终于掌握了一对多和多对多的数据库模型关系 - 我的数据库被种植并在React前端渲染,但我注意到我的最初提取需要花费越来越长的时间完全的。我认为这是从后端路线中获取更多数据的副产品,我将继续构建我的应用程序,然后研究使获取效率提高的方法。我又添加了一条路线 - 一条邮寄路线,将歌曲实例添加到播放列表中 - 突然我的环境崩溃了,我在终端中看到了一个巨大的“递归错误”。

定义递归

welp。这是我第一次遇到递归错误,看起来很恐怖。我试图滚动到错误的顶部,但这似乎是无休止的。事实证明,这正是递归 - 一个函数,该函数从功能中自称。正确编写递归的重要部分是该函数必须在某个时候终止,否则它不能运行,因为它使用了过多的内存,处理能力或仅仅是无限的。

编写递归可能非常有帮助 - 例如,您可能需要写一个从某个起点向后计数并停止在0的函数。只要该函数可以达到终止点,递归功能就可以完全接受。并且可能非常有效。

模型关系中的递归

渲染关系数据是经历递归错误的主要环境。例如,在我构建的mytunes应用程序中,我设计了播放列表和歌曲模型之间的许多关系。我在这里简化了模型,仅包括解决递归错误的相关信息。

播放列表模型:

class Playlist(db.Model):
    __tablename__ = "playlists"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)

    songs = db.relationship('Song', secondary=playlist_song, back_populates='playlists')

    user_id = db.Column(db.Integer, db.ForeignKey('users.id'))

    def __repr__(self):
        return f'<Playlist ID: {self.id} | Name: {self.name}>'

歌曲模型:

class Song(db.Model):
    __tablename__ = "songs"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)
    artist_name = db.Column(db.String, nullable=False)

    playlists = db.relationship('Playlist', secondary=playlist_song, back_populates='songs')

    def __repr__(self):
        return f'<Song ID: {self.id} | Name: {self.name} | Artist: {self.artist_name}>'

加入表:

playlist_song = db.Table('playlist_songs',
                          db.Column('playlist_id', db.Integer, db.ForeignKey('playlists.id'), primary_key=True),
                          db.Column('song_id', db.Integer, db.ForeignKey('songs.id'), primary_key=True))

播放列表和歌曲模型通过Join Table,Playlist_song相关。我通过 /艺术家端点将所有这些数据拉到了我的应用程序中,在应用程序的烧瓶侧的每个实例上调用python方法.to_dict(),然后在通过我的react中呈现fetch请求中的数据时调用.json()前端。将数据对象转换为要传输的其他形式的过程称为序列化

当我继续构建烧瓶端点时,我注意到我对React End的提取请求需要更长的时间才能完成。最终,可怕的递归错误命中率,我疯狂地搜索了谷歌搜索,然后呼吸,然后记住我可以在烧瓶外壳中测试我的模型实例。

flask shell
hello = Song(name="Hello", artist_name="Adele")
jams = Playlist(name="Jams")

创建歌曲和播放列表的实例后,我测试了关系和.to_dict()方法,以确保可以成功调用.to_dict()方法并返回字典对象。

hello.to_dict()
{...} / Successful response /
jams.to_dict() 
{...} / Successful response /
hello.playlists
[] / Successful response as no playlists were assigned /
jams.songs
[] / Successful response as no songs were assigned

没有关系的单个实例能够通过.to_dict()方法成功转换为字典对象。接下来,我搬到了与种子数据库上建立关系的多个实例。

flask shell
playlists = Playlist.query.all()
songs = Song.query.all()
playlists[0].songs / Grabbed first playlist and listed all songs
[...] / Successful response with the songs I had related to the playlist
playlists[0].songs.to_dict()
RECURSION ERROR

啊哈!我回到终端中滚动以检查播放列表[0]的输出。再次播放,除了输出我分配给播放列表的5首歌曲外,这些歌曲中的每一首歌曲都有一个嵌套的“播放列表”键,其中包括顶部Level播放列表,并且每个嵌套的“播放列表”键中的每一个都将歌曲再次嵌套在其中...当在此结构上调用.to_dict()时,它产生了递归错误,因为它导致了数据的永无止境。

我还以相反的方式测试了这些数据 - 作为songs.playlists而不是播放列表。我需要找到一种方法来排除我的数据库,将每个模型无限地相互关联。输入 - sqlalchemy serialializer mixin。

串行器混合物

Serializer Mixin是一种添加.to_dict()方法来建模实例的混合物。这消除了定义明确的.to_dict()类方法的需求。它还允许用户设置序列化规则,以便可以从.to_dict()方法中排除某些关系和列,理想情况下,通过python试图在将数据对象序列化为另一种类型时,通过python试图无休止地嵌套此关系数据来避免递归错误原因。

可以通过从SQL-Alchemy导入序列化合物混合蛋白。这是使用Serializer Mixin的更新的播放列表和歌曲模型。

from sqlalchemy_serializer import SerializerMixin

class Playlist(db.Model, SerializerMixin):
    __tablename__ = "playlists"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)

    songs = db.relationship('Song', secondary=playlist_song, back_populates='playlists')

    user_id = db.Column(db.Integer, db.ForeignKey('users.id'))

    def __repr__(self):
        return f'<Playlist ID: {self.id} | Name: {self.name}>'

class Song(db.Model, SerializerMixin):
    __tablename__ = "songs"

    serialize_rules = ('-playlists', )

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)
    artist_name = db.Column(db.String, nullable=False)

    playlists = db.relationship('Playlist', secondary=playlist_song, back_populates='songs')

    def __repr__(self):
        return f'<Song ID: {self.id} | Name: {self.name} | Artist: {self.artist_name}>'

playlist_song = db.Table('playlist_songs',
                          db.Column('playlist_id', db.Integer, db.ForeignKey('playlists.id'), primary_key=True),
                          db.Column('song_id', db.Integer, db.ForeignKey('songs.id'), primary_key=True))

在歌曲模型中,我指定了从序列化中排除“ playLists”的规则。升级烧瓶数据库并进行了回收后,我打开了烧瓶外壳,并测试了与以前相同的实例。这次,歌曲[0] .playlists.to_dict()和播放列表[0] .songs.to_dict()成功返回了没有递归错误的字典对象。

测试递归功能

通过在烧瓶外壳中测试我的种子数据库的实例在我的代码中找到了第一个递归错误后,我继续测试了我在其他模型之间建立的每一个关系:用户,艺术家,歌曲和播放列表。在每种情况下,当在与另一个模型有关的模型上调用.to_dict()时,我遇到了递归误差。我添加了序列化规则,以排除导致错误的每个区域。查看我的github存储库查看full models.py code,以查看我需要添加的所有序列化规则。

结论

在每个模型中添加必要的序列化规则后,我使用honcho启动了我的应用程序(如果您不知道honcho,check it out可以使用一个命令运行前端和后端开发环境!)我没有遇到递归错误,但是我的获取数据很快加载了。我在事后意识到,我通过模型所内置的其他递归错误还不够问题,无法绕过python的递归限制,但是通过消除偶然的回复功能,我的数据库实例就可以更快地序列化,而不会更快地序列化。嵌套数据。

虽然遵循递归错误可能令人生畏,但我希望本指南有助于从这个错误中删除一些神秘感。自从用序列化器混合蛋白解决自己的递归错误以来,我还从SQL-Alchemy找到了Marshmallow (de)Serialization library。我尚未使用此库,但看起来像是序列化错误的另一个解决方案。

mytunes信息

如果您有兴趣查看mytunes,这是我的完整GitHub repository!对于Mytunes的TLDR版本,请查看我的walkthrough video