Multiple "is" in Python - python

I have a list with a few hundred of objects, and I want to check, if a newcomer object is already added to my list (not an equal object, but exactly this exact instance).
I have a dumb realization like this:
def is_one_of(test_object, all_objects):
for elm in all_objects:
if test_object is elm:
return True
return False
Cannot it be more beautiful?

use any:
if any(x is test_object for x in all_objects):
The example in the python reference looks remarkably similar to your code already :)

Use the any() function:
def is_one_of(test_object, all_objects):
return any(test_object is elm for elm in all_objects)
It'll stop iterating over the generator expression as soon as a True result is found.

Eh, I made it by putting id(element) to a set:
def _unit_list(self):
"""
Returns all units in the order they should be initialized.
(Performs search by width from start_point).
"""
unit_id_set = set()
unit_list = []
unit_id_set.add(self.start_point)
unit_list.append(self.start_point)
pos = 0
while pos < len(unit_list):
cur_unit = unit_list[pos]
for child in cur_unit.links_to:
if not (id(child) in unit_id_set):
unit_list.append(child)
unit_id_set.add(id(child))
pos += 1
return unit_list

You can use
if any(test_object is x for x in all_objects): ...
if you need to do this test often however may be you can keep a set of all object ids instead
all_ids = set(map(id, all_objects))
then you can check faster with
if id(test_object) in all_ids: ...
Another common solution that may apply is to store in the object itself in a specific field if it has been already processed:
# add the object to the list
all_objects.append(x)
x.added = True
...
# Check if already added
if test_object.added: ...

I think you're looking for the in operator. The equivalent function would be:
def is_one_of(test_object, all_objects):
return test_object in all_objects
(but you really wouldn't want to write that as a function).
Edit: I'm wrong. According to the Expressions page:
For the list and tuple types, x in y is true if and only if there exists an index i such that x == y[i] is true.
That would work if your class doesn't define __eq__, but that's more fragile than I'd want to rely on. For example:
class ObjWithEq(object):
def __init__(self, val):
self.val = val
def __eq__(self, other):
return self.val == other.val
a = ObjWithEq(1)
b = ObjWithEq(1)
assert a == b
assert a in [b]
class ObjWithoutEq(object):
def __init__(self, val):
self.val = val
a = ObjWithoutEq(1)
b = ObjWithoutEq(1)
assert a != b
assert a not in [b]

Related

Python, output two lists differences with * wildcard

I'm having a hard time with python and finding differences between two lists.
CMDB list:
ABC:NL1:SB6
ABC:NL2:SB6
ABC:NL3:SB6
ABC:NL4:SB6
NL9:SB9
NL5:SB4
NL6:SB7
DB list:
NL1:SB6
NL2:SB6
ABC:NL3:SB6
ABC:NL4:SB6
ABC:NL8:SB8
ABC:NL5:SB4
ABC:NL6:SB7
I would like to get output that finds differences:
NL9:SB9
ABC:NL8:SB8
I have tried
cmdb_fin = set(cmdb)
db_fin = set(db)
equal = db_fin.symmetric_difference(cmdb_fin)
but the output is like following because it compares exact strings to each other, not like "patterns"
ABC:NL5:SB4
NL6:SB7
ABC:NL2:SB6
NL2:SB6
ABC:NL8:SB8
NL5:SB4
ABC:NL6:SB7
NL9:SB9
ABC:NL1:SB6
NL1:SB6
Is there any way to get expected by me output?
criteria:
if any given string (block of chars) in CMDB list exists in DB list (it can be only part of a string), it should not be in output as it kinda exists in both lists. And of course in other way -> DB compared to CMD
for example NL5:SB4 from CMDB list matches ABC:NL5:SB4 from DB
In order to define a custom equality comparator when using python sets, you need to define a custom class with __eq__, __ne__, & __hash__ defined. Below is an example of how this could be achieved in your case, using the last two elements in each line to define whether two elements are equivalent.
Code:
class Line(object):
def __init__(self, s):
self.s = s
self.key = ':'.join(s.split(':')[-2:])
def __repr__(self):
return self.s
def __eq__(self, other):
if isinstance(other, Line):
return ((self.key == other.key))
else:
return False
def __ne__(self, other):
return (not self.__eq__(other))
def __hash__(self):
return hash(self.key)
cmdb = ['ABC:NL1:SB6', 'ABC:NL2:SB6', 'ABC:NL3:SB6', 'ABC:NL4:SB6', 'NL9:SB9',
'NL5:SB4', 'NL6:SB7']
db = ['NL1:SB6', 'NL2:SB6', 'ABC:NL3:SB6', 'ABC:NL4:SB6', 'ABC:NL8:SB8',
'ABC:NL5:SB4', 'ABC:NL6:SB7']
cmdb_fin = set(Line(l) for l in cmdb)
db_fin = set(Line(l) for l in db)
equal = db_fin.symmetric_difference(cmdb_fin)
Output:
>>> equal
{ABC:NL8:SB8, NL9:SB9}
Usage:
>>> Line('NL5:SB4') == Line('ABC:NL5:SB4')
True

Change a condition after changing the value of some variables

i have this situation: a list of variables initially set to none
A = [none, none, none, none]
and a very simple function that controls if 2 values (different to none) are different:
def notEqual(a, b):
if a is None and b is None:
return True
if a != b:
return True
return False
I want create a list named bigList that shows for each couple of element of A if they are equal or not. So i thought to do this:
for i in range(4):
for j in range(4):
if i != j:
c = ((i, j), (notEqual(A[i], A[j])))
bigList.append((c))
So at the beginning all the element of bigList are ((i,j), (True))
In a second moment i have this situation:
A[0]=10 A[1]=10
So the condition associated to (0,1) and (1,0) have to change to False.
Is there an easy way to do something like this? (Change some conditions when some variables change their values)
No, there is no way. In most languages, expression are evaluated with current values of the variables. You can't make an expression that works like not_equal(current_value_of_A, current_value_of_B) that will automatically change when values of A and/or B change. So, in one way or another, you have to re-run your code.
A common way of doing something similar is the observer pattern. That is, wrap your expression in a class and notify the class when the value of something changes.
Along with that, use a dict instead of a list, which has the form {(i,j): notEqual(A[i], A[j]), so you can update only the individual (i, j) pair without re-running your whole code
You, can use #propery of python. It works like getter similar to other languages like C# and JAVA.
In your case you can make a object similar to that of ((i, j), (notEqual(A[i], A[j]))) with a getter.
See sample implementation below
class bigListElement(object):
A = [] # this is static
def __init__(self, index_tuple, areEqual):
self.index_tuple = index_tuple
self._areEqual = areEqual
#staticmethod
def notEqual(a, b):
if a is None and b is None:
return True
if a != b:
return True
return False
#property
def areEqual(self):
return bigListElement.notEqual(bigListElement.A[self.index_tuple[0]], bigListElement.A[self.index_tuple[1]])
print("Hello World")
bigListElement.A = [10, 2, 10, 3, 4]
a = bigListElement((0, 1), False)
print(a.areEqual) # True
bigListElement.A[1] = 10
print(a.areEqual) # False

Writing filter() function but getting typeError

my code consists of me recreating the function 'filter()' and using it with a function to filter words longer than 5 characters. It worked with the actual function filter when I tried it btw...I'm using python 3+
def filter1(fn, a):
i = 0
while i != len(a):
u = i - 1
a[i] = fn(a[i], a[u])
i += 1
return a
def filter_long_words(l):
if len[l] > 5:
return [l]
listered = ['blue', 'hdfdhsf', 'dsfjbdsf', 'jole']
print(list(filter1(filter_long_words, listered)))
getting error
TypeError: filter_long_words() takes 1 positional argument but 2 were given
You are passing two parameters to fn (which refers to filter_long_words) here:
a[i] = fn(a[i], a[u])
But filter_long_words only accepts one parameter.
Notes:
You can loop through lists using for item in my_list, or if you want index as well for index, item in enumerate(my_list).
I think you might get an IndexError since u will be -1 in the first round of your loop.
The filter function can also be expressed as a list comprehension: (item for item in listered if filter_long_words(item))
My version of filter would look like this, if I have to use a for loop:
def my_filter(fn, sequence):
if fn is None:
fn = lambda x: x
for item in sequence:
if fn(item):
yield item
Since you have stated that you are using Python 3, this returns a generator instead of a list. If you want it to return a list:
def my_filter(fn, sequence):
if fn is None:
fn = lambda x: x
acc = []
for item in sequence:
if fn(item):
acc.append(item)
return acc
If you don't need to use a for loop:
def my_filter(fn, sequence):
if fn is None:
fn = lambda x: x
return (item for item in sequence if fn(item))
Your're calling fn with 2 parameters in filter1(fn, a), and since you've passed filter_long_words() to filter1 as fn, that triggers the error.
But there's more weird stuff:
I don't understand the magick of filter1 or what you were trying to
accomplish, but it seems to me that you don't have a clear idea what to do.
But if you want to mimic (somehow) how filter works, you have to return a
list which contains only items for which the fn function returns true. When
you know this, you can rewrite it - here are a few suggestions for rewrite
# explicit, inefficient and long, but straightforward version:
def filter1(fn, a):
new_list = []
for item in a:
if fn(item):
new_list.append(item):
return new_list
# shorter version using list comprehensions:
def filter1(fn, a):
return [item for item in a if fn(item)]
The filter_long_words function is wrong too - it should return True or
False. The only reason why it could work is because any non-empty list is
treated as True by python and default return value of a function is None,
which translates to False. But it's confusing and syntactically wrong to use
len[l] - the proper usage is len(l).
There are a few suggestions for rewrite, which all returns explicit boolean
values:
# unnecessary long, but self-explanatory:
def filter_long_words(l):
if len(l) > 5:
return True
else
return False
# short variant
def filter_long_words(l):
return len(l) > 5
You are calling "filter_long_words" with 2 parameter => fn(a[i], a[u]) also there is an error
def filter_long_words(l):
if **len[l]** > 5:
return [l]
len is builtin method it should be len(l)

Python: Subsetting a list according to the attribute

I have a list of instances of MC and I want to subset all the instances which attribute equals 2. Can you help me creating the function "superfunction" that does either subset according to a value to accept or to reject.
class MC(object):
def __init__(self,smth):
self.smth=smth
l = [MC(2),MC(4),MC(1),MC(2),MC(2),MC(-3),MC(0)]
def superfunction (a_list,attr,value_to_accept=None, value_to_reject=None):
return a_subset
How would it work for a dictionnary ?
Thanks a lot !
For your specific example, you could use a list comprehension like so:
return [x for x in a_list if x.smth == 2]
For your general example, you could do a similar thing:
if value_to_accept is not None:
return [x for x in a_list if x.smth == value_to_accept]
if value_to_reject is not None:
return [x for x in a_list if x.smth != value_to_reject]
return []
You don't necessarily need a separate function to do this. List comprehensions are pretty straightforward:
[mc from l if mc.smth == value_to_accept]
[mc from l if mc.smth != value_to_reject]
You could use filter() with a predicate:
def superfunction (a_list, attr, value_to_accept=None, value_to_reject=None):
def predicate(item):
value = getattr(item, attr)
if value_to_reject is not None and value != value_to_reject:
return False
if value_to_accept is not None and value == value_to_accept:
return True
return False
return filter(predicate, a_list)
This let's you specify either a value to accept or to reject.
Usually, however, you'd use an inline list comprehension instead of a function:
[elem for elem in a_list if a_list.attr == value_to_accept]

What should I use instead of assignment-in-an-expression in Python?

according to this page one can't use code like
if variable = something():
#do something with variable, whose value is the result of something() and is true
So in case I want to have the following code structure:
if a = something():
#do something with a
elif a = somethingelse():
#...
#5 more elifs
where something() functions are compute-intensive (I mean that using the function and then doing it again to assign value to a variable in case the first one was true can't be done), what should I write instead in Python? Add 7 more variables instead of 1?
I had this problem years ago, in 2001 -- since I was transliterating to Python from a reference-algorithm in C which used assign-and-test heavily, I was keen to keep a similar structure for the first draft (then refactor later once correctness was well tested). So I wrote a recipe in the Cookbook (see also here), which boils down to...:
class DataHolder(object):
def set(self, value): self.value = value; return value
so the if/elif tree can become:
dh = DataHolder()
if dh.set(something()):
# do something with dh.value
elif dh.set(somethingelse()):
# ...
the DataHolder class can clearly be embellished in various ways (and is so embellished in both the online and book versions), but this is the gist of it, and quite sufficient to answer your question.
Another alternative which offers some flexibility:
# Functions to be tested (can be expanded):
tests = [something, somethingelse, yetsomethingelse, anotherfunction, another]
for i, f in enumerate(tests):
a = f()
if a:
if i == 0:
# do something with a
elif 1 <= i <= 3:
# do something else with a
else:
# ...
break
Or you can explicitly compare to the function:
tests = [something, somethingelse, yetsomethingelse, anotherfunction, another]
for i, f in enumerate(tests):
a = f()
if a: break
if not a:
# no result
elif f == something:
# ...
elif f == somethingelse:
# ...
If some of the functions take arguments, you can use lambda to keep the function paradigm:
tests = [lambda: something(args), somethingelse, lambda: something(otherargs)]
for i, f in enumerate(tests):
a = f()
if a: break
if not a:
# no result
elif i == 0:
# ...
elif i == 1:
# ...
You could do this:
a = something()
if a:
#do something with a
else:
a = somethingelse()
if a:
#...
else:
#5 more nested ifs
Or, inside a function you can limit the nesting level with a return in each matching case:
def f():
a = something()
if a:
#do something with a
return
a = somethingelse()
if a:
#...
return
#5 more ifs
Make yourself a simple callable object that saves its returned value:
class ConditionValue(object):
def __call__(self, x):
self.value = x
return bool(x)
Now use it like this:
# example code
makelower = lambda c : c.isalpha() and c.lower()
add10 = lambda c : c.isdigit() and int(c) + 10
test = "ABC123.DEF456"
val = ConditionValue()
for t in test:
if val(makelower(t)):
print t, "is now lower case ->", val.value
elif val(add10(t)):
print t, "+10 ->", val.value
else:
print "unknown char", t
Prints:
A is now lower case -> a
B is now lower case -> b
C is now lower case -> c
1 +10 -> 11
2 +10 -> 12
3 +10 -> 13
unknown char .
D is now lower case -> d
E is now lower case -> e
F is now lower case -> f
4 +10 -> 14
5 +10 -> 15
6 +10 -> 16
I might be missing something, but couldn't you factor each of the branches in your top-level if statement into separate functions, create a list of test to action tuples and loop over them? You should be able to apply this pattern to mimic if (value=condition()) {} else if (value=other_condition()) {} style logic.
This is really an extension of redglyph's response and can probably be compressed down to a iterator that raises StopIteration once it has hit a truth value.
#
# These are the "tests" from your original if statements. No
# changes should be necessary.
#
def something():
print('doing something()')
# expensive stuff here
def something_else():
print('doing something_else()')
# expensive stuff here too... but this returns True for some reason
return True
def something_weird():
print('doing something_weird()')
# other expensive stuff
#
# Factor each branch of your if statement into a separate function.
# Each function will receive the output of the test if the test
# was selected.
#
def something_action(value):
print("doing something's action")
def something_else_action(value):
print("doing something_else's action")
def something_weird_action(value):
print("doing something_weird's action")
#
# A simple iteration function that takes tuples of (test,action). The
# test is called. If it returns a truth value, then the value is passed
# onto the associated action and the iteration is stopped.
#
def do_actions(*action_tuples):
for (test,action) in action_tuples:
value = test()
if value:
return action(value)
#
# ... and here is how you would use it:
#
result = do_actions(
(something, something_action),
(something_else, something_else_action),
(something_weird, something_weird_action)
)
You could use a decorator like this memorise for those functions - assuming they always return the same value. Notice that you can call expensive_foo and expensive_bar as many times as you like and the function body only ever gets executed once
def memoize(f):
mem = {}
def inner(*args):
if args not in mem:
mem[args] = f(*args)
return mem[args]
return inner
#memoize
def expensive_foo():
print "expensive_foo"
return False
#memoize
def expensive_bar():
print "expensive_bar"
return True
if expensive_foo():
a=expensive_foo()
print "FOO"
elif expensive_bar():
a=expensive_bar()
print "BAR"
I would solve this the same way I solve several other problems of tricky flow control: move the tricky bit into a function, and then use return to cause an early exit from the function. Like so:
def do_correct_something():
a = something()
if a:
# do something with a
return a
a = somethingelse()
if a:
# do something else with a
return a
# 5 more function calls, if statements, do somethings, and returns
# then, at the very end:
return None
a = do_correct_something()
The major other "tricky problem of flow control" for which I do this is breaking out of multiple nested loops:
def find_in_3d_matrix(matrix, x):
for plane in matrix:
for row in plane:
for item in row:
if test_function(x, item):
return item
return None
You can also solve the stated question by writing a for loop that will iterate only once, and use "break" for the early exit, but I prefer the function-with-return version. It's less tricky, and clearer what is going on; and the function-with-return is the only clean way to break out of multiple loops in Python. (Putting "if break_flag: break" in each of the for loops, and setting break_flag when you want to break, is not IMHO clean.)
This is possible if we working with strings - because we can convert string to list and use method extends for list that logically make inline appending one string to another (in list format):
>>> my_str = list('xxx')
>>> if not my_str.extend(list('yyy')) and 'yyy' in ''.join(my_str):
... print(True)
True
Here we inside if 'added to the original string' new data and trying to find it. Maybe this is ugly but this is assignment in an expression like:
if my_str += 'yyy' and 'yyy' in my_str:
From Python 3.8.0a1+ on, we can use assignment expression syntax.
For example:
>>> if a := 0:
... print('will not be printed')
... elif a := 1:
... print('print value of a: %d, a should be 1' % a)
... else:
... print('will not be printed')
...
print value of a: 1, a should be 1

Categories