Storing Objects in a List in Python - python

If an object exists with variable .X
randomData.X
is the created object. If multiple objects are stored in a list and can be accessed via
randomList[3].X
Is there a way to pull all values of X from the list without looping through every object in the list as below:
for x i range(0,10)
randomList[x].X

You are probably looking for a list comprehension.
[obj.X for obj in randomList]
This produces a list with all properties X of every object in your list of objects.
Keep in mind that you can't get away from looping over the list. This is just syntactic sugar for the same loop as before.

Just in case you're looking for maximum efficiency on larger lists, an alternative to the list comprehension in this case is using map + operator.attrgetter. You can either loop over the map directly:
from operator import attrgetter
for X in map(attrgetter('X'), randomList):
which involves no temporary list (map lazily pulls items on demand in Python 3), or if you really need the list, just wrap in the list constructor or use list unpacking to run it out eagerly:
Xs = list(map(attrgetter('X'), randomList))
# or
Xs = [*map(attrgetter('X'), randomList)]
For small input lists, this will be slower than the list comprehension (it has a slightly higher setup overhead), but for medium to large inputs, it will be faster (the per-item overhead is slightly lower, as it involves no per-item bytecode execution).
To be clear, it's still going to have to loop over the list. There is no magic way to get the attributes of every item in a list without looping over it; you could go to extreme lengths to make views of the list that seamlessly read the attribute from the underlying list, but if you accessed every element of that view it would be equivalent to the loop in work required.

Related

Python - Fastest way to create a tuple from a list with computations

I need to create in the fastest way possible a Python tuple from a list, but with intermediate computations. To be clearer I put a code snippet:
a=[1,2,3,4]
b=tuple([id(x) for x in a])
This is what I have by now.
Is there something better than this? This code creates a list and then it converts it to a tuple, is there a way to directly create the tuple?
Thank you!
[EDIT]
As suggested I tried with
b=tuple(id(x) for x in a)
However it seems slower than before.
Test script:
import time
a=[1,2,3,4]
t1=time.time()
for i in range(1000000):
b=tuple([id(x) for x in a])
print(time.time()-t1)
t2=time.time()
for i in range(1000000):
b=tuple(id(x) for x in a)
print(time.time()-t2)
It was very unexpected to me.
Using the list comprehension is faster. This is because the size of the tuple is known beforehand (memory can be allocated for len(list) items).
With generator, the generator needs to be converted to a sequence first so that a tuple can be allocated. This will be slower because of the inherent overhead in generators.
The fastest option is naturally to not use a tuple at all and use a list instead. In my opinion, a tuple is not the same as "an immutable list"; one should really use tuple only when one needs to use a sequence as a dictionary key, or as a set element, or when you could name each element (as in namedtuple).

What is the use case of the immutable objects

What is the use case of immutable types/objects like tuple in python.
Tuple('hello')
('h','i')
Where we can use the not changeable sequences.
One common use case is the list of (unnamed) arguments to a function.
In [1]: def foo(*args):
...: print(type(args))
...:
In [2]: foo(1,2,3)
<class 'tuple'>
Technically, tuples are semantically different to lists.
When you have a list, you have something that is... a list. Of items of some sort. And therefore can have items added or removed to it.
A tuple, on the other hand, is a set of values in a given order. It just happens to be one value that is made up of more than one value. A composite value.
For example. Say you have a point. X, Y. You could have a class called Point, but that class would have a dictionary to store its attributes. A point is only two values which are, most of the time, used together. You don't need the flexibility or the cost of a dictionary for storing named attributes, you can use a tuple instead.
myPoint = 70, 2
Points are always X and Y. Always 2 values. They are not lists of numbers. They are two values in which the order of a value matters.
Another example of tuple usage. A function that creates links from a list of tuples. The tuples must be the href and then the label of the link. Fixed order. Order that has meaning.
def make_links(*tuples):
return "".join('%s' % t for t in tuples)
make_links(
("//google.com", "Google"),
("//stackoveflow.com", "Stack Overflow")
)
So the reason tuples don't change is because they are supposed to be one single value. You can only assign the whole thing at once.
Here is a good resource that describes the difference between tuples and lists, and the reasons for using each: https://mail.python.org/pipermail/tutor/2001-September/008888.html
The main reason outlined in that link is that tuples are immutable and less extensive than say, lists. This makes them useful only in certain situations, but if those situations can be identified, tuples take up much less resources.
Immutable objects will make life simpler in many cases. They are especially applicable for value types, where objects don't have an identity so they can be easily replaced. And they can make concurrent programming way safer and cleaner (most of the notoriously hard to find concurrency bugs are ultimately caused by mutable state shared between threads). However, for large and/or complex objects, creating a new copy of the object for every single change can be very costly and/or tedious. And for objects with a distinct identity, changing an existing objects is much more simple and intuitive than creating a new, modified copy of it.

How to map a function to each dictionary element?

I have a dictionary self.what_to_build
I am iterating on each element, and apply another method to each element using the following way:
[self.typeBuild(obj_type,dest) for obj_type,dest in self.what_to_build.items()]
It is my understanding this builds a list in memory, while there is no real impact on the program, I would like to refrain from this, I really do not need the list, just applying the method.
How would I do this same map, in the most Pythonic way, without doing a list comprehension
Just use a regular loop:
for obj_type,dest in self.what_to_build.items():
self.typeBuild(obj_type, dest)
A list comprehension indeed creates a list object with the return values of the self.typeBuild() calls, which is a waste of CPU and memory if you don't need those return values.
Don't get too hung up trying to write 'compact' code; readability is found in just the right level of verbosity.

Sorting a list of lists with itertools imap in Python

I'm wondering what's happening when I execute this code and also if there is a better way to accomplish the same task. Is a list of lists being made in memory to preform the sort, and then bar is assigned to be an iterator of foo.values()? Or possibly foo.values() is sorted in the allotted dictionary memory space (seems unlikely)?
Imagine the first value in the list, the integer, refers to a line number in a file. I want to open the file and update only the lines referenced in the foo.values() lists with the rest of the data in the list (EG update line 1 with strings '123' and '097').
from itertools import imap
>>> foo = {'2134':[1, '123', '097'], '6543543':[3, '1'], '12315':[2, '454']}
>>> bar = imap([].sort(), foo.values())
Thanks~
First, you're passing [].sort(), which is just None, as the first argument to imap, meaning it's doing nothing at all. As the docs explain: "If function is set to None, then imap() returns the arguments as a tuple."
To pass a callable to a higher-order function like imap, you have to pass the callable itself, not call it and pass the result.
Plus, you don't want [].sort here; that's a callable with no arguments that just sorts an empty list, which is useless.
You probably wanted list.sort, the unbound method, which is a callable with one argument that will sort whatever list it's given.
So, if you did that, what would happen is that you'd creating an iterator that, if you iterated it, would generate a bunch of None values and, as a side effect, sort each list in foo.values(). No new lists would be created anywhere, because list.sort mutates the list in-place and returns None.
But since you don't ever iterate it anyway, it hardly matters what you put into imap; what it actually does is effectively nothing.
Generally, abusing map/imap/comprehensions/etc. for side-effects of the expressions is a bad idea. An iterator that generates useless values, but that you have to iterate anyway, is a recipe for confusion at best.
The simple thing to do here is to just use a loop:
for value in foo.values():
value.sort()
Or, instead of sorting in-place, generate new sorted values:
bar = imap(sorted, foo.values())
Now, as you iterate bar, each list will be sorted and given to you, so you can use it. If you iterate this, it will generate a sorted list in memory for each list… but only one will ever be alive at a time (unless you explicitly stash them somewhere).

Correct way to iterate twice over a list?

What is the correct way to perform multiple iteration over a container? From python documentation:
Iterator - A container object (such as a list) produces a fresh new
iterator each time you pass it to the iter() function or use it in a
for loop. Attempting this with an iterator will just return the same
exhausted iterator object used in the previous iteration pass, making
it appear like an empty container.
The intention of the protocol is that once an iterator’s next() method
raises StopIteration, it will continue to do so on subsequent calls.
Implementations that do not obey this property are deemed broken.
(This constraint was added in Python 2.3; in Python 2.2, various
iterators are broken according to this rule.)
If I have this code:
slist = [1,2,3,4]
rlist = reversed(slist)
list(rlist)
#[4,3,2,1]
tuple(rlist)
#()
What would be the easiest and most correct way to iterate over 'rlist' twice?
rlist = list(reversed(slist))
Then iterate as often as you want. This trick applies more generally; whenever you need to iterate over an iterator multiple times, turn it into a list. Here's a code snippet that I keep copy-pasting into different projects for exactly this purpose:
def tosequence(it):
"""Turn iterable into a sequence, avoiding a copy if possible."""
if not isinstance(it, collections.Sequence):
it = list(it)
return it
(Sequence is the abstract type of lists, tuples and many custom list-like objects.)
I wouldn't stored the list twice, if you can not combine it to iterate once, then I would
slist = [1,2,3,4]
for element in reversed(slist):
print element # do first iteration stuff
for element in reversed(slist):
print element # do second iteration stuff
Just think of the reversed() as setting up a reverse iterator on slist. The reversed is cheap. That being said, if you only ever need it reversed, I would reverse it and just have it stored like that.
What is the correct way to perform multiple iteration over a container?
Just do it twice in a row. No problem.
What would be the easiest and most correct way to iterate over 'rlist' twice?
See, the reason that isn't working for you is that rlist isn't "a container".
Notice how
list(slist) # another copy of the list
tuple(slist) # still works!
So, the simple solution is to just ensure you have an actual container of items if you need to iterate multiple times:
rlist = list(reversed(slist)) # we store the result of the first iteration
# and then that result can be iterated over multiple times.
If you really must not store the items, try itertools.tee. But note that you won't really avoid storing the items if you need to complete one full iteration before starting the next. In the general case, storage is really unavoidable under those restrictions.
Why don't you simply reverse the original list in-place (slist.reverse()), then iterate over it as many times as you wish, and finally reverse it again to obtain the original list once again?
If this doesn't work for you, the best solution for iterating over the list in reversed order is to create a new reverse iterator every time you need to iterate
for _ in xrange(as_many_times_as_i_wish_to_iterate_this_list_in_reverse_order):
for x in reversed(slist):
do_stuff(x)

Categories