issue with for loop in python only gets the last item - python

I'm a beginner in python, currently I'm trying to automate filling website field using selenium.
I'm trying to iterate over nested lists using for loop but always get only the last element. Any suggestions why?
fields = [['a','b','c'],['x','y','z']]
for i in range(len(fields)):
driver.find_element_by_xpath("element").send_keys(fields[i][0],fields[i[1],fields[i][2])
driver.find_element_by_xpath("element_save").click()
#then loop and iterate through 2nd nested list
# OUTPUT = x,y,z
I expect to iterate starting with index 0 to the end of the list.

You don't need range(len(list_)) for iterating over indeces only.
Usual for will do. You can also unpack list with *:
fields = [['a','b','c'],['x','y','z']]
len_ = len(fields)
for i in range(len_):
driver.find_element_by_xpath("element").send_keys(*fields[i])
You could also iterate trhrough the values of the fields itself:
fields = [['a','b','c'],['x','y','z']]
for field in fields:
driver.find_element_by_xpath("element").send_keys(*field)

Firstly there is a bug in your program as you have written it:
fields = [['a','b','c'],['x','y','z']]
for i, v in enumerate(fields):
driver.find_element_by_xpath("element").send_keys(fields[i][0],fields[i[1],fields[i][2])
^ # No closing ]
Secondly there is a term that Python developers like to throw around: Pythonic Code.
We like to write short concise code that favors readability over squeezing every last inch of performance.
Referring to this you should change your code as it is unnecessarily cluttered and you are not even utilizing the value element of enumerate. I would recommend the following:
fields = [['a','b','c'],['x','y','z']]
for field in fields:
name, age, height = field # Replace this line with whatever the fields represent
driver.find_element_by_xpath("element").send_keys(name, age, height)
This code is short, concise, and above all extremely readable to someone else.
Note: Replace the name, age, height with whatever they represent in your program.
If in fact this didn't solve your problem, your problem may not be with python but with selenium itself and that is out of the scope of this question. You can test this with simply printing the values before feeding it to the selenium function like this:
fields = [['a','b','c'],['x','y','z']]
for field in fields:
name, age, height = field # Replace this line with whatever the fields represent
print(name, age, height)
driver.find_element_by_xpath("element").send_keys(name, age, height)
Hope this helps.

Related

Append class records to a list

I load a row of data in a class, row by row with a loop. I'd like to append each row to a list.
class Biz:
def __init__(self, dba_name, legal_name):
self.dba_name = dba_name
self.legal_name = legal_name
MasterList = []
File_From_Excel = pd.read_excel('C:\file.xlsx')
for index, row in File_From_Excel.iterrows():
record = Biz(row['Field 1'], row['Field 2'])
MasterList.append(record)
print(MasterList)
When I run code like this, I do not get an error, but I get info like this printed:
"[<main.Biz object at 0x0C11BFB0>, <main.Biz object at 0x00BDED50>]"
I'm a newbie and I haven't figured out how to overcome this one. Thank you!
You are printing a list of class instances, so the output is their memory addresses. What you probably want instead is the attributes of these instances. It can be achieved as follows (for illustrative purposes):
# this goes at the end of your code, outside of the loop!
print([[record.dba_name, record.legal_name] for record in MasterList])
This approach is by no means optimal and and will give you memory issues if there are a lot of elements in MasterList. In that case you would want to use either a generator or a class iterator.
Edit: Come to think of it, there is no need for a generator here since a simple for loop can iterate over the list:
for record in MasterList:
print([record.dba_name, record.legal_name], end=' ')

Parsing and arranging text in python

I'm having some trouble figuring out the best implementation
I have data in file in this format:
|serial #|machine_name|machine_owner|
If a machine_owner has multiple machines, I'd like the machines displayed in a comma separated list in the field. so that.
|1234|Fred Flinstone|mach1|
|5678|Barney Rubble|mach2|
|1313|Barney Rubble|mach3|
|3838|Barney Rubble|mach4|
|1212|Betty Rubble|mach5|
Looks like this:
|Fred Flinstone|mach1|
|Barney Rubble|mach2,mach3,mach4|
|Betty Rubble|mach5|
Any hints on how to approach this would be appreciated.
You can use dict as temporary container to group by name and then print it in desired format:
import re
s = """|1234|Fred Flinstone|mach1|
|5678|Barney Rubble|mach2|
|1313|Barney Rubble||mach3|
|3838|Barney Rubble||mach4|
|1212|Betty Rubble|mach5|"""
results = {}
for line in s.splitlines():
_, name, mach = re.split(r"\|+", line.strip("|"))
if name in results:
results[name].append(mach)
else:
results[name] = [mach]
for name, mach in results.items():
print(f"|{name}|{','.join(mach)}|")
You need to store all the machines names in a list. And every time you want to append a machine name, you run a function to make sure that the name is not already in the list, so that it will not put it again in the list.
After storing them in an array called data. Iterate over the names. And use this function:
data[i] .append( [ ] )
To add a list after each machine name stored in the i'th place.
Once your done, iterate over the names and find them in in the file, then append the owner.
All of this can be done in 2 steps.

How can I take a text file and create a triple nested list from it with tkinter python

I'm making a program that allows the user to log loot they receive from monsters in an MMO. I have the drop tables for each monster stored in text files. I've tried a few different formats but I still can't pin down exactly how to take that information into python and store it into a list of lists of lists.
The text file is formatted like this
item 1*4,5,8*ns
item 2*3*s
item 3*90,34*ns
The item # is the name of the item, the numbers are different quantities that can be dropped, and the s/ns is whether the item is stackable or not stackable in game.
I want the entire drop table of the monster to be stored in a list called currentDropTable so that I can reference the names and quantities of the items to pull photos and log the quantities dropped and stuff.
The list for the above example should look like this
[["item 1", ["4","5","8"], "ns"], ["item 2", ["2","3"], "s"], ["item 3", ["90","34"], "ns"]]
That way, I can reference currentDropTable[0][0] to get the name of an item, or if I want to log a drop of 4 of item 1, I can use currentDropTable[0][1][0].
I hope this makes sense, I've tried the following and it almost works, but I don't know what to add or change to get the result I want.
def convert_drop_table(list):
global currentDropTable
currentDropTable = []
for i in list:
item = i.split('*')
currentDropTable.append(item)
dropTableFile = open("droptable.txt", "r").read().split('\n')
convert_drop_table(dropTableFile)
print(currentDropTable)
This prints everything properly except the quantities are still an entity without being a list, so it would look like
[['item 1', '4,5,8', 'ns'], ['item 2', '2,3', 's']...etc]
I've tried nesting another for j in i, split(',') but then that breaks up everything, not just the list of quantities.
I hope I was clear, if I need to clarify anything let me know. This is the first time I've posted on here, usually I can just find another solution from the past but I haven't been able to find anyone who is trying to do or doing what I want to do.
Thank you.
You want to split only the second entity by ',' so you don't need another loop. Since you know that item = i.split('*') returns a list of 3 items, you can simply change your innermost for-loop as follows,
for i in list:
item = i.split('*')
item[1] = item[1].split(',')
currentDropTable.append(item)
Here you replace the second element of item with a list of the quantities.
You only need to split second element from that list.
def convert_drop_table(list):
global currentDropTable
currentDropTable = []
for i in list:
item = i.split('*')
item[1] = item[1].split(',')
currentDropTable.append(item)
The first thing I feel bound to say is that it's usually a good idea to avoid using global variables in any language. Errors involving them can be hard to track down. In fact you could simply omit that function convert_drop_table from your code and do what you need in-line. Then readers aren't obliged to look elsewhere to find out what it does.
And here's yet another way to parse those lines! :) Look for the asterisks then use their positions to select what you want.
currentDropTable = []
with open('droptable.txt') as droptable:
for line in droptable:
line = line.strip()
p = line.find('*')
q = line.rfind('*')
currentDropTable.append([line[0:p], line[1+p:q], line[1+q:]])
print (currentDropTable)

ArcMap Field Calculator Program to create Unique ID's

I'm using the Field Calculator in ArcMap and
I need to create a unique ID for every storm drain in my county.
An ID Should look something like this: 16-I-003
The first number is the municipal number which is in the column/field titled "Munic"
The letter is using the letter in the column/field titled "Point"
The last number is simply just 1 to however many drains there are in a municipality.
So far I have:
rec=0
def autoIncrement()
pStart=1
pInterval=1
if(rec==0):
rec=pStart
else:
rec=rec+pInterval
return "16-I-" '{0:03}'.format(rec)
So you can see that I have manually been typing in the municipal number, the letter, and the hyphens. But I would like to use the fields: Munic and Point so I don't have to manually type them in each time it changes.
I'm a beginner when it comes to python and ArcMap, so please dumb things down a little.
I'm not familiar with the ArcMap, so can't directly help you, but you might just change your function to a generator as such:
def StormDrainIDGenerator():
rec = 0
while (rec < 99):
rec += 1
yield "16-I-" '{0:03}'.format(rec)
If you are ok with that, then parameterize the generator to accept the Munic and Point values and use them in your formatting string. You probably should also parameterize the ending value as well.
Use of a generator will allow you to drop it into any later expression that accepts an iterable, so you could create a list of such simply by saying list(StormDrainIDGenerator()).
Is your question on how to get Munic and Point values into the string ID? using .format()?
I think you can use following code to do that.
def autoIncrement(a,b):
global rec
pStart=1
pInterval=1
if(rec==0):
rec=pStart
else:
rec=rec+pInterval
r = "{1}-{2}-{0:03}".format(a,b,rec)
return r
and call
autoIncrement( !Munic! , !Point! )
The r = "{1}-{2}-{0:03}".format(a,b,rec) just replaces the {}s with values of variables a,b which are actually the values of Munic and Point passed to the function.

How to transform a column field into a list

I'm trying to make a simple field calculation with ArcGIS 9.3:
New field = Old field - Old field(first value)
which in Calculate Field with python code should be
Expression: !Old field! - first
Code Block: list= [AngOriz] first = list[0]
The error is
ERROR 000539: Runtime error : name 'AngOriz' is not defined Failed to execute (Calculate Field).
How could I tranform a column field into a list?
I've tried this way
Expression:
makeCalc( !AngOriz!, !AngOriz!)
Code Block:
def makeCalc(x, y):
first_value = y.split(' , ')[0]
return x-first_value
but still I get:
ERROR 000539: Error running expression: makeCalc( 43.01841, 43.01841) : 'float' object has no attribute 'split' Failed to execute (Calculate Field).
I need to calculate the difference between a value of a column and the first value of the same column (values are floating points).
The purpose is to calculate the displacement of different survey points on a landslide.
I'm not getting the whole picture, what do you mean by Old field(first value)?? is Old field a text field with multiple values on it separated by commas or something like that? and if so, how come you use Old field before that?
But anyway, the error you're getting is because on the code block you haven't defined AngOriz... the best way to do what you're trying to do is inside a function, which gets the necessary values to work with as parameters, and returns the value to the field, here is an example:
on the Expression field:
my_function(!Old field!, !some_other_field!)
and on the codeblock:
def my_function(old_field, other_field):
# supposing other_field is a list of int-values in a string = "1, 23, 5"
first_value = int(other_field.split(', ')[0])
return old_field - first_value
Sorry for taking so long to answer. Had a busy week :-) Ok, I thought you meant a string field, not a float field, so forget about the split-stuff. I'm leaving the answer above, as it may help someone else looking for how to transform a (string) field into a list and use its values for a field calculation.
If I'm getting this right now, you mean the value of the first row of column AngOriz, right? So if this value doesn't change, then just use it "hard coded" on the function like this:
on the Expression field:
my_function(!AngOriz!)
and on the codeblock:
def my_function(old_field):
return old_field - 34.123 # just type the value of the first row here
If you don't want to "hard code" the value of the first row, then you would have to retrieve it somehow, but I'm not sure how you could do this on the field calculator. Probably you would have to solve this in a completely different way. With a script in arcpy or so.
Update:
There is also the possibility of using global variables inside the field calculator - this means variables that are persistent between function calls. Because the function gets called by the field calculator for each row, and normally all variables inside a function "die" between calls, it is impossible to "remember" something on local variables. But you could use a global variable to save the first row's value and use it on the other following calls. I haven't tried it, but maybe something like this would work:
firstValue = None
def my_function(old_field):
global firstValue
if firstValue == None: # this will be true only if this is the first call/row ...
firstValue = old_field # ... save the value on the global variable
return old_field - firstValue

Categories

Resources