How to do variable assignment inside a while(expression) loop in Python? - python

I have the variable assignment in order to return the assigned value and compare that to an empty string, directly in the while loop.
Here is how I'm doing it in PHP:
while((name = raw_input("Name: ")) != ''):
names.append(name)
What I'm trying to do is identical to this in functionality:
names = []
while(True):
name = raw_input("Name: ")
if (name == ''):
break
names.append(name)
Is there any way to do this in Python?

from functools import partial
for name in iter(partial(raw_input, 'Name:'), ''):
do_something_with(name)
or if you want a list:
>>> names = list(iter(partial(raw_input, 'Name: '), ''))
Name: nosklo
Name: Andreas
Name: Aaron
Name: Phil
Name:
>>> names
['nosklo', 'Andreas', 'Aaron', 'Phil']

You can wrap raw_input() to turn it into a generator:
def wrapper(s):
while True:
result = raw_input(s)
if result = '': break
yield result
names = wrapper('Name:')
which means we're back to square one but with more complex code. So if you need to wrap an existing method, you need to use nosklo's approach.

No, sorry. It's a FAQ, explained well here:
In Pydocs, and Fredrik Lundh's blog.
The reason for not allowing assignment in Python expressions is a common, hard-to-find bug in those other languages.
Many alternatives have been proposed. Most are hacks that save some typing but use arbitrary or cryptic syntax or keywords, and fail the simple criterion for language change proposals: it should intuitively suggest the proper meaning to a human reader who has not yet been introduced to the construct.
An interesting phenomenon is that most experienced Python programmers recognize the while True idiom and don’t seem to be missing the assignment in expression construct much; it’s only newcomers who express a strong desire to add this to the language.
There’s an alternative way of spelling this that seems attractive:
line = f.readline() while line:
... # do something with line...
line = f.readline()

I'm only 7 years late, but there's another solution. It's not the best solution I can think of, but it highlights an interesting use of the StopIteration exception. You can do a similar loop for chunk reading files/sockets and handle Timeouts and whatnot nicely.
names=[]
try:
while True:
f = raw_input()
if not f:
raise StopIteration
else:
names.append(f)
except StopIteration:
pass
print names

names = []
for name in iter(lambda: raw_input("Name: "), ''):
names.append(name)

PEP 572 proposes Assignment Expressions and has already been accepted. Starting with Python 3.8, you will be able to write:
while name := input("Name: "):
names.append(name)
Quoting the Syntax and semantics part of the PEP for some more examples:
# Handle a matched regex
if (match := pattern.search(data)) is not None:
# Do something with match
# A loop that can't be trivially rewritten using 2-arg iter()
while chunk := file.read(8192):
process(chunk)
# Reuse a value that's expensive to compute
[y := f(x), y**2, y**3]
# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]

Related

Can I shorten this piece of code with less elif? [duplicate]

I love using the expression
if 'MICHAEL89' in USERNAMES:
...
where USERNAMES is a list.
Is there any way to match items with case insensitivity or do I need to use a custom method? Just wondering if there is a need to write extra code for this.
username = 'MICHAEL89'
if username.upper() in (name.upper() for name in USERNAMES):
...
Alternatively:
if username.upper() in map(str.upper, USERNAMES):
...
Or, yes, you can make a custom method.
str.casefold is recommended for case-insensitive string matching. #nmichaels's solution can trivially be adapted.
Use either:
if 'MICHAEL89'.casefold() in (name.casefold() for name in USERNAMES):
Or:
if 'MICHAEL89'.casefold() in map(str.casefold, USERNAMES):
As per the docs:
Casefolding is similar to lowercasing but more aggressive because it
is intended to remove all case distinctions in a string. For example,
the German lowercase letter 'ß' is equivalent to "ss". Since it is
already lowercase, lower() would do nothing to 'ß'; casefold()
converts it to "ss".
I would make a wrapper so you can be non-invasive. Minimally, for example...:
class CaseInsensitively(object):
def __init__(self, s):
self.__s = s.lower()
def __hash__(self):
return hash(self.__s)
def __eq__(self, other):
# ensure proper comparison between instances of this class
try:
other = other.__s
except (TypeError, AttributeError):
try:
other = other.lower()
except:
pass
return self.__s == other
Now, if CaseInsensitively('MICHAEL89') in whatever: should behave as required (whether the right-hand side is a list, dict, or set). (It may require more effort to achieve similar results for string inclusion, avoid warnings in some cases involving unicode, etc).
Usually (in oop at least) you shape your object to behave the way you want. name in USERNAMES is not case insensitive, so USERNAMES needs to change:
class NameList(object):
def __init__(self, names):
self.names = names
def __contains__(self, name): # implements `in`
return name.lower() in (n.lower() for n in self.names)
def add(self, name):
self.names.append(name)
# now this works
usernames = NameList(USERNAMES)
print someone in usernames
The great thing about this is that it opens the path for many improvements, without having to change any code outside the class. For example, you could change the self.names to a set for faster lookups, or compute the (n.lower() for n in self.names) only once and store it on the class and so on ...
Here's one way:
if string1.lower() in string2.lower():
...
For this to work, both string1 and string2 objects must be of type string.
I think you have to write some extra code. For example:
if 'MICHAEL89' in map(lambda name: name.upper(), USERNAMES):
...
In this case we are forming a new list with all entries in USERNAMES converted to upper case and then comparing against this new list.
Update
As #viraptor says, it is even better to use a generator instead of map. See #Nathon's answer.
You could do
matcher = re.compile('MICHAEL89', re.IGNORECASE)
filter(matcher.match, USERNAMES)
Update: played around a bit and am thinking you could get a better short-circuit type approach using
matcher = re.compile('MICHAEL89', re.IGNORECASE)
if any( ifilter( matcher.match, USERNAMES ) ):
#your code here
The ifilter function is from itertools, one of my favorite modules within Python. It's faster than a generator but only creates the next item of the list when called upon.
To have it in one line, this is what I did:
if any(([True if 'MICHAEL89' in username.upper() else False for username in USERNAMES])):
print('username exists in list')
I didn't test it time-wise though. I am not sure how fast/efficient it is.
Example from this tutorial:
list1 = ["Apple", "Lenovo", "HP", "Samsung", "ASUS"]
s = "lenovo"
s_lower = s.lower()
res = s_lower in (string.lower() for string in list1)
print(res)
My 5 (wrong) cents
'a' in "".join(['A']).lower()
UPDATE
Ouch, totally agree #jpp, I'll keep as an example of bad practice :(
I needed this for a dictionary instead of list, Jochen solution was the most elegant for that case so I modded it a bit:
class CaseInsensitiveDict(dict):
''' requests special dicts are case insensitive when using the in operator,
this implements a similar behaviour'''
def __contains__(self, name): # implements `in`
return name.casefold() in (n.casefold() for n in self.keys())
now you can convert a dictionary like so USERNAMESDICT = CaseInsensitiveDict(USERNAMESDICT) and use if 'MICHAEL89' in USERNAMESDICT:

trying to use "or" operator elegantly [duplicate]

This question already has an answer here:
Re-use of a regular expression capture group in Python
(1 answer)
Closed 1 year ago.
I have a small bit of regex that I use to generate a new string:
someinput = "some/very/long/string"
changedinput = re.match("(.*\/)", str(thisDir)).group(1)[:-1]
I use .group(1)[:-1] to get the actual string, and this code needs to work for all kinds of strings.
If the inputstring here is "blbalba", then there is not match, and the regex will return Nonetype, therefore, this error appears when the grou() call is made:
'NoneType' object has no attribute 'group'
I could solve with a construction like this:
someinput = "some/very/long/string"
tmp = re.match("(.*\/)", str(bla))# .group(1)[:-1]
if (tmp):
changedinput = tmp.group(1)[:-1]
else:
changedinput = ""
It does annoy me though that I need six lines of code for this very simple construct.
So I started searching for something like a elvis constructor in python, and found the "or" operator.
I then thought I could maybe do something like this:
someinput = "some/very/long/string"
changedinput = re.match("(.*\/)", str(someinput)).group(1)[:-1] or ""
The issue here of cause, is that if there is not match, then the group() call will throw the same error again.
Is there any way I can do this, in very few lines of code, in a nice and pythonic way?
You can use an assignment expression:
changedinput = (tmp.group(1)[:-1]
if (tmp := re.match("(.*\/)", str(someinput))
else "")
re.match is called and its return value is assigned to tmp before it is evaluated in a boolean context. If it is true, the value of tmp is used to call group rather than calling re.match a second time.
You could do something like:
changedinput = tmp.group(1)[:-1] if tmp else ""
This is also more "pythonic" in that it's even less code, but it is less efficient since you are calling re.match() twice:
changedinput = re.match("(.*\/)", str(someinput)).group(1)[:-1] if re.match("(.*\/)", str(someinput)) else ""

Alternatives for 3 lines of java-like code?

Assume you have a function, that sometimes returns a value, and sometimes doesn't, because there really is nothing you could return in this case, not even a default value or something. Now you want to do something with the result, but of course only when there is one.
Example:
result = function_call(params)
if result:
print result
Is there a way to write this in a more pythonic way, maybe even in one line?
Like that:
print function_call(params) or #nothing
(Note that I mean it shouldn't print "nothing" or "None". It should actually just not print at all, if the result is None)
No; in Python, name binding is a statement and so cannot be used as an expression within a statement. Since print is also a statement you're going to require 3 lines; in Python 3 you could write:
result = function_call(params)
print(result) if result else None
This isn't quite true for name binding within a comprehension or generator, where name binding is a syntax item that has statement-like semantics:
[print(result) for result in generator_call(params) if result]
As Kos says, you can abuse this to create a one-element comprehension:
[print(result) for result in (function_call(params), ) if result]
Another syntax item that performs name binding and can similarly be abused is the lambda expression:
(lambda result: print(result) if result else None)(function_call(params))
Note that in both these cases the operation on the return value must be an expression and not a statement.
I think the more Pythonic version is actually closer to your original:
result = function_call(params)
if result is not None:
do_something(result)
Checking for is (not) None seems very idiomatic to me - I've used it several times myself and I've also seen it used elsewhere[citation-needed].
From the answers up to now I would do that:
>>> from __future__ import print_function #if Python2.7
>>> def filtered_print(txt):
... txt and print(txt)
...
>>> filtered_print('hello world')
hello world
>>> filtered_print('None')
None
>>> filtered_print(None)
>>>
If someone else has a better solution in mind, I am still open for alternatives, though!

Python: avoiding if condition for this code?

for the following code
a =func()
if a != None:
b.append(a)
a can be assigned to None, is there a way to avoid the if statement and only use one line of code?
original problem is the following
import xml.etree.ElementTree as etree
r = etree.parse(f).getroot()
b = etree.Element('register',{})
a = r.find('tag_name') # a may get None if did not find it
if a != None:
b.append(a)
ok, I used all the answers and got this, personally I think it's the most complex python I have ever wrote so far, lol
NS_MAP = {
'spirit' : 'http://www.spiritconsortium.org/XMLSchema/SPIRIT/1.4',
'app' : 'http://www.app.com/SPIRIT-app'
}
mp=etree.Element('MemoryProperty', {'version':'alpha'})
mpt=etree.ElementTree(mp)
def copy_tags(tp, op, p, tn, ns='spirit'):
c = p.find('{%s}%s'%(NS_MAP[ns],tn))
if c is not None:
(op == '<-') and tp.append(c)
return c
for reg in regs:
te = etree.Element('register',{})
copy_tags(te,'<-',reg,'name')
copy_tags(te,'<-',reg,'addressOffset')
copy_tags(te,'<-',reg,'access')
(lambda e, t: copy_tags(te,'<-',t,'usageConstraints',ns='app') if t is not None else None)(te, copy_tags(te,'|',reg,'vendorExtensions'))
mp.append(te)
mpt.write('map_gen.xml')
If you can call func() beforehand, and you want to combine the test and assignment statements into a single statement, then you can do this, with an if-else expression:
b += [a] if a is not None else []
If a is not None, then this will add [a] to b -- essentially the same operation as b.append(a)
If a is None, then this will add [] to b, which will leave b unchanged.
This won't work unless b is a list, or at least supports "+=" in-place addition. If it doesn't -- perhaps it's some custom object, then you should be able to do this:
(b.append(a) if a is not None else None)
This is an expression, evaluated for its side effects, and then thrown away. If a is None, then the b.append(a) call will never be executed. In either case, the value of the expression is None, but we don't care about it, so it gets ignored.
Now, if you want to combine the func() call with this, then you'll have to do something different in order to avoid calling func twice. If you can use the "+=" syntax, then you can do it like this:
b += filter(None, [func()])
filter(None, <list>) returns the list with all false elements (None included, but also 0 and []) removed. This statement, then, will add either [func()] or [] to b.
[Edited]
Finally, for the worst case scenario: If you can't call func() more than once, and you can't use b += <list>, and you need to accept 0, "", [], etc, and only exclude None, and you need it all on one line, here's the ugliest line of code yet:
(lambda l, a: l.append(a) if a is not None else None)(b, func())
This is essentially #ekhumoro's solution, compressed into one line. It defines an anonymous function, calls it, discards the value, and then discards the function, all for the sake of the side effect.
Now, this is a single line, but it's certainly not easier to read or understand than the original code. If I were you, I'd stick with the original, or go with #ekhumoro's idea of just defining a helper function and using that.
python 3.8 walrus operator
if a := func(): b.append(a)
You asked the wrong question here. The clue is in your reply to one of the comments where you say "I have 10+ tags, if I can get 3 line to 1 line, I will save 20+ lines".
So your problem actually is not that you have 3 lines of code but that you are needlessly repeating 3 lines of code over and over. You could use a function to extract the repeated lines, but it sounds like in this case you may actually want a loop:
THE_TAGS = ('tag1', 'tag2', 'and so on')
for tag in THE_TAGS:
a = r.find(tag) # a may get None if did not find it
if a != None:
b.append(a)
Or if you need to append to different lists:
def extract_tag(r, tag_name, to):
a = r.find(tag_name) # a may get None if did not find it
if a != None:
to.append(a)
extract_tag(r, 'tag1', b)
extract_tag(r, 'tag2', c)
Short answer: Not really.
Longer answer: If you really wanted to avoid this (perhaps because you want to implement this behavior --- appending only non-None values) from several different blocks of code) then you could create a class as a proxy around the underlying b object and hide the details in its append method.
class NonNoneAppender:
def __init__(self, obj):
if not hasattr(obj, 'append') or not callable(obj.append):
raise ValueError, "Object must have append method"
self.__obj = obj
def append(self, item):
if item is not None:
return self.__obj.append(item)
def __getattr__(self, attr):
return getattr( self.__obj, attr)
... and then you could do something like:
b = NonNoneAppender(b)
However, I'm not sure this would make any sense at all for your code.
Attacking your real problem, and doing it in two lines for clarity:
temp = [r.find(tag) for tag in list_of_tags]
b.extend(x for x in temp if x is not None)
Note: Element.extend is new in Python 2.7/3.2
Presumably you're not trying to remove just a single if statement from your code...
So the obvious answer is to use a function:
import xml.etree.ElementTree as etree
def append(parent, child):
if child is not None:
parent.append(child)
r = etree.parse(f).getroot()
b = etree.Element('register',{})
append(b, r.find('tag_name'))
You can just add everything and remove Nones at the end with b = [a for a in b if b is not None]. Or, in your particular use case, you can do b.extend(r.findall('tag_name')[:1]). This may be a bit slower, however, as it will go through the whole tree, rather than stopping at the first instance.
b+=list(set([r.find('tag_name')])-set([None]))
But it's very ugly. A little cleaner, but also a line longer:
b.append(r.find('tag_name'))
b.remove(None)
Still not very neat though. If I were you I'd just keep that if statement.

How to loop until EOF in Python?

I need to loop until I hit the end of a file-like object, but I'm not finding an "obvious way to do it", which makes me suspect I'm overlooking something, well, obvious. :-)
I have a stream (in this case, it's a StringIO object, but I'm curious about the general case as well) which stores an unknown number of records in "<length><data>" format, e.g.:
data = StringIO("\x07\x00\x00\x00foobar\x00\x04\x00\x00\x00baz\x00")
Now, the only clear way I can imagine to read this is using (what I think of as) an initialized loop, which seems a little un-Pythonic:
len_name = data.read(4)
while len_name != "":
len_name = struct.unpack("<I", len_name)[0]
names.append(data.read(len_name))
len_name = data.read(4)
In a C-like language, I'd just stick the read(4) in the while's test clause, but of course that won't work for Python. Any thoughts on a better way to accomplish this?
You can combine iteration through iter() with a sentinel:
for block in iter(lambda: file_obj.read(4), ""):
use(block)
Have you seen how to iterate over lines in a text file?
for line in file_obj:
use(line)
You can do the same thing with your own generator:
def read_blocks(file_obj, size):
while True:
data = file_obj.read(size)
if not data:
break
yield data
for block in read_blocks(file_obj, 4):
use(block)
See also:
file.read
I prefer the already mentioned iterator-based solution to turn this into a for-loop. Another solution written directly is Knuth's "loop-and-a-half"
while 1:
len_name = data.read(4)
if not len_name:
break
names.append(data.read(len_name))
You can see by comparison how that's easily hoisted into its own generator and used as a for-loop.
I see, as predicted, that the typical and most popular answer are using very specialized generators to "read 4 bytes at a time". Sometimes generality isn't any harder (and much more rewarding;-), so, I've suggested instead the following very general solution:
import operator
def funlooper(afun, *a, **k):
wearedone = k.pop('wearedone', operator.not_)
while True:
data = afun(*a, **k)
if wearedone(data): break
yield data
Now your desired loop header is just: for len_name in funlooper(data.read, 4):.
Edit: made much more general by the wearedone idiom since a comment accused my slightly less general previous version (hardcoding the exit test as if not data:) of having "a hidden dependency", of all things!-)
The usual swiss army knife of looping, itertools, is fine too, of course, as usual:
import itertools as it
for len_name in it.takewhile(bool, it.imap(data.read, it.repeat(4))): ...
or, quite equivalently:
import itertools as it
def loop(pred, fun, *args):
return it.takewhile(pred, it.starmap(fun, it.repeat(args)))
for len_name in loop(bool, data.read, 4): ...
The EOF marker in python is an empty string so what you have is pretty close to the best you are going to get without writing a function to wrap this up in an iterator. I could be written in a little more pythonic way by changing the while like:
while len_name:
len_name = struct.unpack("<I", len_name)[0]
names.append(data.read(len_name))
len_name = data.read(4)
I'd go with Tendayi's suggestion re function and iterator for readability:
def read4():
len_name = data.read(4)
if len_name:
len_name = struct.unpack("<I", len_name)[0]
return data.read(len_name)
else:
raise StopIteration
for d in iter(read4, ''):
names.append(d)

Categories

Resources