Unpickle namedtuple 具有向后兼容性(忽略其他属性)
Unpickle namedtuple with backwards compatibility (ignoring additional attributes)
这是一个模拟 运行 较旧版本的 Python 程序针对较新版本编写的搁置数据库的场景。理想情况下,User 对象仍会被解析和读入; favouritePet 属性将被忽略。可以理解,它会抛出一个错误,抱怨元组不匹配。
是否有一种好的方法可以使这种情况与命名元组一起使用,或者更好地切换到存储字典或 class 如果需要这种灵活性?
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)
编辑:为了完整起见,这里使用了相同的想法 class:
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)
我建议使用字典或自定义 class。
命名元组需要的参数与它具有的字段一样多,因此要直接使用命名元组进行此操作,您必须更改 class' __new__
方法以使用 *args
和 **kwargs
而不是固定的参数列表。如果您查看 User
class 的定义(通过添加 verbose=True
参数),您将看到 class 是如何定义的:
...
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__
一起使用。使用专用 class 而不是以这种方式修改 namedtuple 的行为可能更好。
使用自定义构造函数更改 User
namedtuple 的 pickle 方式会更容易,例如:
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
函数在所有兼容版本中可用,这就可以工作,但正如最初所说,我仍然建议使用不同的数据结构。
这是一个模拟 运行 较旧版本的 Python 程序针对较新版本编写的搁置数据库的场景。理想情况下,User 对象仍会被解析和读入; favouritePet 属性将被忽略。可以理解,它会抛出一个错误,抱怨元组不匹配。
是否有一种好的方法可以使这种情况与命名元组一起使用,或者更好地切换到存储字典或 class 如果需要这种灵活性?
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)
编辑:为了完整起见,这里使用了相同的想法 class:
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)
我建议使用字典或自定义 class。
命名元组需要的参数与它具有的字段一样多,因此要直接使用命名元组进行此操作,您必须更改 class' __new__
方法以使用 *args
和 **kwargs
而不是固定的参数列表。如果您查看 User
class 的定义(通过添加 verbose=True
参数),您将看到 class 是如何定义的:
...
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__
一起使用。使用专用 class 而不是以这种方式修改 namedtuple 的行为可能更好。
使用自定义构造函数更改 User
namedtuple 的 pickle 方式会更容易,例如:
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
函数在所有兼容版本中可用,这就可以工作,但正如最初所说,我仍然建议使用不同的数据结构。