python load from shelve - can I retain the variable name? - python

I'm teaching myself how to write a basic game in python (text based - not using pygame). (Note: I haven't actually gotten to the "game" part per-se, because I wanted to make sure I have the basic core structure figured out first.)
I'm at the point where I'm trying to figure out how I might implement a save/load scenario so a game session could persist beyond a signle running of the program. I did a bit of searching and everything seems to point to pickling or shelving as the best solutions.
My test scenario is for saving and loading a single instance of a class. Specifically, I have a class called Characters(), and (for testing's sake) a sigle instance of that class assigned to a variable called pc. Instances of the Character class have an attribute called name which is originally set to "DEFAULT", but will be updated based on user input at the initial setup of a new game. For ex:
class Characters(object):
def __init__(self):
self.name = "DEFAULT"
pc = Characters()
pc.name = "Bob"
I also have (or will have) a large number of functions that refer to various instances using the variables they are asigned to. For example, a made up one as a simplified example might be:
def print_name(character):
print character.name
def run():
print_name(pc)
run()
I plan to have a save function that will pack up the pc instance (among other info) with their current info (ex: with the updated name). I also will have a load function that would allow a user to play a saved game instead of starting a new one. From what I read, the load could work something like this:
*assuming info was saved to a file called "save1"
*assuming the pc instance was shelved with "pc" as the key
import shelve
mysave = shelve.open("save1")
pc = mysave["pc"]
My question is, is there a way for the shelve load to "remember" the variable name assotiated with the instance, and automatically do that << pc = mysave["pc"] >> step? Or a way for me to store that variable name as a string (ex as the key) and somehow use that string to create the variable with the correct name (pc)?
I will need to "save" a LOT of instances, and can automate that process with a loop, but I don't know how to automate the unloading to specific variable names. Do I really have to re-asign each one individually and explicitly? I need to asign the instances back to the apropriate variable names bc I have a bunch of core functions that refer to specific instances using variable names (like the example I gave above).
Ideas? Is this possible, or is there an entirely different solution that I'm not seeing?
Thanks!
~ribs

Sure, it's possible to do something like that. Since a shelf itself is like a dictionary, just save all the character instances in a real dictionary instance inside it using their variable's name as the key. For example:
class Character(object):
def __init__(self, name="DEFAULT"):
self.name = name
pc = Character("Bob")
def print_name(character):
print character.name
def run():
print_name(pc)
run()
import shelve
mysave = shelve.open("save1")
# save all Character instances without the default name
mysave["all characters"] = {varname:value for varname,value in
globals().iteritems() if
isinstance(value, Character) and
value.name != "DEFAULT"}
mysave.close()
del pc
mysave = shelve.open("save1")
globals().update(mysave["all characters"])
mysave.close()
run()

Related

how to instantiate a string input by the user in python [duplicate]

This question already has answers here:
How to dynamically load a Python class
(13 answers)
Closed 11 months ago.
I'm new in python, and need some help here:
The user enters an identifer (project name, client name) through the GUI or controle screen.
This text entered by the user should be the identifier that automatically instantiates a Class:
identifier = Class()
Question: how can I do that in python? (so far as I have seen in videos, every instantiation was done directly from a variable typed inside the code...)
You can use a bunch of if statements to decide which class to instantiate
user_input = input()
if user_input == 'myClass':
identifier = myClass()
elif user_input == 'anotherClass':
identifier = anotherClass()
...
You don't want to just blindly accept any input from the user because of security issues. There is eval and exec, but you would be basically giving the user free reign to also run whatever code they want by using those.
If you have more than a few classes, then instead of if statements you can use a dictionary like so.
inputs_to_classes = { 'myClass': myClass, 'anotherClass': anotherClass }
user_input = input('Enter a class to instantiate: ').lower()
identifier = inputs_to_classes[user_input]()
Or in Python 3.10+ you could use a match expression, but I don't currently have access to Python 3.10.
the correct answer in this case is: don't do this. (and yes, it's possible to do this, but again, don't do this)
Essentially, your user should not even need to be made aware of what variables are, and should absolutely not be controlling what variable names are created in your code dynamically for your objects of these classes.
So, one actual solution that bypasses this issue is to just use a dictionary. take a string from user, that's a key in a dictionary. initialize your class against the key in the dictionary, and just use the key to access the class.
Another, perhaps better solution, is to just have your class take an extra name attribute. when you need to display the name the user entered for the class, you access it on the attribute. Then you do not even need to use a dictionary, you just create the class with your own variable name internally, but always display the user entered name from it's attribute. This separates concerns between what the user knows and what the programmer should deal with.
# Recommended solution
class Project:
def __init__(self, name, other_params):
self.name = name
self.other_params = other_params
def some_method(self, extra_args):
pass
class Client:
def __init__(self, name, other_params):
self.name = name
self.other_params = other_params
def some_method(self, extra_args):
pass
# these can come from your gui instead of input, doesn't matter
project_user_input = input("enter project name: ")
# these can come from your gui instead of input, doesn't matter
client_user_input = input("enter client name: ")
# make the actual objects, your variable names are internal to you,
# and the names themselves should not be tied to business logic.
# use the .name method to access the user-facing names.
project_obj = Project(name=project_user_input, other_params=42)
client_obj = Client(name=client_user_input, other_params=100)
# you can always access user facing names as necessary.
print(f"the project has the name: {project_obj.name}")
print(f"the client has the name: {client_obj.name}")
Decide on gui framework
Learn gui framework
Come back with questions regarding that framework

Hook every variable reference and execute code

I have a Python app split across different files. One of them, models.py, contains, among PyQt5 table models, several maps referred from several PyQt5 form files:
# first lines:
agents_id_map = \
{agent.name:agent.id for agent in db.session.query(db.Agent, db.Agent.id)}
# ....
# 2000 thousand lines
I want to keep this kind of maps centralized in a single point. I'm using SQLAlchemy also. Agent class is defined in a db.py file. I use these maps to fulfill the foreign key in another object, say, an invoice, like:
invoice = db.Invoice()
# Here is a reference
invoice.agent_id = models.agents_id_map[agent_combo.currentText()]
ยทยทยทยท
db.session.add(invoice)
db.session.commit()
The problem is that the model.py module gets cached and several parts of the application access old data, and, if another running instance A of the app creates a new agent, and a running instance B wants to create a new invoice, the B running instance won't see the new Agent created by A unless restarts the app. This also happens if a user in the same running instance creates an agent and then he wants to create an invoice. My solutions are:
Reload the module, to get the whole code executed again, but this could be very expensive.
Isolate the code building those maps in another file, say maps.py, which would be less expensive to reload and change all code that references it through refactoring.
Is there a solution that would allow me to touch only the code building those maps and the rest of the application remains ignorant of the change, and every time the map is referenced from another module or even the same, the code gets executed, effectively re-building maps with fresh data?
Is there a solution that would allow me to touch only the code building those maps and the rest of the application remains ignorant of the change, and every time the map is referenced from another module or even the same, the code gets executed, effectively re-building maps with fresh data?
Certainly: put you maps inside a function, or even better, a class.
If I understand this problem correctly, you have stateful data (maps) which need regenerating under some condition (every time they are accessed? Or just every time the db is updated?). I would do something like this:
class Mappings:
def __init__(self, db):
self._db = db
... # do any initial db stuff you need to here
def id_map(self, thing):
db_thing = getattr(self._db, thing.title)
return {x.name:x.id for x in self._db.session.query(db_thing, db_thing.id)}
def other_property_map(self, prop):
... # etc
mapping = Mapping(db)
mapping.id_map("agent")
This assumes that the mapping example you've given is your major use-case, but this model could easily be adapted for almost any other mapping you might want.
You would write a method of every kind of 'mapping' you need, and it would return the desired dictionary. Note that here I've assumed you handle setting up the db elsewhere and pass a fully initialised db access object to the class, which is probably what you want to do---this class is just about encapsulating mapper state, not re-inventing your orm.
Caching
I have not provided any caching. But if you have complete control over the db, it is easy enough to run a hook before you do any db commits looking to see if you've touched any particular model, and then state that those need rebuilding. Something like this:
class DbAccess(Mappings):
def __init__(self, db, models):
super().init(db)
self._cached_map = {model: {} for model in models}
def db_update(model: str, params: dict):
try:
self._cached_map[model] = {} # wipe cache
except KeyError:
pass
self._db.update_with_model(model, params) # dummy fn
def id_map(self, thing: str):
try:
return self._cached_map[thing]["id"]
except KeyError:
self._cached_map[thing]["id"] = super().id_map(thing)
return self._cached_map[thing]["id"]
I don't really think DbAccess should inherit from Mappings---put it all in one class, or have a DB class and a Mappings mixin and inherit from both. I just didn't want to write everything out again.
I've not written any real db access routines, (hence my dummy fn) as I don't know how you're doing it (but clearly using an ORM). But the basic idea is just to handle the caching yourself, by storing the mapping every time, but deleting all the stored mappings every time you do any commit transactions involving the model in question (thus rebuilding the cache as needed).
Aside
Note that if you really do have 2,000 lines of manually declared mappings of the form thing.name: thing.id you really should generate them at runtime anyhow. Declarative is all very well and good, but writing out 2,000 permutations of the same thing isn't declarative, it's just time-consuming---and doing the job a simple loop putting the data in ram could do for you at startup.

Creating Object With A For Loop

Firstly, I do apologise as I'm not quite sure how to word this query within the Python syntax. I've just started learning it today having come from a predominantly PowerShell-based background.
I'm presently trying to obtain a list of projects within our organisation within Google Cloud. I want to display this information in two columns: project name and project number - essentially an object. I then want to be able to query the object to say: where project name is "X", give me the project number.
However, I'm rather having difficulty in creating said object. My code is as follows:
import os
from pprint import pprint
from googleapiclient import discovery
from oauth2client.client import GoogleCredentials
credentials = GoogleCredentials.get_application_default()
service = discovery.build('cloudresourcemanager', 'v1', credentials=credentials)
request = service.projects().list()
response = request.execute()
projects = response.get('projects')
The 'projects' variable then seems to be a list, rather than an object I can explore and run queries against. I've tried running things like:
pprint(projects.name)
projects.get('name')
Both of which return the error:
"AttributeError: 'list' object has no attribute 'name'"
I looked into creating a Class within a For loop as well, which nearly gave me what I wanted, but only displayed one project name and project number at a time, rather than the entire collection I can query against:
projects=[]
for project in response.get('projects', []):
class ProjectClass:
name = project['name']
projectNumber = project['projectNumber']
projects.append(ProjectClass.name)
projects.append(ProjectClass.projectNumber)
I thought if I stored each class in a list it might work, but alas, no such joy! Perhaps I need to have the For loop within the class variables?
Any help with this would be greatly appreciated!
As #Code-Apprentice mentioned in a comment, I think you are missing a critical understanding of object-oriented programming, namely the difference between a class and an object. Think of a class as a "blueprint" for creating objects. I.E. your class ProjectClass tells python that objects of type ProjectClass will have two fields, name and projectNumber. However, ProjectClass itself is just the blueprint, not an object. You then need to create an instance of ProjectClass, which you would do like so:
project_class_1 = ProjectClass()
Great, now you have an object of type ProjectClass, and it will have fields name and projectNumber, which you can reference like so:
project_class_1.name
project_class_1.projectNumber
However, you will notice that all instances of the class that you create will have the same value for name and projectNumber, this just won't do! We need to be able to specify values when we create each instance. Enter init(), a special python method colloquially referred to as the constructor. This function is called by python automatically when we create a new instance of our class as above, and is responsible for setting up all the fields of that class. Another powerful feature of classes and objects is that you can define a collection of different functions that can be called at will.
class ProjectClass:
def __init__(self, name, projectNumber):
self.name = name
self.projectNumber = projectNumber
Much better. But wait, what's that self variable? Well, just as before we were able reference the fields of our instance via the "project_class_1" variable name, we need a way to access the fields of our instance when we're running functions that are a part of that instance, right? Enter self. Self is another python builtin parameter that contains a reference to the current instance of the ProjectClass that is being accessed. That way, we can set fields on the instance of the class that will persist, but not be shared or overwritten by other instances of the ProjectClass. It's important to remember that the first argument passed to any function defined on a class will always be self (except for some edge-cases you don't need to worry about now).
So restructuring your code, you would have something like this:
class ProjectClass:
def __init__(self, name, projectNumber):
self.name = name
self.projectNumber = projectNumber
projects = []
for project in response.get('projects', []):
projects.append(ProjectClass(project["name"], project["projectNumber"])
Hopefully I've explained this well and given you a complete answer on how all these pieces fit together. The hope is for you to be able to write that code on your own and not just give you the answer!

Variable from __init__ changed in one function, but not recognized as changed to other function

Sup, i'm doing a little account registration just for learning propose.
I created a class called Accounts and did many different functions to work with. With previous acknowledgment i knew that i need to start them from a function called def __init__(self)
class Account:
def __init__(self):
self.contas = {}
self.bancodecontas = open("x.txt", 'r')
def getaccounts(self):
for linha in self.bancodecontas:
linha = linha.strip()
conta = linha.split(",")
login = conta[0]
senha = conta[1]
self.contas[login] = senha
def accountsprinting(self):
for login, senha in self.contas.items():
print("Login= ", login, "Senha= ", senha)
getaccounts() is working fine, i tested a print(self.contas) in the end of it and it printed all accounts from inside my x.txt. The problem start when i need to call accountsprinting(), i tried to start it with print(self.contas) but shows me a empty dictionary, which means it is not accessing the "new" self.contas. I did the exact samething in a different type of project and it worked fine, i know i'm missing something really obvious here, so i'm asking sorry beforehand for my lack of attention.
Thanks for your time, good codding.
EDIT 1
People asked for the entire program, this is my entire program. I'm using PyCharm, i created this as a accounts.py, a root file or resources file, and i'm going to be importing this class to another main.py to use the respective functions. I know i must call Accounts().getaccounts() first, then i must call the other functions, so i can first fill my "accounts database". Even doing this:
Adding print(self.contas) to the end of getaccounts() and the start of accountsprinting()
And doing on the same .py:
Account().getaccounts()
Account().accountsprinting()
Or doing on different .py:
from AccountManager import Account
Account().getaccounts()
Account().accountsprinting()
The output is the same:
{'Bruno': '666', 'Bruno2': '444', 'Pedro': '2222a', 'Breno': '092b'}
{}
EDIT 2
Adding self.getaccounts()to def __init__(self) as #Darkonaut said, really worked, on the same .py and even doing an import from another .py, but i would like to understand why without it, it doesn't work, makes no sense to me.
Thanks a lot =)
You need to create AND use the same instance to call both methods with your expected results.
acc = Account()
accounts = acc.getaccounts()
acc.accountsprinting()
Also, you import Accounts but you call Account in your code.
You forgot self.getaccounts() in the last line of your __init__ method, hence contas remains empty because it never get's filled.
TL;DR Make sure you keep a reference to a newly created instance to keep it alive
and perform following method calls on this instance.
__init__ is a method which is being called after an instance get's created
and initializes the instance. You create an instance of a class every time
you call a class like you did with Account().
But if you don't keep a (external) reference to this newly created instance,
you can't address the instance to invoke further methods on it. What happens in your code:
Account().getaccounts() # new instance of Account created,
# i.a. __init__ called, finally getaccounts called
Account().accountsprinting() # new instance of Account created,
# i.a. __init__ called, finally accountsprinting called
Both instances are quickly garbage collected (CPython implementation of Python assumed), because you don't hold an external reference to them, like you would do if you assign a new instance to a name like: acc = Account().
You can check that you get a new object every time you call Account() by comparing identity with Account(1) is Account(1) # False or by looking at the id numbers:
id(Account(1))
# 88311904
id(Account(1))
# 88312408
As a side note:
It doesn't have to be a named reference like acc above, you could also hold an
implicit, unnamed reference by placing the new instances in a list for example and the list would keep the reference to the instances and thus, keeping them alive:
class Account:
def __init__(self, x):
self.x = x
lst = [Account(1), Account(2)]
lst[0].x
# 1
lst[1].x
# 2
self in your code is an (internal) reference connecting (binding) class and instance.
If you assign to self like you do with self.contas[login] = senha within your instance-method getaccounts, you do this only for the actual instance where you are calling getaccounts upon. So when you call Account().getaccounts() and
later Account().accountsprinting() you are doing this for two different
instances and not on the same. Hence the second instance has an empty contas dict because for this instance you didn't call getaccounts() before.

Python pickle load doesn't work when program is run again

I'm trying to store a class instance that contains one variable using pickle.
class Scm1:
keys = {'b':0, 'i':0, 's':0}
The first thing I do in my program is check if the pickled file is exists. If it does, I attempt to load the data using pickle load. If it doesn't (this only happens the very first time the program is run), I create two instances of this class t1 = Scm1() and t2 = Scm1(). Then, in my program, I modify the entries in the keys field. At the end, I attempt to store the instances to a file. For this, I add the two instances to a dictionary -- tmpDict = {'t1':t1, 't2':t2} and execute a pickle dump using tmpDict as the object. When I load the data using pickle load right after the dump, I get what I expect (the data is set to what it was during the program). However, when I run the program again (this time the file exists) and load the data, all entries in the keys field for the two objects (t1 and t2) are 0. Why is it that when I'm able to get the correct results when I do pickle load prior to my program ending and not again when I rerun the program. I'm new to python and so I'm not sure if I'm expecting pickle to work the right way. Sorry for not being able to paste more code snippets as it's for a school assignment.
class Scm1:
keys = {'b':0, 'i':0, 's':0}
keys is a class variable, so all instances of the class share it. Class variables are not pickled when you store the instances, as they do not belong to the instances.
You should use instance attributes instead:
class Scm1:
def __init__(self):
self.keys = {'b':0, 'i':0, 's':0}

Categories