You can get a list of all matching elements with a list comprehension:
[x for x in myList if x.n == 30] # list of all elements with .n==30
If you simply want to determine if the list contains any element that matches and do it (relatively) efficiently, you can do
def contains(list, filter):
for x in list:
if filter(x):
return True
return False
if contains(myList, lambda x: x.n == 3) # True if any element has .n==3
# do stuff
Answer from Adam Rosenfield on Stack OverflowYou can get a list of all matching elements with a list comprehension:
[x for x in myList if x.n == 30] # list of all elements with .n==30
If you simply want to determine if the list contains any element that matches and do it (relatively) efficiently, you can do
def contains(list, filter):
for x in list:
if filter(x):
return True
return False
if contains(myList, lambda x: x.n == 3) # True if any element has .n==3
# do stuff
Simple, Elegant, and Powerful:
A generator expression in conjuction with a builtin… (python 2.5+)
any(x for x in mylist if x.n == 10)
Uses the Python any() builtin, which is defined as follows:
any(iterable)
->Return True if any element of the iterable is true. Equivalent to:
def any(iterable):
for element in iterable:
if element:
return True
return False
Like ok... the title said it all. For example : I have a list filled by class name Food. The class uniqueness are defined by name and price attribute. And I create a method to check if the Food is free. the question is how do I use the method to filter the list of Food class using the free checking method of food class?
@dataclass
class Food:
name : str
price : int
def isfree(self):
return self.price == 0
food_list = [Food(name = "curry", price = 5), Food(name = "sushi", price = 0)]
filtered_list = filter(Food.isfree, food_list) # got error TypeError: isfree() takes 0 positional arguments but 2 were givenHi all, interested in your approaches and suggestions to this problem.
Let's say I have created a custom Player class that contains many attributes relating to a particular sportsperson's characteristics:
@dataclass
class Player:
id: int
first_name: str
last_name: str
age: int
height: int
weight: int
team: int
games_played: int
...If I create a small (~500) length list of these objects and then want to filter on multiple conditions, what's the best way for me to do that?
Of course, in a one off situation I could write something like:
filtered = [player for player in player_list if 20 <= player.age < 25 and player.height > 180 and player.team == "Rovers"]
This gives me the flexibility to filter exactly how I'd like but it's not the most glamorous solution in my opinion, especially if I want to try out lots of different filters.
I had a thought that this might be easier to by creating something like a PlayerFilter class which can be instantiated and then have filters added to a dict attribute or similar, and then can be applied to a list of players and return the filtered list. This seems more Pythonic to me but also seems slightly overkill and I'm looking for the most straightforward and intuitive way to filter these values. I'm imagining if a random python user was to use my package with this filter implementation, I'm not sure if that would make sense.
Is the complexity a side effect of using a list of objects to store my data as opposed to something like a dataframe? I've considered this too but I want to maintain the ability to call the methods that I've created for my custom player class on any filtered output.
Let me know what you think!
There are in fact two ways.
itertoolsmap(..., itertools.ifilter(..))List comprehension
[x.room_number() for x in people if check(x)]
Which you choose is mostly a matter of taste, but convention leans towards the latter.
In the case of object filtering where you want to do an inclusive union of a subset of properties to equal a limited set of values and then perform any option (including list the attribute values) of the filtered list you can do the following using generators in a single statement (last line of code, the rest is there for instruction showing generating a large list of objects using matrix multiplication to generate constructor params)
#!/usr/bin/env python
import itertools
import pprint
class myObj(object):
attr_1 = None
attr_2 = None
attr_3 = None
def __init__(self, at1, at2, at3):
self.attr_1 = at1
self.attr_2 = at2
self.attr_3 = at3
super(myObj, self).__init__()
def __repr__(self):
return '<%s %s>' % (type(self), pprint.pformat(self.__dict__))
objs = itertools.starmap(myObj, itertools.product(iter('val_%d' % (i) for i in
range(1,4)), repeat=3))
filter_dict = {
'attr_1' : 'val_1',
'attr_2' : 'val_2',
'attr_3' : 'val_3',
}
print(list(result.attr_3 for result in objs if not list(False for pn,cval in
filter_dict.items() if getattr(result, pn, None) != cval)))
I would use a list comprehension:
contexts_to_display = ...
tasks = [t for t in Task.objects.all()
if t.matches_contexts(contexts_to_display)
if not t.is_future()]
tasks.sort(cmp=Task.compare_by_due_date)
Since you already have a list, I see no reason not to sort it directly, and that simplifies the code a bit.
The cmp keyword parameter is more of a reminder that this is 2.x code and will need to be changed to use a key in 3.x (but you can start using a key now, too):
import operator
tasks.sort(key=operator.attrgetter("due_date"))
# or
tasks.sort(key=lambda t: t.due_date)
You can combine the comprehension and sort, but this is probably less readable:
tasks = sorted((t for t in Task.objects.all()
if t.matches_contexts(contexts_to_display)
if not t.is_future()),
cmp=Task.compare_by_due_date)
Since you are writing Django code, you don't need lambdas at all (explanation below). In other Python code, you might want to use list comprehensions, as other commenters have mentioned. lambdas are a powerful concept, but they are extremely crippled in Python, so you are better off with loops and comprehensions.
Now to the Django corrections.
tasks = Task.objects.all()
tasks is a QuerySet. QuerySets are lazy-evaluated, i.e. the actual SQL to the database is deferred to the latest possible time. Since you are using lambdas, you actually force Django to do an expensive SELECT * FROM ... and filter everything manually and in-memory, instead of letting the database do its work.
contexts_to_display = ...
If those contexts are Django model instances, then you can be more efficient with the queries and fields instead of separate methods:
# tasks = Task.objects.all()
# tasks = filter(lambda t: t.matches_contexts(contexts_to_display), tasks)
# tasks = filter(lambda t: not t.is_future(), tasks)
# tasks = sorted(tasks, Task.compare_by_due_date)
qs = Task.objects.filter(contexts__in=contexts_to_display, date__gt=datetime.date.today()).order_by(due_date)
tasks = list(qs)
The last line will cause Django to actually evaluate the QuerySet and thus send the SQL to the database. Therefore you might as well want to return qs instead of tasks and iterate over it in your template.
You can do this very easily with a set:
class MyObject:
def __init__(self, id_, result_name):
self.id_ = id_
self.result = Result(result_name)
class Result:
def __init__(self, name):
self.name = name
object1 = MyObject(1, 'A')
object2 = MyObject(2, 'A')
object3 = MyObject(3, 'B')
object4 = MyObject(4, 'B')
T = (object1, object2, object3, object4)
S = {o.result.name for o in T}
print(S)
Output:
{'A', 'B'}
Note:
You could obviously convert the set to a list if that's what you need.
Use of id not a great idea as a variable name
You can that using reduce from functools.
Let's start by creating the sample data:
class MyObject:
def __init__(self, id: int, result_name: str):
self.id = id
self.result = Result(result_name)
class Result:
def __init__(self, name: str):
self.name = name
object1 = MyObject(1, 'A')
object2 = MyObject(2, 'A')
object3 = MyObject(3, 'B')
object4 = MyObject(4, 'B')
x_ids = (object1, object2, object3, object4)
Now we perform the list comprehension:
from functools import reduce
result = tuple(reduce(lambda t, s: {**t, s.result.name: s}, x_ids, {}).values())
Result:
(object2, object4)
The list comprehension (reduce) starts with an empty dict {}. Each iteration, an entry from x_ids is passed to the lambda function as s and the current state of the dict is passed as t. The dict gets updated by adding the object s with key s.result.name. If that key already exists, the entry will be overwritten - this filters out objects with the same result name. The result is a dict: {'A':object2, 'B':object4}. The result is then converted into the required tuple format.
It is strange how much beauty varies for different people. I find the list comprehension much clearer than filter+lambda, but use whichever you find easier.
There are two things that may slow down your use of filter.
The first is the function call overhead: as soon as you use a Python function (whether created by def or lambda) it is likely that filter will be slower than the list comprehension. It almost certainly is not enough to matter, and you shouldn't think much about performance until you've timed your code and found it to be a bottleneck, but the difference will be there.
The other overhead that might apply is that the lambda is being forced to access a scoped variable (value). That is slower than accessing a local variable and in Python 2.x the list comprehension only accesses local variables. If you are using Python 3.x the list comprehension runs in a separate function so it will also be accessing value through a closure and this difference won't apply.
The other option to consider is to use a generator instead of a list comprehension:
def filterbyvalue(seq, value):
for el in seq:
if el.attribute==value: yield el
Then in your main code (which is where readability really matters) you've replaced both list comprehension and filter with a hopefully meaningful function name.
This is a somewhat religious issue in Python. Even though Guido considered removing map, filter and reduce from Python 3, there was enough of a backlash that in the end only reduce was moved from built-ins to functools.reduce.
Personally I find list comprehensions easier to read. It is more explicit what is happening from the expression [i for i in list if i.attribute == value] as all the behaviour is on the surface not inside the filter function.
I would not worry too much about the performance difference between the two approaches as it is marginal. I would really only optimise this if it proved to be the bottleneck in your application which is unlikely.
Also since the BDFL wanted filter gone from the language then surely that automatically makes list comprehensions more Pythonic ;-)