The Pythonic way of validating a long chain of conditions in Python - python

So I have a long chain of conditions that should be validated to be true. Instead of chaining a long if condition, I tried to be "innovative" and did it this way, which I reckon is more readable. But my question is, is this the optimal way of doing it?
Or is there a pythonic way of doing it? PS: Please respond with an alternative instead of answering "No", thanks!
Here's the code chunk:
def site_exists(site):
"""
returns the sitebean if it exists,
else returns false
"""
vpadmin_service = _get_vpadmin_service(site)
all_sites = VpAdminServiceUtil.getSites(vpadmin_service)
for site_listing in all_sites:
if site.getId():
#condition check
try:
assert site.getId() == site_listing.getId()
assert site.getName() == site_listing.getName()
assert site.getCustomer().getId() == site_listing.getCustomer().getId()
except AssertionError:
continue
#pass conditions
return site_listing
#no id, so just check for name and customer
else:
#condition check
try:
assert site.getName() == site_listing.getName()
assert site.getCustomer().getId() == site_listing.getCustomer().getId()
except AssertionError:
continue
#pass conditions
site.setId(site_listing.getId())
return site_listing
return False

A simpler approach is to build a tuple of the conditions and compare the tuples:
def site_info(s):
return s.getId(), s.getName(), s.getCustomer().getId()
if site_info(site) == site_info(site_listing):
return site_listing
else:
continue
If you have a lot of conditions, or the conditions are expensive, you can instead create a generator for the conditions, and compare with any or all:
import itertools
def iter_site_info(s):
yield s.getId()
yield s.getName()
yield s.getCustomer().getId()
if all(x==y for (x, y) in itertools.izip(iter_site_info(site), iter_site_info(site_listing)):
return site_listing
else:
continue
I'm not sure whether Jython has any and all, but they're trivial functions to write.
EDIT - any and all appeared in Python 2.5, so Jython should have them.

Using exception for control flow is bad! And it will also kill your performance. Assuming the condition will be true in one out of 10 cases? So 9 exceptions to handle in the worst case - this comes with a huge performance cost.
If you want readability, try:
if (
condition1 and \
condition2 and \
condition3
):
# do something
For the revised version, this should be equivalent:
for site_listing in all_sites:
if site.getName() == site_listing.getName() and site.getCustomer().getId() == site_listing.getCustomer().getId():
if not site.getId():
site.setId(site_listing.getId())
if site.getId() == site_listing.getId():
return site_listing

I would implement an is_same method on site and call it in the for loops.
all_sites = VpAdminServiceUtil.getSites(vpadmin_service)
for site_listing in all_sites:
if site_listing.is_same(site, set_id=True):
return site_listing
As for its implementation (that is your real question), I suggest something like:
...
if self.getName() != site.getName(): return False
if self.getCustomer() != site.getCustomer(): return False
...
return True

Use the __eq__ method! If you wrote the class of site yourself, no problem. If it is from a module outside of your control, see this solution, or create a somewhat less elegant wrapping class. For the wrap variant, this would be the class:
class WrappedSite(object):
def __init__(self, site):
self.site = site
def __eq__(self, other):
if site.getId():
if self.site.getId() != other.site.getId():
return False
if self.site.getName() != other.site.getName():
return False
if self.site.getCustomer().getId() != other.site.getCustomer().getId():
return False
return True
Then your big ugly function is reduced to this:
def site_exists(site):
"""
returns the sitebean if it exists,
else returns false
"""
vpadmin_service = _get_vpadmin_service(site)
all_sites = VpAdminServiceUtil.getSites(vpadmin_service)
wsite = WrappedSite(site)
for site_listing in all_sites:
if wsite == WrappedSite(site_listing):
return site_listing
return False
EDIT Fixed site_exists to return sitebean instead of True.

Remove some code duplication:
def site_exists(site):
"""
returns the sitebean if it exists,
else returns None
"""
vpadmin_service = _get_vpadmin_service(site)
all_sites = VpAdminServiceUtil.getSites(vpadmin_service)
for site_listing in all_sites:
if (site.getName() == site_listing.getName() and
site.getCustomer().getId() == site_listing.getCustomer().getId()):
if site.getId(): # if id is set; it should be the same
if site.getId() != site_listing.getId(): continue
else: # no id; consider it the same site
site.setId(site_listing.getId()) #XXX side-effect
return site_listing
Note: it is unexpected that site_exists() might modify its argument (via .setId()). Consider to refactor it:
def same_site(site, other):
if site.getId() and site.getId() != other.getId():
# if id is set; it should be the same
return False
return (site.getName() == other.getName() and
site.getCustomer().getId() == other.getCustomer().getId())
def get_site_listing(site):
"""
returns the sitebean corresponding to `site`,
returns None if there is none
"""
vpadmin_service = _get_vpadmin_service(site)
all_sites = VpAdminServiceUtil.getSites(vpadmin_service)
return next((s for s in all_sites if same_site(site, s)), None)
Note: the code doesn't modify the site object. Use the return value from get_site_listing() instead.
If next() is unavailable then use:
for site_listing in all_sites:
if same_site(site, site_listing):
return site_listing
return None
btw, jython should provide property wrappers for you; so you could write:
def same_site(site, other):
if site.id and site.id != other.id:
# if id is set; it should be the same
return False
return site.name == other.name and site.customer.id == other.customer.id
and site.id = id instead of site.setId(id).

Using an exception handler as a code target is basically a "goto". I don't see anything wrong with it really (despite the ancient furor surrounding "goto considered harmful"), but knowing that it's a goto might give you a different perspective.

Using exception handling for control flow is pythonic in the sense that "it's easier to ask for forgiveness than for permission". However, that doesn't extend to making rules to break just so that you can ask for forgiveness.
Others have provided excellent alternatives. Just wanted to address the principle aspect.

Related

returning an IF statment without closing a loop event filter pyqt5

I have a number of frames that are made depending on the number of items in a list. I want to click on this frame to expand it to have more options. I have installed an event filter on the frame. I have been making the frames like this:
for num, i in enumerate(self.data):
exec(f"self.frame_user_{num} = QFrame(self.frame_user_frame)")
getattr(self, f"frame_user_{num}").setMouseTracking(True)
getattr(self, f"frame_user_{num}").installEventFilter(self)
and it works fine.
when it comes to the event filter
def eventFilter(self, object, event):
loop_length = len(self.data)
z = 0
if event.type() == QEvent.MouseButtonPress:
while z < loop_length:
if object == getattr(self, f"frame_user_{z}"):
self.expand_buttons(z)
else:
return False
z += 1
else:
return False
The problem I have is I need to close the if statement in the loop with an else: return False otherwise I get the error:
a 'bool' is expected not 'NoneType'
but if I put the else: return false it ends the loop and doesn't go through the rest of the frames.
How would I write the event filter so It goes through each frame without closing the loop and not giving an error?
or is there another better way?
If you get to the end of the while loop without executing else: return False, you'll exit the function without an explicit return statement. Put another return after the loop.
def eventFilter(self, object, event):
loop_length = len(self.data)
if event.type() == QEvent.MouseButtonPress:
for z in range(loop_length):
if object == getattr(self, f"frame_user_{z}"):
self.expand_buttons(z)
else:
return False
return True
else:
return False
I return True there since all the other paths return False, so I assume this is considered the successful case.
However, I'm not sure that your loop is doing what you really want. It will return False immediately if object is not the same as self.frame_user_0, so you won't keep searching for other matching attributes. I suspect what you want is:
for z in range(loop_length):
if object == getattr(self, f"frame_user_{z}"):
self.expand_buttons(z)
return True
return False
This will look for the first frame_user_{z} attribute that matches object, expand it, and return True. If none are found, it will return False.

How can I accept one variable or multple when using a function?

I'm looking to create a search function similar to my rent_book fuction that allows me to search by first name, second name or title or any combination of the three. So I could maybe search for first name "George" and title "Animal Farm" or just title "Animal Farm" and receive the same result.
Books are stored in a list of dictionaries this is the dict struct and the rent_book function. I could do a convoluted nest of ifs but I'm sure there's a better way.
book = {
"fname": fname,
"sname": sname,
"title": title,
"avail": True
}
def rent_book(self, fname, sname, title):
# if is_return is False:
for x in self.lstBooks:
if x['fname'] == fname and x['sname'] == sname and x['title'] == title and x['avail'] is True:
x['avail'] = False
return True
return False
Thanks
Since your arguments are the same as the dictionary keys you're matching, you could just use **kwargs and iterate over the kwargs:
def rent_book(self, **kwargs):
# if is_return:
# return False
if not kwargs:
raise KeyError("Must search on at least one of fname, sname, or title.")
for x in self.lstBooks:
if not (x['avail'] and all(x[k] == v for k, v in kwargs.items())):
continue
x['avail'] = False
return True
return False
Note that the function will implicitly raise KeyError if it's called with any invalid keys (the x[k] will raise it), and there's an explicit raise KeyError to guard against the caller accidentally not providing any kwargs at all, since otherwise it would just return the first book in lstBooks.
(Yes, pedants, they can still call it with avail=True.)
I am not sure If I understood Your question correctly,
But If I am correct, You want the function to work appropriately handling the following cases:
if all "title","fname","sname" are given.
if only one among the 3 is given.
If any pair of 2 among the 3 is given.
If that is the case You can use default Parameters in the function for all the 3.
def rent_book(self, fname="", sname="", title=""):
# if is_return is False:
for x in self.lstBooks:
if (len(fname) and x['fname'] == fname) and (len(sname) and x['sname'] == sname) and (len(title) and x['title'] == title) and x['avail'] is True:
x['avail'] = False
return True
return False
The line
(len(fname) and x['fname'] == fname)
makes sure that You only check the condition if fname is explicitly provided in as parameter.
So, In all the above scenarios mentioned above,
You can call the same function with only optional parameters
(Dont forget to specify the parameter name while calling else it will always consider it the value for the first parameter by default!!)
The first thing to do here is think about having a Book class. Initially it needs to support sname, fname, title and avail (a flag to indicate whether or not the book is available to rent).
We could then construct a Book instance with any combination of these attributes or even none.
So let's start with this:
class Book:
klist = ['sname', 'fname', 'title']
def __init__(self, **kwargs):
self.vars = kwargs
for k in Book.klist:
self.vars.setdefault(k, None)
self.vars.setdefault('avail', True)
#property
def avail(self):
return self['avail']
#avail.setter
def avail(self, v):
self.vars['avail'] = v
def __getitem__(self, item):
return self.vars.get(item, None)
def __repr__(self):
return f'sname={self["sname"]} fname={self["fname"]} title={self["title"]} avail={self.avail}'
Defining getitem keeps the code neater and safer. The repr is optional - it just helps to see what's going on should we ever want to print an instance of this class.
Let's create a couple of Book instances and put them in a list.
booklist = [Book(sname='Orwell', fname='George', title='Animal Farm'), Book(sname='Fleming', fname='Ian', title='Casino Royale')]
Now we want to rent a book. We can provide any one or all of the main attributes. If there's a match for all of the attributes that have been passed and if that book is available, we'll mark it as unavailable and return True. If nothing matches or the book isn't available we return False.
def rent_book(**kwargs):
for book in booklist:
if book.avail:
for k, v in kwargs.items():
if book[k] is not None and v != book[k]:
break
else:
book.avail = False
return True
return False
Now let's see what happens:
print(rent_book(title='Casino Royale'))
print(rent_book(title='Casino Royale'))
print(rent_book(sname='Orwell', title='Animal Farm'))
Output:
True # the book title matches and it's available
False # the book title matches but it's no longer available
True # both the surname and title match so it's available

How to avoid duplicating function call in the IF check and return statement if the condition evaluates to true?

I have a function process. If attempts to retrieve one of name, address, phone from the input data (in that order) using their own functions which either return the appropriate value or return ''. If all return '' then return the data as it is.
Following is the code, is there a better way to avoid duplicating function invocations in both if check and return?
def process(data):
if get_name(data):
return get_name(data)
elif get_address(data):
return get_address(data)
elif get_phone(data):
return get_phone(data)
return data
As per your code logic, you can write it like this. The or sequence will return the first positive data, in other words the data that is truthy.
def process(data):
name = get_name(data)
address = get_address(data)
phone = get_phone(data):
return name or address or phone or data
You can use walrus operator := if you use python 3.8+
def process(data):
if name:=get_name(data):
return name
elif address := get_address(data):
return address
elif phone := get_phone(data):
return phone
return data

How to best remove redundant calls from a code block, that should be executed when the block exists successfully?

I'm trying to simplify the following code (removing the redundant prints), but can't find a satisfying way to do this:
original code
def main():
if expression1:
print("1")
print("always_do_this")
return
if expression2:
print("2")
print("always_do_this")
return
# ... possibly more expressions and thus redundancy
print("always_do_this")
# do something else
My first idea was a try-(except-)else combination, but the else is not executed on a return in the try-block.
Solution 1 - extracting into a separate function
def func():
if expression1:
print("1")
return True
if expression2:
print("2")
return True
return False
def main():
result = func()
print("always_do_this")
if result:
return
# do something else
Solution 2 - workaround using finally
def main():
error = False
try:
if expression1:
print("1")
return
if expression2:
print("2")
return
except:
error = True
raise
finally:
if not error:
print("always_do_this")
# do something else
Surely there must be a better way to achieve this in python?
PS: Also any ideas for a better title would be appreciated...
PPS: I'm not directly asking about (subjective) codestyle, but wondering if there is a way to write this that I didn't consider (e.g. a language construct/pattern, that also makes the code more concise/cleaner; obviously there are a lot worse ways).
Check if your flow did not enter the first two if blocks by checking for the opposite of the first two if statements joined by an andso that you can execute "do something else" only if the first two if statements failed. Return at the end instead of in the middle of the if statements.
def main():
expression1 = True
expression2 = False
if expression1:
print("1")
elif expression2:
print("2")
print("always_do_this")
if not expression1 and not expression2:
# do something else
return
If the thing you always want to do is closing a file, I would use a with statement. In a more general case you can create your own context manager to have full control of what gets run at the end.
Sample code:
class my_closer:
def __enter__(self):
return True
def __exit__(self, type, value, traceback):
if type is None:
print("always_do_this")
else
print("An exception was raised: {}".format(type))
def main():
with my_closer() as c:
if someexpr:
print("1")
return
if someexpr:
print("2")
return
I added a superfluous else to print something about the exception in case of error, but leave it out to reproduce your original code more accurately.
This code is not shorter than yours with the trivial print statement, but I like this method for more complex "closer" code.
You can also define the context manager this way using the contextlib library:
from contextlib import contextmanager
#contextmanager
def my_closer(*args, **kwds):
try:
yield True
except:
#print("some error happened")
raise
else:
print("always_do_this")
References:
http://effbot.org/zone/python-with-statement.htm
https://docs.python.org/3/library/stdtypes.html#typecontextmanager
https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager

Workaround for equality of nested functions

I have a nested function that I'm using as a callback in pyglet:
def get_stop_function(stop_key):
def stop_on_key(symbol, _):
if symbol == getattr(pyglet.window.key, stop_key):
pyglet.app.exit()
return stop_on_key
pyglet.window.set_handler('on_key_press', get_stop_function('ENTER'))
But then I run into problems later when I need to reference the nested function again:
pyglet.window.remove_handler('on_key_press', get_stop_function('ENTER'))
This doesn't work because of the way python treats functions:
my_stop_function = get_stop_function('ENTER')
my_stop_function is get_stop_function('ENTER') # False
my_stop_function == get_stop_function('ENTER') # False
Thanks to two similar questions I understand what is going on but I'm not sure what the workaround is for my case. I'm looking through the pyglet source code and it looks like pyglet uses equality to find the handler to remove.
So my final question is: how can I override the inner function's __eq__ method (or some other dunder) so that identical nested functions will be equal?
(Another workaround would be to store a reference to the function myself, but that is duplicating pyglet's job, will get messy with many callbacks, and anyways I'm curious about this question!)
Edit: actually, in the questions I linked above, it's explained that methods have value equality but not reference equality. With nested functions, you don't even get value equality, which is all I need.
Edit2: I will probably accept Bi Rico's answer, but does anyone know why the following doesn't work:
def get_stop_function(stop_key):
def stop_on_key(symbol, _):
if symbol == getattr(pyglet.window.key, stop_key):
pyglet.app.exit()
stop_on_key.__name__ = '__stop_on_' + stop_key + '__'
stop_on_key.__eq__ = lambda x: x.__name__ == '__stop_on_' + stop_key + '__'
return stop_on_key
get_stop_function('ENTER') == get_stop_function('ENTER') # False
get_stop_function('ENTER').__eq__(get_stop_function('ENTER')) # True
You could create a class for your stop functions and define your own comparison method.
class StopFunction(object):
def __init__(self, stop_key):
self.stop_key = stop_key
def __call__(self, symbol, _):
if symbol == getattr(pyglet.window.key, self.stop_key):
pyglet.app.exit()
def __eq__(self, other):
try:
return self.stop_key == other.stop_key
except AttributeError:
return False
StopFunciton('ENTER') == StopFunciton('ENTER')
# True
StopFunciton('ENTER') == StopFunciton('FOO')
# False
the solution is to keep a dictionary containing the generated functions around,
so that when you make the second call, you get the same object as in the first call.
That is, simply build some memoization logic, or use one of the libraries
existing with memoizing decorators:
ALL_FUNCTIONS = {}
def get_stop_function(stop_key):
if not stop_key in ALL_FUNCTIONS:
def stop_on_key(symbol, _):
if symbol == getattr(pyglet.window.key, stop_key):
pyglet.app.exit()
ALL_FUNCTIONS[stop_key] = stop_on_key
else:
stop_on_key = ALL_FUNCTIONS[stop_key]
return stop_on_key
You can generalize Bi Rico's solution to allow wrapping any functions up with some particular equality function pretty easily.
The first problem is defining what the equality function should check. I'm guessing for this case, you want the code to be identical (meaning functions created from the same def statement will be equal, but two functions created from character-for-character copies of the def statement will not), and the closures to be equal (meaning that if you call get_stop_function with two equal but non-identical stop_keys the functions will be equal), and nothing else to be relevant. But that's just a guess, and there are many other possibilities.
Then you just wrap a function the same way you'd wrap any other kind of object; just make sure __call__ is one of the things you delegate:
class EqualFunction(object):
def __init__(self, f):
self.f = f
def __eq__(self, other):
return (self.__code__ == other.__code__ and
all(x.cell_contents == y.cell_contents
for x, y in zip(self.__closure__, other.__closure__)))
def __getattr__(self, attr):
return getattr(self.f, attr)
def __call__(self, *args, **kwargs):
return self.f(*args, **kwargs)
If you want to support other dunder methods that aren't required to go through getattr (I don't think any of them are critical for functions, but I could be wrong…), either do it explicitly (as with __call__) or loop over them and add a generic wrapper to the type for each one.
To use the wrapper:
def make_f(i):
def f():
return i
return EqualFunction(f)
f1 = f(0)
f2 = f(0.0)
assert f1 == f2
Or, notice that EqualFunction actually works as a decorator, which may be more readable.
So, for your code:
def get_stop_function(stop_key):
#EqualFunction
def stop_on_key(symbol, _):
if symbol == getattr(pyglet.window.key, stop_key):
pyglet.app.exit()
return stop_on_key

Categories

Resources