Unpickle namedtuple具有向后兼容性(忽略其他属性)

时间:2016-02-07 20:57:44

标签: python python-3.x namedtuple

这是一个模拟针对由较新版本编写的搁置数据库运行较旧版本的Python程序的场景。理想情况下,仍然会解析和读入User对象; favouritePet属性将被忽略。可以理解的是,它会引发错误,抱怨元组不匹配。

是否有一种很好的方法可以使这个场景与namedtuples一起使用,或者如果需要这种灵活性,可以更好地切换到存储字典或类?

import shelve
from collections import namedtuple

shelf = shelve.open("objectshelf", flag='n')

User = namedtuple("User", ("username", "password", "firstname", "surname", "favouritePet"))
shelf["namedtupleAndrew"] = User("andrew@example.com", "mypassword", "Andrew", "Smith", "cat")

# Redefine User to simulate a previous version of the User object that didn't record favouritePet;
# someone using an old version of the program against a database written to by a new version
User = namedtuple("User", ("username", "password", "firstname", "surname"))
# Throws error "takes 5 positional arguments but 6 were given"
namedTupleRead = shelf["namedtupleAndrew"]

print(namedTupleRead.username)

修改:为了完整起见,使用类的想法是相同的:

import shelve

shelf = shelve.open("objectshelf", flag='n')

class User:

    def __init__(self, username, password, firstname, surname, favouritePet):
        self.username = username
        self.password = password
        self.firstname = firstname
        self.surname = surname
        self.favouritePet = favouritePet

shelf["objectAndrew"] = User("andrew@example.com", "mypassword", "Andrew", "Smith", "cat")

# Redefine User to simulate a previous version of the User object that didn't record favouritePet;
# someone using an old version of the program against a database written to by a new version
class User:

    def __init__(self, username, password, firstname, surname):
        self.username = username
        self.password = password
        self.firstname = firstname
        self.surname = surname

objectRead = shelf["objectAndrew"]

print(objectRead.username)
# favouritePet is still there; it's just a dictionary, after all.
print(objectRead.favouritePet)

1 个答案:

答案 0 :(得分:2)

我建议使用字典或自定义类。

一个命名元组需要与字段一样多的参数,所以要直接使用命名元组,你必须更改类__new__方法以使用*args和{ {1}}而不是固定的参数列表。如果您查看**kwargs类的定义(通过添加User参数),您将看到如何定义类:

verbose=True

... class User(tuple): 'User(username, password, firstname, surname, favouritePet)' __slots__ = () _fields = ('username', 'password', 'firstname', 'surname', 'favouritePet') def __new__(_cls, username, password, firstname, surname, favouritePet): 'Create new instance of User(username, password, firstname, surname, favouritePet)' return _tuple.__new__(_cls, (username, password, firstname, surname, favouritePet)) ... 必须成为__new__,然后正确解析__new__(_cls, *args, **kwargs)args(您仍然希望能够使用kwargs作为User('a', 'b', 'c', ...)以及User('a', password='b', firstname='c', ...)但不User('a', username='A', ...)与namedtuple保持一致),然后将结果序列与tuple.__new__一起使用。使用专用类可能更好,而不是以这种方式修改namedtuple的行为。

通过使用自定义构造函数,使用__reduce__协议(或copyreg.pickle())更改User namedtuple的方式会更容易,例如:

from collections import namedtuple
import shelve
import copyreg

shelf = shelve.open("test")

User = namedtuple("User", ("username", "password", "firstname", "surname", "favouritePet"))

User.__reduce__ = lambda user: (construct_user, tuple(user))
# or: copyreg.pickle(User, lambda user: (construct_user, tuple(user)))

def construct_user(*args):
    print('creating new user:', args)       # for debugging
    return User(*args[:len(User._fields)])


user = User("andrew@example.com", "mypassword", "Andrew", "Smith", "cat")
print(user)
shelf["namedtupleAndrew"] = user

# redefine User
User = namedtuple("User", ("username", "password", "firstname", "surname"))

print(shelf["namedtupleAndrew"])

只要construct_user函数在所有兼容版本中都可用,这将有效,但如前所述,我仍然建议使用不同的数据结构。