Passing a list to another function - python

I'm using a python module https://gis.stackexchange.com/a/5943/16793 to export a list of feature classes within an SDE database.
import os, csv, arcpy, arcplus
>>> fcs = arcplus.listAllFeatureClasses("Database Connections\\Connection to oracle.sde\\BASINS.ACF")
The output is a list:
[u'BASINS.ACF_FL_SUB', u'BASINS.ACF_CHATTAHOOCHEE_BASIN', u'BASINS.ACF_CHIPOLA_BASIN', u'BASINS.ACF_CHIPOLA_AL']
In order to pass this list to another function, I've prepended a string to each element in the list:
mylist = ['Database Connections\\Connection to oracle.sde\{0}'.format(i) for i in fcs]
which looks like:
print mylist[0]
Database Connections\Connection to oracle.sde\BASINS.ACF_FL_SUB
I'd like to pass this list to another function arcpy.ListFields(dataset) which will return the fields of each feature class:
fn = [f.name for f in arcpy.ListFields(mylist[0])]
>>> print fn
[u'OBJECTID', u'HUC', u'BASIN', u'NAME', u'ACRES', u'SHAPE', u'SHAPE.AREA', u'SHAPE.LEN']
I'm trying to figure out how to pass the list in fcs to the function arcpy.ListFields and write the results to csv file, but the structure of the loop needed is really giving me trouble. I'm a novice at this and the Python documentation is getting me turned around. Any pointers would be helpful.
_________________________ETC__________________________________________________
#Tony Your solution worked great. Although I try to use listAllFeatureClasses on the larger geodatabase, I don't have insufficient privileges to read some of the attributes, which gives an IOError: Database Connections\Connection to oracle.sde\LAND.LANDS\LAND_POINTS does not exist. I'm working on how to handle this, and continue to the next feature class in the list. Maybe Try/Continue?

To call the arcpy.ListFields for every item in a list :
fns = [[f.name for f in arcpy.ListFields( list_entry )] for list_entry in mylist]
This will give you a list of lists, where fns[0] are the functions for entry mylist[0]
what might be easier to work with is a dictionary :
fns_dict = dict( [ (list_entry, [f.name for f in arcpy.ListFields( list_entry ) ] )
for list_entry in mylist ] )
Using the data in your example :
fns_dict["BASINS.ACF_FL_SUB"] should be
[u'OBJECTID', u'HUC', u'BASIN', u'NAME', u'ACRES', u'SHAPE', u'SHAPE.AREA', u'SHAPE.LEN']

Related

Unable to store values in an array from for loop in python

First I tried directly storing values from a list having the name 'data' in an array variable 'c' using loop but 'none' got printed
for i in data:
print(i['name'])
c=i['name']
Here print(i['name']) perfectly worked and output appeared
This is the working ouput
Then I printed c in order to print the values generated using loop. The ouput came as none.
print(c)
Then I tried another way by storing the values and making the array iterable at the same time using for loop. An error occurred which I was unable to resolve.
for i in data:
b[c]=i['name']
c=c+1
The error apeared is as follow-
I have tried two ways, if there is any other way please help me out as I am new to python.
It looks like the variable 'data' is a dictionary.
If you want to add each name from that dictionary to a list:
# create a new list variable
names = []
for i in data:
name = i['name']
print(name)
# add the name to the list
names.append(name)
# output the new list
print(names)
Assuming your data object here is a list like [{"name": "Mr. Green", ...}, {"name": "Mr. Blue", ...}].
If your goal is to end up with c == ["Mr. Green", "Mr. Blue"], then you're looking for something like:
c = []
for i in data:
c.append(i['name'])
print(c)
or you can accomplish tasks like these using list comprehensions like:
c = [i['name'] for i in data]
print(c)
The first code example you posted is iterating through the items in data and reassigning the value of c to each item's name key - not adding them to a list("array"). Without knowing more about the code you ran to produce the screenshot and/or the contents of data, it's hard to say why you're seeing print(c) produce None. I'd guess the last item in data is something like {"name": None, ...} which if it's coming from JSON is possible if the value is null. Small note: I'd generally use .get("name") here instead so that your program doesn't blow up if an item is missing a "name" key entirely.
For your second code example, the error is different but I think falls along a similar logical fallacy which is that lists in python function differently from primitives(things like numbers and strings). For the interpreter to know that b or c are supposed to be lists("arrays"), they need to be instantiated differently and they have their own set of syntax/methods for mutation. For example, like arrays in other languages, lists are indexed by position so doing b[c] = <something> will only work if c is an integer. So something similar to your second example that would also produce a list of names like my above would be:
b = [None] * len(data)
c = 0
for i in data:
b[c]=i['name']
c=c+1
Note that if you only initialize b = [], you get an IndexError: list assignment index out of range on the initial assignment of b[0] = "some name" because the list is of size 0.
Add
b = []
above your first line of code. As the error is saying that you have not (and correctly so) defined the list to append.
I personally would use list comprehension here
b = [obj['name'] for obj in data]
where obj is i as you have defined it.

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 to write dictionary comprehension in this complicated case?

An example is artificial, but I had similar problems many times.
db_file_names = ['f1', 'f2'] # list of database files
def make_report(filename):
# read the database and prepare some report object
return report_object
Now I want to construct a dictionary: db_version -> number_of_tables. The report object contains all the information I need.
The dictionary comprehension could look like:
d = {
make_report(filename).db_version: make_report(filename).num_tables
for filename in db_file_names
}
This approach sometimes works, but is very inefficient: the report is prepared twice for each database.
To avoid this inefficiency I usually use one of the following approaches:
Use temporary storage:
reports = [make_report(filename) for filename in db_file_names]
d = {r.db_version: r.num_tables for r in reports}
Or use some adaptor-generator:
def gen_data():
for filename in db_file_names:
report = make_report(filename)
yield report.db_version, report.num_tables
d = {dat[0]: dat[1] for dat in gen_data()}
But it's usually only after I write some wrong comprehension, think over and realize, that clean and simple comprehension isn't possible in this case.
The question is, is there a better way to create required dictionary in such situations?
Since yesterday (when I decided to post this question) I invented one more approach, which I like more then all others:
d = {
report.db_version: report.num_tables
for filename in db_file_names
for report in [make_report(filename), ]
}
but even this one looks not very good.
You can use:
d = {
r.db_version: r.num_tables
for r in map(make_report, db_file_names)
}
Note that in Python 3, map gives an iterator, thus there is no unnecessary storage cost.
Here's a functional way:
from operator import attrgetter
res = dict(map(attrgetter('db_version', 'num_tables'),
map(make_report, db_file_names)))
Unfortunately, functional composition is not part of the standard library, but the 3rd party toolz does offer this feature:
from toolz import compose
foo = compose(attrgetter('db_version', 'num_tables'), make_report)
res = dict(map(foo, db_file_names))
Conceptually, you can think of these functional solutions outputting an iterable of tuples, which can then be fed directly to dict.

TypeError: list indices must be integers or slices, not str

I've got two lists that I want to merge into a single array and finally put it in a csv file.
How I can avoid this error :
def fill_csv(self, array_urls, array_dates, csv_file_path):
result_array = []
array_length = str(len(array_dates))
# We fill the CSV file
file = open(csv_file_path, "w")
csv_file = csv.writer(file, delimiter=';', lineterminator='\n')
# We merge the two arrays in one
for i in array_length:
result_array[i][0].append(array_urls[i])
result_array[i][1].append(array_dates[i])
i += 1
csv_file.writerows(result_array)
And got :
File "C:\Users\--\gcscan.py", line 63, in fill_csv
result_array[i][0].append(array_urls[i])
TypeError: list indices must be integers or slices, not str
How can my count work ?
First, array_length should be an integer and not a string:
array_length = len(array_dates)
Second, your for loop should be constructed using range:
for i in range(array_length): # Use `xrange` for python 2.
Third, i will increment automatically, so delete the following line:
i += 1
Note, one could also just zip the two lists given that they have the same length:
import csv
dates = ['2020-01-01', '2020-01-02', '2020-01-03']
urls = ['www.abc.com', 'www.cnn.com', 'www.nbc.com']
csv_file_patch = '/path/to/filename.csv'
with open(csv_file_patch, 'w') as fout:
csv_file = csv.writer(fout, delimiter=';', lineterminator='\n')
result_array = zip(dates, urls)
csv_file.writerows(result_array)
Follow up on Abdeali Chandanwala answer above (couldn't comment because rep<50) -
TL;DR: I was trying to iterate through a list of dictionaries incorrectly by focusing to iterate over the keys in the dictionary but instead had to iterate over the dictionaries themselves!
I came across the same error while having a structure like this:
{
"Data":[
{
"RoomCode":"10",
"Name":"Rohit",
"Email":"rohit#123.com"
},
{
"RoomCode":"20"
"Name":"Karan",
"Email":"karan#123.com"
}
]
}
And I was trying to append the names in a list like this-
Fixed it by-
I had same error and the mistake was that I had added list and dictionary into the same list (object) and when was iterating over the list of dictionaries and hit a list type object then I would get this error since I was trying to access keys within each dictionary.
I had to made sure that I only added dictionary objects to that list
In my case I was trying to change the value of a dict key but since my dict was there in a for loop and was getting changed to type list i was getting the same error.
for value in source_list:
my_dict['my_key']=some_val
dict=list(mydict)
exctraction0 = dict[0]
i resolved it by making sure the type of dict remains the same by making a deepcopy and re-initializing after every iteration(that is what the use-case was all about).
copy_dict = copy.deepcopy(my_dict)
for value in source_list:
my_dict =copy.deepcopy(copy_dict)
my_dict['my_key']=some_val
dict=list(mydict)
exctraction0 = dict[0]
I received this error overloading a function in python where one function wrapped another:
def getsomething(build_datastruct_inputs : list[str]) -> int:
# builds datastruct and calls getsomething
return getsomething(buildit(build_datastruct_inputs))
def getsomething(datastruct : list[int]) -> int:
# code
# received this error on first use of 'datastruct'
Fix was to not overload and use unique method name.
def getsomething_build(build_datastruct_inputs : list[str]) -> int:
# builds datastruct and calls getsomething
return getsomething_ds(buildit(build_datastruct_inputs))
def getsomething_ds(datastruct : list[int]) -> int:
# code
# works fine again regardless of whether invoked directly/indirectly
Another fix could be to use python multipledispatch package which will let you overload and figures this out for you.
Was a bit confusing because where the error was occuring (nor message) corresponded to what cause was. I thought I had seen that python supported overloading natively but now I've learned it's implementation requires more work from the user.

Arcmap Field Calculator Python sPrefix

I am working in Arcmap using the Field Calculator.
I have a attibute with values like the follwoing:
"addr:city"="Bielefeld","addrostcode"="33699","addr:street"="Westerkamp"
"addr:city"="Bielefeld","addr:street"="Detmolder Straße"
"addr:city"="Bielefeld","addr:housenumber"="34"
I want to extract them into individual attributes.
So I thought I need codes like:
dim city
if sPrefix = "addr:city":
return everything past "addr:city" until a comma appears
Any ideas how to solve that. I don't have much experience unfortunatley.
Thanks,
Uli!
here is a screenshot
screenshot
Have a look at python's csv module.
Edit:
I've never used Arcmap, but I'd imagine you can still import modules in it.
If the strings are pretty regular, you could just parse the data without it though:
eg.
#test.py
def func(s, srch):
parts = dict([item.replace('"','').split('=') for item in s.split(',')])
return parts.get(srch,'')
if __name__ == '__main__':
tags = '"addr:city"="Bielefeld","addrostcode"="33699","addr:street"="Westerkamp"'
print func(tags, 'addr:city')
>python test.py
>Bielefeld
something like this, define your own function:
In [40]: def func(x,item):
spl=strs.split(",")
for y in spl:
if item in y:
return y.split("=")[-1].strip('"')
....:
....:
In [53]: strs='"addr:city"="Bielefeld","addrostcode"="33699","addr:street"="Westerkamp"'
In [54]: func(strs,"addr:city")
Out[54]: 'Bielefeld'
In [55]: func(strs,"addr:street")
Out[55]: 'Westerkamp'
As I read your question, you want to extract a string which looks like '"addr:city"="Bielefeld","addr:housenumber"="34"' into individual (key, value) pairs. The easiest way to do this is probably to use the csv reader (http://docs.python.org/2/library/csv.html). You will need to determine exactly how to use it in your use case, but here is a generic example which is likely to work:
import csv
for pairs in csv.reader(attribute_list):
key, value = pair.split('"="')
print key, value

Categories

Resources