Trying to dynamically change part of a function call - python-3.x

I might be (probably am) using the wrong terminology for some of this, but here's what I'm trying to do.
This is the current code:
with open('matchup' + MatchupStr_Test + '_' + HomeAway[MatchupNum_Test] + 'team_name_NewTest.txt', 'w') as f:
f.write(str(box_scores[MatchupNum[MatchupNum_Test]].home_team.team_name))
I am attempting to take the word home from the second line (.home_team.team_name) and having it dynamically change so the word home would change depending on an array.
Here's what I thought would work, but doesn't.
with open('matchup' + MatchupStr_Test + '_' + HomeAway[MatchupNum_Test] + 'team_name_NewTest.txt', 'w') as f:
f.write(str(box_scores[MatchupNum[MatchupNum_Test]].[HomeAway[MatchupNum_Test]]_team.team_name))
Absolute beginner here, so sorry if I'm wording this in a confusing way. Just trying to have some fun on a Raspbi I wasn't currently using, so it doesn't have to be perfect.

There is a function called getattr, which should work for this. The way getattr works is that it dynamically retrieves a property of an object using the name of the property.
For example:
team_name = HomeAway[MatchupNum_Test]
with open(f'matchup{MatchupStr_Test}_{team_name}team_name_NewTest.txt', 'w') as f:
opposing_team = getattr(box_scores[MatchupNum[MatchupNum_Test]], f'{team_name}_team')
f.write(str(opposing_team.team_name))
In the above, we dynamically retrieve the attribute f'{team_name}_team' from the object box_scores[MatchupNum[MatchupNum_Test]].
So if team_name was "home" for example, then getattr(box_scores[MatchupNum[MatchupNum_Test]], f'{team_name}_team') will be equivalent to box_scores[MatchupNum[MatchupNum_Test]].home_team
The f in front of the strings has nothing to do with the f representing the file. They are called f-strings and are a much nicer way of combining strings, than using +.

Related

Replacing "DoIt.py" script with flexible functions that match DFs on partial string matching of column names [Python3] [Pandas] [Merge]

I spent too much time trying to write a generic solution to a problem (below this). I ran into a couple issues, so I ended up writing a Do-It script, which is here:
# No imports necessary
# set file paths
annofh="/Path/To/Annotation/File.tsv"
datafh="/Path/To/Data/File.tsv"
mergedfh="/Path/To/MergedOutput/File.tsv"
# Read all the annotation data into a dict:
annoD={}
with open(annofh, 'r') as annoObj:
h1=annoObj.readline()
for l in annoObj:
l=l.strip().split('\t')
k=l[0] + ':' + l[1] + ' ' + l[3] + ' ' + l[4]
annoD[k]=l
keyset=set(annoD.keys())
with open(mergedfh, 'w') as oF:
with open(datafh, 'r') as dataObj:
h2=dataObj.readline().strip(); oF.write(h2 + '\t'+ h1) # write the header line to the output file
for l in dataObj:
l=l.strip().split('\t') # Read through the data to be annotated line-by-line:
if "-" in l[13]:
pos=l[13].split('-')
l[13]=pos[0]
key=l[12][3:] + ":" + l[13] + " " + l[15] + " " + l[16]
if key in annoD.keys():
l = l + annoD[key]
oF.write('\t'.join(l) + '\n')
else:
oF.write('\t'.join(l) + '\n')
The function of DoIt.py (which functions correctly, above ^ ) is simple:
first read a file containing annotation information into a dictionary.
read through the data to be annotated line-by-line, and add annotation info. to the data by matching a string constructed by pasting together 4 columns.
As you can see, this script contains index positions, that I obtained by writing a quick awk one-liner, finding the corresponding columns in both files, then putting these into the python script.
Here's the thing. I do this kind of task all the time. I want to write a robust solution that will enable me to automate this task, *even if column names vary. My first goal is to use partial string matching; but eventually it would be nice to be even more robust.
I got part of the way to doing this, but at present the below solution is actually no better than the DoIt.py script...
# Across many projects, the correct columns names vary.
# For example, the name might be "#CHROM" or "Chromosome" or "CHR" for the first DF, But "Chrom" for the second df.
# in any case, if I conduct str.lower() then search for a substring, it should match any of the above options.
MasterColNamesList=["chr", "pos", "ref", "alt"]
def selectFields(h, columnNames):
##### currently this will only fix lower case uppercase problems. need to fix to catch any kind of mapping issue, like a partial string match (e.g., chr will match #CHROM)
indices=[]
h=map(str.lower,h)
for fld in columnNames:
if fld in h:
indices.append(h.index(fld))
#### Now, this will work, but only if the field names are an exact match.
return(indices)
def MergeDFsByCols(DF1, DF2, colnames): # <-- Single set of colnames; no need to use indices
pass
# eventually, need to write the merge statement; I could paste the cols together to a string and make that the indices for both DFs, then match on the indices, for example.
def mergeData(annoData, studyData, MasterColNamesList):
####
import pandas as pd
aDF=pd.read_csv(annoData, header=True, sep='\t')
sDF=pd.read_csv(studyData, header=True, sep='\t')
####
annoFieldIdx=selectFields(list(aVT.columns.values), columnNames1) # currently, columnNames1; should be MasterColNamesList
dataFieldIdx=selectFields(list(sD.columns.values), columnNames2)
####
mergeDFsByCols(aVT, sD):
Now, although the above works, it is actually no more automated than the DoIt.py script, because the columnNames1 and 2 are specific to each file and still need to be found manually ...
What I want to be able to do is enter a list of generic strings that, if processed, will result in the correct columns being pulled from both files, then merge the pandas DFs on those columns.
Greatly appreciate your help.

Is there a way from me to load code from another file in python3?

I'm making a text based python game at the moment. And i'm trying to make a save function so that you can continiue from where you were last time you played. The way i have set it up is that different choises i make ingame is saved to an external file. Is there a way i could make it read that file and having it say for example " story_beginning_eatbreakfeast() " and the main file then calling that function?
def story_beginning_eatbreakfeast():
os.system("cls")
f = open("save.py", "w")
global Color
global Gender
global Weight
global Food_eaten
f.write("Color = " + str(Color) + "\n")
f.write("Gender = " + str(Gender) + "\n")
f.write("Weight = " + str(Weight) + "\n")
f.write("Food_eaten = " + str(Food_eaten) + "\n")
f.write("story_beginning_eatbreakfeast()" + "\n")
The most common way (that I've seen or used, anyway) is to use the json module to encode and write out, then read later and decode, your state data structure, but since all of your information is in discrete globals, that won't work for you. I'd advise that you gather your data into a single dictionary (or class, if that makes any sense to you) so that you can do this. That way, you can deal with saving and restoring state without the code having to know about every little detail about your data.
It's clear that you're just starting out. Try to find a better book on programming. Good practice is to make things like saving state as simple as possible, which means you don't have to rewrite them when (not if) you change your data.

python use for loop to modify list of variables

I have a script using argparse to gather a list of user defined directories. On the command line they may or may not specify a trailing "/" symbol. I'd like to do something up front so that all variables have the trailing "/" so I can reliably do:
# What I want:
with open(args.a + filename, "w") as fileout:
#do stuff
print('whatever', file=fileout)
rather than having to include an extra "/" in the name like this:
# What I have:
with open(args.a + "/" + filename, "w") as fileout:
#do stuff
print('whatever', file=fileout)
I also know that dir/ect/ory and dir//ect//ory are nearly equivalent save some fringe cases which are not applicable, but putting + "/" + all over the place seems wrong/wasteful.
In attempting to make a small function to run on all relevant variable I'm only seeing the desired outcome when I explicitly call the function on the variable not on a list containing the elements.
def trailingSlash(x):
if x.endswith("/"):
return x
else:
return x + "/"
a = 'ok/'
b = 'notok'
c = 'alsonotok'
for _ in [a, b, c]:
_ = trailingSlash(_)
print(a,b,c) #gives ok/ notok alsonotok
c = trailingSlash(c)
print(c) #gives alsonotok/
I understand why changing a list as you are iterating over it is generally bad, and understand that in the for loop the iterator is not actually pointing to a, b, or c. I also know if I wanted the values in a new list i could do something like [trailingSlash(x) for x [a,b,c]] but I need to maintain the a,b,c handle. in I know that I can also solve this by specifically calling x = trailingSlash(x) on every individual variable, but seems like there should be a better way. Any solutions I'm missing?
You can use os.path.join() to ignore the whole issue. It behaves no matter whether there are slashes at the end or not, and is platform-independent as a bonus (that is, it uses \\ instead of / when running on Windows, for example):
import os
...
os.path.join("dir/", "ect", "ory")
# "dir/ect/ory" on Unix, "dir\\ect\\ory" on Windows
In your case you'd want to do
with open(os.path.join(args.a, filename), "w") as fileout:
...

Problem with multivariables in string formatting

I have several files in a folder named t_000.png, t_001.png, t_002.png and so on.
I have made a for-loop to import them using string formatting. But when I use the for-loop I got the error
No such file or directory: '/file/t_0.png'
This is the code that I have used I think I should use multiple %s but I do not understand how.
for i in range(file.shape[0]):
im = Image.open(dir + 't_%s.png' % str(i))
file[i] = im
You need to pad the string with leading zeroes. With the type of formatting you're currently using, this should work:
im = Image.open(dir + 't_%03d.png' % i)
where the format string %03s means "this should have length 3 characters and empty space should be padded by leading zeroes".
You can also use python's other (more recent) string formatting syntax, which is somewhat more succinct:
im = Image.open(f"{dir}t_{i:03d}")
You are not padding the number with zeros, thus you get t_0.png instead of t_000.png.
The recommended way of doing this in Python 3 is via the str.format function:
for i in range(file.shape[0]):
im = Image.open(dir + 't_{:03d}.png'.format(i))
file[i] = im
You can see more examples in the documentation.
Formatted string literals are also an option if you are using Python 3.6 or a more recent version, see Green Cloak Guy's answer for that.
Try this:
import os
for i in range(file.shape[0]):
im = Image.open(os.path.join(dir, f't_{i:03d}.png'))
file[i] = im
(change: f't_{i:03d}.png' to 't_{:03d}.png'.format(i) or 't_%03d.png' % i for versions of Python prior to 3.6).
The trick was to specify a certain number of leading zeros, take a look at the official docs for more info.
Also, you should replace 'dir + file' with the more robust os.path.join(dir, file), which would work regardless of dir ending with a directory separator (i.e. '/' for your platform) or not.
Note also that both dir and file are reserved names in Python and you may want to rename your variables.
Also check that if file is a NumPy array, file[i] = im may not be working.

The output values in one line.(python3/csv.write)

I write a list of dics into a csv file. But the output is in one line. How could witer each value in new lines?
f = open(os.getcwd() + '/friend1.csv','w+',newline='')
for Member in MemberList:
f.write(str(Member))
f.close()
Take a look at the writing example in the csv module of the standard library and this question. Either that, or simply append a newline ("\n") after each write: f.write(str(Member)) + "\n").

Resources