How can I create aliases of objects that inherit only a particular attribute - python-3.x

I'm making a program in python where the user can add a name and number to a list.
I went with saving the entries in a class with 2 attributes and saving the instances of the class as objects in a list:
class NewEntry:
def __init__(self):
self.name = ""
self.number = ""
entry = NewEntry()
entry.name = userinput[1]
entry.number = userinput[2]
entrylist.append(entry)
I'm now trying to add a function called alias, that will save an entry under a new name. The alias is supposed to be connected to the original entry in such a way that changing the number of one of them will change the number of every corresponding alias to the same value.
Here is the code for my attempt at this:
for key in entrylist:
if key.name == userinput[1]:
entry = key
entry.name = userinput[2]
entrylist.append(entry)
This does not work as intended. When creating a new alias, it also changes the .name of key, the I'm attempting to only inherit the number-attribute from.
I've also attempted the following method:
for key in entrylist:
if key.name == userinput[1]:
entry = NewEntry()
entry.name = userinput[2]
entry.number = key.number
entrylist.append(entry)
TL;DR:
I want to be able to create copies of an object where the .number-attribute is the same across all aliases. I want the name to be different in all of these objects, but the number to stay the same. A change in the number of one of the aliases would affect the others as well.

You could link them up by adding an alias_of attribute to signify what a name is an alias of.
If you pair it up with a property for your number attribute then you control which object actually gets set.
class NewEntry:
# I added the attributes as constructor
# arguments since you're setting them anyway
def __init__(self, name='', number=None, alias_of=None):
self.name = name
# since we want self.number to be a property
# it is common practice to use a _ to signify
# that an attribute is private
self._number = number
# here alias will refer to the instance it's
# an alias of. It defaults to None
# or not an alias
self.alias_of = alias_of
# giving a method a #property decorator
# makes it get the attribute from here
# That way you control which number it's getting
#property
def number(self):
if self.alias_of:
return self.alias_of.number
else:
return self._number
# The same is true if you give it a setter
# method. It controls which instances number
# that you set
#number.setter
def number(self, value):
if self.alias_of:
self.alias_of.number = value
else:
self._number = value
entry = NewEntry('my name', number=1)
entry2 = NewEntry('my name 2', number=2)
# here we say that entry3 is an alias of entry
entry3 = NewEntry('my other name', alias_of=entry)
print(entry.number)
print(entry2.number)
# and here entry3 is actually setting entry
# 1's number attribute
entry3.number = 3
print(entry.number)

Related

Making vars() defined variables global in a function

I am running multiple scenarios for my experiment, which requires me to dynamically change the variable names depending upon the Scenario and Class. For that, I have got a few lines of working code, where changing simulations (i.e., Scenario and Class) changes the variable names. However, this code needs to be called everytime after I define my experiment. Code below:
# Funtion
def Moisture_transport(Scenario, Class, delta_crop):
""" (unrelated to this question) """
return Class_direct, Class_sum_cmr
""" Define the Scenario and Class """
Scenario = 2; Class = 1; delta_crop = True # Assign the Scenario, Class and delta_crop
## Few lines of code that needs to run every time without any change
if delta_crop == False:
vars()['Moisture_direct_Scenario_'+str(Scenario)+'_Class_'+str(Class)], vars()['Moisture_with_CMR_Scenario_'+str(Scenario)+'_Class_'+str(Class)] = Moisture_transport(Scenario, Class, delta_crop)
else:
vars()['Moisture_direct_Scenario_'+str(Scenario)+'_Class_'+str(Class)+'_deltacrop'], vars()['Moisture_with_CMR_Scenario_'+str(Scenario)+'_Class_'+str(Class)+'_deltacrop'] = Moisture_transport(Scenario, Class, delta_crop)
Does any one know how to make vars()['variable_name'] global in the function Moisture_transport?
I think this can be simpler still. There is some cost to handling a key so I'd not make them excessively long. Please note the global, where it is and is not used.
Moisture_variables = {}
def Moisture_transport(Scenario, Class, delta_crop):
global Moisture_variables
""" (unrelated to this question) """
#return Class_direct, Class_sum_cmr
Moisture_variables[f"{Scenario} {Class} {delta_crop}"] = (Class_direct, Class_sum_cmr)
You can also sub-dictionary the results although this creates a bit of overhead to checking if sub-dictionaries exist. Note I've deliberately changed (shortened) the variables in the called function to make it clear these are in a different scope.
Moisture_variables = {}
def Moisture_transport(Scenario, Class, delta_crop):
""" (unrelated to this question) """
#return Class_direct, Class_sum_cmr
add_Moisture_Variables(Scenario, Class, delta_crop, Class_direct, Class_sum_cmr)
def add_Moisture_variables(s, c, d, cd, cs):
global Moisture_variables
if s not in Moisture_variables:
Moisture_variables[s] = {}
if c not in Moisture_variables[s]:
Moisture_variables[s][c] = {}
Moisture_variables[s][c][d] = (cd, cs)
Yet another approach if a list works, the double bracket to append a tuple are important.
Moisture_variables = []
def Moisture_transport(Scenario, Class, delta_crop):
global Moisture_variables
""" (unrelated to this question) """
#return Class_direct, Class_sum_cmr
Moisture_variables.append((Scenario, Class, delta_crop, Class_direct, Class_sum_cmr))
The choice of which approach works best depends on how you wish to recover the data.
Defining a dictionary is more efficient in the following case to hold all the variables as string, which can be called with conditions, i.e., Scenario or Class.
#Add a last line to the original function
def Moisture_transport(Scenario, Class, delta_crop):
""" (unrelated to this question) """
#return Class_direct, Class_sum_cmr
variables_dict(Class_direct, Class_sum_cmr,delta_crop)
#Add a normal dictionary and a variable name defining funtion
Moisture_variables = {}
def variables_dict(Class_direct, Class_sum_cmr, delta_crop):
if delta_crop == False:
Moisture_variables['Moisture_direct_Scenario_{0}_Class_{1}'.format(Scenario,Class)] = Class_direct
Moisture_variables['Moisture_with_CMR_Scenario_{0}_Class_{1}'.format(Scenario,Class)] = Class_sum_cmr
else:
Moisture_variables['Moisture_direct_Scenario_{0}_Class_{1}_deltacrop'.format(Scenario,Class)] = Class_direct
Moisture_variables['Moisture_with_CMR_Scenario_{0}_Class_{1}_deltacrop'.format(Scenario,Class)] = Class_sum_cmr
After that, you can run the function Moisture_transport() as it is, and not worry about defining the variables outside the function, i.e., code after ## Few lines of code that needs to run every time without any change from the original question is not needed. E.g.:
""" Define the Scenario and Class """
Scenario = 1; Class = 0; delta_crop = False
Moisture_transport(Scenario, Class, delta_crop)

Accessing dynamically created Variables set in a for loop

TLDR FIX: put your objects(or list of objects) into a dictionary
Simply put, i am creating a bunch of pages in a wxpython notebook dynamically and on each page i am placing further widgets and sizers and such. my issue is that if i want to access a variable set in the for loop when it creates the page, i am unable to because once the for loops runs again the variable will be reset and used to populate the new page.
something along the lines of this
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.SetSize((400, 300))
self.notebook_1 = wx.Notebook(self, wx.ID_ANY)
self.text_button = wx.Button(self, wx.ID_ANY, "get text")
self.Bind(wx.EVT_BUTTON, self.get_text_handler, self.text_button)
self.__set_properties()
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_1.Add(self.text_button, 0, 0, 0)
sizer_1.Add(self.notebook_1, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
numlist = (1, 2, 3, 4)
#global page_dictionary
#page_dictionary = {}
for i in numlist:
self.notebook_1_pane_2 = wx.Panel(self.notebook_1, wx.ID_ANY)
self.notebook_1_pane_2.SetSizer(sizer_2)
self.notebook_1.AddPage(self.notebook_1_pane_2, str(i))
self.label_1 = wx.TextCtrl(self.notebook_1_pane_2, wx.ID_ANY, str(i))
sizer_2.Add(self.label_1, 0, 0, 0)
self.Layout()
#page_k = self.label_1.GetValue()
#page_k_good = page_k + str(i)
#page_dictionary[page_k_good] = i
#print(page_dictionary)
def __set_properties(self):
self.SetTitle("frame")
def get_text_handler(self, event): # wxGlade: MyFrame.<event_handler>
print(self.label_1.GetValue())
the only difference between this and what im actually running, is that the numlist variable is actually generating a random number of values dynamically
my issue is that if i want to reference any of the labels ive set in previous pages, i can't because the self.label_1 variable gets reset to the entry on the last page. so if i want to access the label on say page number 1 the label variable is set to the values of the last page created in the for loop. its like once the label is placed and populated on the wxNotebook page its just gone and what is in that widget isn't accessible anymore.
so how would i, say for example, print(self.label_1) of page 1 in the notebook when self.label_1 was rewritten to match the label in the last page generated?
was messing around with dictionaries so i commented that out.
how would i be able to click that button, and have it get the text from the boxes in current pages?
Final edit hopefully:
how would i go about assigning multiple objects into the dictionary? would i need to just assign the value i as a list (label_1, Label_2) then when needing to call methods on them just reference them from the list, that is now in the dictionary?
yup, that did it, put them into the dictionary as a list, can pull the list when needed.
I am still at a loss as to exactly what you are trying to do, but based on an educated guess I think you are trying to preserve the identity of objects created in a loop so that they maybe accessed outside of the loop. With this in mind, here is an example of how this can be done. Here is a very simplistic example:
import random as rnd #used to create a random number for each object
# just a list of names to be scanned in for loop
lst = ["a", "b", "c", "d"]
# Just an illustrative object for demo purposes
class SillyClass:
def __init__(self, name, nmb):
self._id = name
self._nmb = nmb
#property
def name(self):
return self._id
#property
def number(self):
return self._nmb
# create a list of objects for later reference
def build_objects(lst):
rslt = []
for itm in lst:
rslt.append(SillyClass(itm, rnd.randrange(5, 200)))
return rslt
# show use of list to print object data
obj_lst = build_objects(lst)
# Show the contents of obj_lst
print(obj_lst, "\n")
# show access of object attributes
for obj in obj_lst:
print(f"Object: {obj.name} contains {obj.number}")
The result of running this code is:
[<__main__.SillyClass object at 0x0000022C5CB9E9D0>, <__main__.SillyClass object at 0x0000022C5CB9EFD0>, <__main__.SillyClass object at 0x0000022C5CB9E670>, <__main__.SillyClass object at 0x0000022C5CC42370>]
Object: a contains 187
Object: b contains 164
Object: c contains 97
Object: d contains 52
This illustrates that obj_lst contains a list of objects, which then can be used to access the attributes of the object. You can utilize this approach or possibly associate the object to a specific identifier using a dict structure. What ever you need to best serve your purposes.

How to print class variables in a list

So I am very new to coding and started with python, I am trying to build a class in a program that puts together a DnD party by randomising their attributes. So far I can get the program to initialise instances of the party members and just give the user a prompt on how many of the hero's to choose from they would like in their party. My issue is that after setting the lists up and getting everything in place. I am unable to print any of the attributes of the individual heros. Regardless of whether I am calling them from within the lists or if I am directly trying to print them. I have tried using __str__ to create strings of the attributes but I am clearly missing something. Any help would be greatly appreciated.
import random
class Party:
def __init__(self, name="", race="", alignment="", class_=""):
self.name = name
while name == "":
name = random.choice(names)
# print(name)
self.race = race
while race == "":
race = random.choice(races)
# print(race)
self.alignment = alignment
while alignment == "":
alignment = random.choice(alignments)
# print(alignment)
self.class_ = class_
while class_ == "":
class_ = random.choice(classes)
# print(class_)
def character_stats(self):
return "{} - {} - {} - {}".format(self.name, self.race, self.class_, self.alignment)
Each attribute pulls a random value from a list. My format statement is the latest attempt to get the values of the attributes to print rather than the object/attributes instead.
I apologise if any of the terminology is wrong, very very new to this
You are not assigning anything else but the input, (in this case being an empty string "" to the attribuytes. In your minimal example you have this constructor:
class Party:
def __init__(self, name=""):
self.name = name
while name == "":
name = random.choice(names)
After you randomly assign a new name from names, you should assign it to self, otherwise the local variable just goes out of scope when the __init__ method finishes. This code snippet should work:
class Party:
def __init__(self, name=""):
while name == "":
name = random.choice(names)
# Now we assign the local variable as
# an attribute
self.name = name

How to add a set of multiple values to a key?

I created a class which is basically a hobby book. The book can be accessed by two methods, enter(n,h) which takes a name and keep adding hobbies to that name(one name can have multiple hobbies). The other method returns a set of hobbies for a particular name. My hobby book is storing every hobby that I insert to one name. Can someone help me fixing it?
class Hobby:
def __init__(self):
self.dic={}
self.hby=set()
def enter(self,n,h):
if n not in self.dic.items():
self.dic[n]=self.hby
for k in self.dic.items():
self.hby.add(h)
def lookup(self,n):
return self.dic[n]
I tried running following cases
d = Hobby(); d.enter('Roj', 'soccer'); d.lookup('Roj')
{'soccer'}
d.enter('Max', 'reading'); d.lookup('Max')
{'reading', 'soccer'} #should return just reading
d.enter('Roj', 'music'); d.lookup('Roj')
{'reading', 'soccer','music'} #should return soccer and music
Why are you re-inventing a dict here? Why are you using a separate set to which you always add values, and reference it to every key which ensures that it always returns the same set on a lookup?
Don't reinvent the wheel, use collections.defaultdict:
import collections
d = collections.defaultdict(set)
d["Roj"].add("soccer")
d["Roj"]
# {'soccer'}
d["Max"].add("reading")
d["Max"]
# {'reading'}
d["Roj"].add("music")
d["Roj"]
# {'soccer', 'music'}
.
UPDATE - If you really want to do it through your own class (and before you do, watch Stop Writing Classes!), you can do it as:
class Hobby(object):
def __init__(self):
self.container = {}
def enter(self, n, h):
if n not in self.container:
self.container[n] = {h}
else:
self.container[n].add(h)
def lookup(self, n):
return self.container.get(n, None)
d = Hobby()
d.enter("Roj", "soccer")
d.lookup("Roj")
# {'soccer'}
d.enter("Max", "reading")
d.lookup("Max")
# {'reading'}
d.enter("Roj", "music")
d.lookup("Roj")
# {'soccer', 'music'}
Note how no extra set is used here - every dict key gets its own set to populate.

Dynamically add methods to a class in Python 3.0

I'm trying to write a Database Abstraction Layer in Python which lets you construct SQL statments using chained function calls such as:
results = db.search("book")
.author("J. K. Rowling")
.price("<40.00")
.title("Harry")
.execute()
but I am running into problems when I try to dynamically add the required methods to the db class.
Here is the important parts of my code:
import inspect
def myName():
return inspect.stack()[1][3]
class Search():
def __init__(self, family):
self.family = family
self.options = ['price', 'name', 'author', 'genre']
#self.options is generated based on family, but this is an example
for opt in self.options:
self.__dict__[opt] = self.__Set__
self.conditions = {}
def __Set__(self, value):
self.conditions[myName()] = value
return self
def execute(self):
return self.conditions
However, when I run the example such as:
print(db.search("book").price(">4.00").execute())
outputs:
{'__Set__': 'harry'}
Am I going about this the wrong way? Is there a better way to get the name of the function being called or to somehow make a 'hard copy' of the function?
You can simply add the search functions (methods) after the class is created:
class Search: # The class does not include the search methods, at first
def __init__(self):
self.conditions = {}
def make_set_condition(option): # Factory function that generates a "condition setter" for "option"
def set_cond(self, value):
self.conditions[option] = value
return self
return set_cond
for option in ('price', 'name'): # The class is extended with additional condition setters
setattr(Search, option, make_set_condition(option))
Search().name("Nice name").price('$3').conditions # Example
{'price': '$3', 'name': 'Nice name'}
PS: This class has an __init__() method that does not have the family parameter (the condition setters are dynamically added at runtime, but are added to the class, not to each instance separately). If Search objects with different condition setters need to be created, then the following variation on the above method works (the __init__() method has a family parameter):
import types
class Search: # The class does not include the search methods, at first
def __init__(self, family):
self.conditions = {}
for option in family: # The class is extended with additional condition setters
# The new 'option' attributes must be methods, not regular functions:
setattr(self, option, types.MethodType(make_set_condition(option), self))
def make_set_condition(option): # Factory function that generates a "condition setter" for "option"
def set_cond(self, value):
self.conditions[option] = value
return self
return set_cond
>>> o0 = Search(('price', 'name')) # Example
>>> o0.name("Nice name").price('$3').conditions
{'price': '$3', 'name': 'Nice name'}
>>> dir(o0) # Each Search object has its own condition setters (here: name and price)
['__doc__', '__init__', '__module__', 'conditions', 'name', 'price']
>>> o1 = Search(('director', 'style'))
>>> o1.director("Louis L").conditions # New method name
{'director': 'Louis L'}
>>> dir(o1) # Each Search object has its own condition setters (here: director and style)
['__doc__', '__init__', '__module__', 'conditions', 'director', 'style']
Reference: http://docs.python.org/howto/descriptor.html#functions-and-methods
If you really need search methods that know about the name of the attribute they are stored in, you can simply set it in make_set_condition() with
set_cond.__name__ = option # Sets the function name
(just before the return set_cond). Before doing this, method Search.name has the following name:
>>> Search.price
<function set_cond at 0x107f832f8>
after setting its __name__ attribute, you get a different name:
>>> Search.price
<function price at 0x107f83490>
Setting the method name this way makes possible error messages involving the method easier to understand.
Firstly, you are not adding anything to the class, you are adding it to the instance.
Secondly, you don't need to access dict. The self.__dict__[opt] = self.__Set__ is better done with setattr(self, opt, self.__Set__).
Thirdly, don't use __xxx__ as attribute names. Those are reserved for Python-internal use.
Fourthly, as you noticed, Python is not easily fooled. The internal name of the method you call is still __Set__, even though you access it under a different name. :-) The name is set when you define the method as a part of the def statement.
You probably want to create and set the options methods with a metaclass. You also might want to actually create those methods instead of trying to use one method for all of them. If you really want to use only one __getattr__ is the way, but it can be a bit fiddly, I generally recommend against it. Lambdas or other dynamically generated methods are probably better.
Here is some working code to get you started (not the whole program you were trying to write, but something that shows how the parts can fit together):
class Assign:
def __init__(self, searchobj, key):
self.searchobj = searchobj
self.key = key
def __call__(self, value):
self.searchobj.conditions[self.key] = value
return self.searchobj
class Book():
def __init__(self, family):
self.family = family
self.options = ['price', 'name', 'author', 'genre']
self.conditions = {}
def __getattr__(self, key):
if key in self.options:
return Assign(self, key)
raise RuntimeError('There is no option for: %s' % key)
def execute(self):
# XXX do something with the conditions.
return self.conditions
b = Book('book')
print(b.price(">4.00").author('J. K. Rowling').execute())

Resources