I haven’t looked through it all, but it looks well designed, cleanly written and well documented. Good job. I never imagined a savegame system could be that losely coupled. Most systems I’ve seen so far use a deeply integrated serialisation system.
My own one works similar to that of Django or Unity3D, where you assign class wide attributes that are handled differently for each class instance using the data descriptor protocol. In practice it looks like this:
class C(object):
f1 = FloatProperty(minimum=0)
f2 = IntProperty(default=1)
def __init__(self):
self.f1 = 3.1 # this will work and look like a float for the object
self.f2 = 3.14 # this will be converted, in this case rounded 3
assert isinstance(self.f1, float) # true
assert isinstance(C.f1, FloatProperty) # true
The good thing about this is that you can clearly define serialized (to be saved) attributes of any type and you can even constraint them. The downside is that it’s complex under the hood and Python newbies might have a hard time with using this.
By looking through the code I strumbled upon a few lines that are not wrong or anything, but I think could be a bit better readable or flexible. I hope you don’t feel offended by my nitpicking.
Skip the next part if you don’t like coding suggestions.
if isinstance(obj, list):
for item in obj:
newEntry.addItem("", item)
elif isinstance(obj, tuple):
for item in obj:
newEntry.addItem("", item)
elif isinstance(obj, dict):
for pair in obj.items():
newEntry.addItem("", pair)
you can write
import collections
addItem = lambda item: newEntry.addItem("", item)
if isinstance(e, dict):
filter(addItem(i), e.items())
elif isinstance(e, collections.Iterable): # any other iterable than dict
filter(addItem(i), e)
or you could simply try to iterate without checking in a try…except block (try…except is common practice and way performance cheaper than one might think).
Using one of those will also catch other iterables like sets, strings, self-built ones and so on, giving much more flexibility. Only dicts need a special handling. If you need the results of filter, use map() or a nested list comprehension. This is more of a personal preference. I bet many people will find the for loop more readable.
elif isinstance(obj, types.FunctionType):
...
elif isinstance(obj, types.MethodType):
...
you could write
if callable(obj):
...
This way you catch all objects you can call, e.g. classes and other objects with call.
notFoundSpecialType = True
i = 0
keys = GameSaver.specialTypeDictionary.keys()
while i < len(keys) and notFoundSpecialType:
if isinstance(obj, keys[i]):
notFoundSpecialType = False
newEntry.addItem("", GameSaver.specialTypeDictionary[keys[i]].saveFn(obj))
i += 1
if notFoundSpecialType:
newEntry.dataList.append(str(obj))
could be rewritten as
foundSpecialType = False
for specialType, noIdeaWhat in GameSaver.specialTypeDictionary.iteritems():
if isinstance(obj, specialType):
foundSpecialType = True
newEntry.addItem("", noIdeaWhat.saveFn(obj))
break
if not foundSpecialType:
newEntry.dataList.append(str(obj))
It isn’t really that much less code, but a for loop reads better than a while with index IMHO. Oh and for booleans rather don’t use negated forms, or you’ll get things like “not not not found”
- And as last, rather than writing so much
result += "Game Save Entry: " + str(self.objType) + " " + str(self.loadFn) + "\n"
you could use the new style format function:
result += "Game Save Entry: {} {}\n".format(self.objType, self.loadFn)
or
"\n".join(result, "Game Save Entry: {} {}".format(self.objType, self.loadFn))
That function also allows many other formating options and is very useful when combining many items. Using join you can also combine multiple lines simply putting them into that function rather than writing \n at the end of each one, which one forgets quickly. Again, this is all personal preference, I guess.
Keep up the good work and have a nice day.
PS: I’m applying for the longest post ever award!