Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
SurgicalOntologist
Jun 17, 2004

ArcticZombie posted:

I'm doing something wrong/misunderstanding how to use asyncio. I want to read from stdin, start a task based on that input and be able to receive more input and start more tasks while waiting for that task to complete. But the first task seems to be blocking and I don't understand why. Here is an distilled example (with load simulating a task taking some time):

Python code:
import asyncio
import sys


async def load():
    await asyncio.sleep(3)
    return "done"


async def listen(loop):
    while True:
        recv = await loop.run_in_executor(None, sys.stdin.readline)
        print(f"Received: {recv}", end="")
        res = await load()
        print(f"Result {recv.strip()}: {res}")

loop = asyncio.get_event_loop()
loop.run_until_complete(listen(loop))
loop.close()

A coroutine blocks every time you use await. Although they allow other coroutines to run at those times, they nevertheless themselves run top-to-bottom. So, your while loop never returns to the top before the second await returns.

For your desired behavior, the call to readline must be the only await within the loop. You need to schedule the execution of a coroutine without awaiting it. This can be done with futures. Something like:
Python code:
import asyncio
from functools import partial
import sys


async def load():
    await asyncio.sleep(3)
    return "done"


def print_result(input_, future):
    print(f"Result {input_.strip()}: {future.result()}")


async def listen(loop):
    while True:
        recv = await loop.run_in_executor(None, sys.stdin.readline)
        print(f"Received: {recv}", end="")
        future = asyncio.ensure_future(load(), loop=loop)
        future.add_done_callback(partial(print_result, recv))

loop = asyncio.get_event_loop()
loop.run_until_complete(listen(loop))
loop.close()
Note that if your slow operation is CPU-bound rather than IO-bound there may be a better solution than making it a coroutine, but this is how you get your desired behavior at least.

Adbot
ADBOT LOVES YOU

ArcticZombie
Sep 15, 2010
The "tasks" are network IO, I wanted to fire them off as quickly as input came in. I was under the mistaken belief that at the await, the event loop would start another listen coroutine or do some other magic to allow the loop to continue processing input while the original coroutine was blocked (thinking about how this would work now, this sounds dumb), so thanks for putting me straight on that.

Jose Cuervo
Aug 25, 2004
I am trying to understand what I am doing wrong with a Pandas merge of two data frames:

Python code:
merged_df = df_one.merge(df_two, how='left', on=['ID', 'Date'])
My understanding of this merge is that merged_df would have exactly the same number of rows as df_one because I am merging only on keys that exist in df_one (i.e., each ('ID', 'Date') tuple from df_one is iterated over and if that tuple exists as a row in df_two, then that data is copied over, and ('ID', 'Date') tuples in df_two that don't exist in df_one are ignored, correct?). However, when I run this code with my data, merged_df ends up having more rows than df_one has, and I don't know what I am doing wrong.

vikingstrike
Sep 23, 2007

whats happening, captain

Jose Cuervo posted:

I am trying to understand what I am doing wrong with a Pandas merge of two data frames:

Python code:
merged_df = df_one.merge(df_two, how='left', on=['ID', 'Date'])
My understanding of this merge is that merged_df would have exactly the same number of rows as df_one because I am merging only on keys that exist in df_one (i.e., each ('ID', 'Date') tuple from df_one is iterated over and if that tuple exists as a row in df_two, then that data is copied over, and ('ID', 'Date') tuples in df_two that don't exist in df_one are ignored, correct?). However, when I run this code with my data, merged_df ends up having more rows than df_one has, and I don't know what I am doing wrong.

Is df_two unique on ID, Date?

Jose Cuervo
Aug 25, 2004

vikingstrike posted:

Is df_two unique on ID, Date?

Are you asking if all the (ID, Date) tuple combinations in df_two are unique? If so, yes. df_two was generated using groupby where on=['ID', 'Date'].

vikingstrike
Sep 23, 2007

whats happening, captain

Jose Cuervo posted:

Are you asking if all the (ID, Date) tuple combinations in df_two are unique? If so, yes. df_two was generated using groupby where on=['ID', 'Date'].

Er, sorry. Unique was a bad word. In df_two does (ID, Date) ever include multiple rows?

Jose Cuervo
Aug 25, 2004

vikingstrike posted:

Er, sorry. Unique was a bad word. In df_two does (ID, Date) ever include multiple rows?

No, because df_two was generated using a groupby statement where on=['ID', 'Date'] - so every row in df_two corresponds to a unique ('ID', 'Date') tuple.

wasey
Apr 6, 2009
I have another newbie question. I have to read a text file for an activity selection problem and am having trouble getting the data from the file. The file format lists the number of activities, followed the the activity id number, start time, and finish time. In this instance there are two sets of activities:
code:
11
1 1 4
2 3 5
3 0 6
4 5 7
5 3 9
6 5 9
7 6 10
8 8 11
9 8 12
10 2 14
11 12 16
3
3 6 8
1 7 9
2 1 2
How do I assign each line element to an object in Python, e.g. first task ID = 1, start time = 1, finish time = 4? I created an activity class
code:
class Activity():
	def __init__(self, id=None, start=None, finish=None):
		self.id = id
		self.start = start
		self.finish = finish
but I can't seem to properly assign the info. Thanks for helping out

Cingulate
Oct 23, 2012

by Fluffdaddy

wasey posted:

How do I assign each line element to an object in Python
First of all, and apologies if this is a dumb question, but are you perfectly sure you actually do want that? Why?

vikingstrike
Sep 23, 2007

whats happening, captain

Jose Cuervo posted:

No, because df_two was generated using a groupby statement where on=['ID', 'Date'] - so every row in df_two corresponds to a unique ('ID', 'Date') tuple.

Try the indicator flag on the merge and then use it to see if it might lead you to where the extra rows are coming.

wasey
Apr 6, 2009

Cingulate posted:

First of all, and apologies if this is a dumb question, but are you perfectly sure you actually do want that? Why?

I'm the dumb one here, trying to learn Python on the fly for a class and it has been rough. I'm not sure that I want to do that, I just want to make sure that an activity's start and end time are not separated after I sort them by start time. I'm able to put the number of elements in a set in one list(11, 3) and the elements themselves into another list, but I don't know how to proceed from there

Dr Subterfuge
Aug 31, 2005

TIME TO ROC N' ROLL

wasey posted:

but I can't seem to properly assign the info. Thanks for helping out

How have you been trying to do it so far?

baka kaba
Jul 19, 2003

PLEASE ASK ME, THE SELF-PROFESSED NO #1 PAUL CATTERMOLE FAN IN THE SOMETHING AWFUL S-CLUB 7 MEGATHREAD, TO NAME A SINGLE SONG BY HIS EXCELLENT NU-METAL SIDE PROJECT, SKUA, AND IF I CAN'T PLEASE TELL ME TO
EAT SHIT

wasey posted:


How do I assign each line element to an object in Python, e.g. first task ID = 1, start time = 1, finish time = 4? I created an activity class
code:
class Activity():
	def __init__(self, id=None, start=None, finish=None):
		self.id = id
		self.start = start
		self.finish = finish
but I can't seem to properly assign the info. Thanks for helping out

You need to work out your plan first. Basically (if I'm reading this right) what you want to do is:
  • read in a line
  • assume it's a single number that represents a line count, so parse that as an int
  • read and parse the next count lines as Activities
  • repeat this whole thing until you run out of lines

and the activity parsing bit goes
  • read in a line
  • assume it represents an activity, i.e. three numbers separated by spaces
  • split and parse that line as three ints
  • do something with that data

hopefully that makes it a bit clearer it's the "do something with that data" where you need to store your activities
You might not want to use classes, but only because it's not 'pythonic' - there's nothing wrong with using them while you get your head around the problem. It's just there are simpler tools you can use that will do the same job here (better, even)

edit- it might help to explain exactly what you want to have at the end of this. How should it be organised? Do you need to store the 11 and 3, what do they represent?

baka kaba fucked around with this message at 19:24 on Feb 3, 2018

Cingulate
Oct 23, 2012

by Fluffdaddy

wasey posted:

I'm the dumb one here, trying to learn Python on the fly for a class and it has been rough. I'm not sure that I want to do that, I just want to make sure that an activity's start and end time are not separated after I sort them by start time. I'm able to put the number of elements in a set in one list(11, 3) and the elements themselves into another list, but I don't know how to proceed from there
What do you want to do with the data? is it literally only sorting? Because if you want to do anything more with that, you'll probably want to use Pandas, or at least Numpy.

Once you have the data in either format, the sorting will be absolutely trivial (literally df.sort()), but it would be a bit more complicated to get the data in there due to the lone "3" in the 4th to last line.

So really, it depends a bit on what exactly you want to do.

QuarkJets
Sep 8, 2008

Cingulate posted:

What do you want to do with the data? is it literally only sorting? Because if you want to do anything more with that, you'll probably want to use Pandas, or at least Numpy.

Once you have the data in either format, the sorting will be absolutely trivial (literally df.sort()), but it would be a bit more complicated to get the data in there due to the lone "3" in the 4th to last line.

So really, it depends a bit on what exactly you want to do.

It looks like the data is such that any line with 1 integer is just a count of the number of subsequent rows until the next single-integer line

wasey if you wanted to use classes you could do something like this:
Python code:
# let's just make a list of all of the activities
activity_list = []
# open file
with open('my_file.txt', 'r') as fi:
    # iterate over all lines in the file
    for line in fi:
        # each line is either 1 integer or 3 integers separated by spaces
        # split the line by spaces
        line_split = line.split(' ')
        # throw away the single-integer lines
        if len(line_split) != 3:
            new_activity = Activity(int(line_split[0]), int(line_split[1]), int(line_split[2]))
            # Maybe we put the activity in a list or something
            activity_list.append(new_activity)
I agree with what others are saying, you probably want a dataframe or something. This is just a demonstrative example of what you're currently trying to do

wasey
Apr 6, 2009

wasey posted:

poo poo code

I took the advice of everyone in the thread and went with the select-all -> delete method and started over. I made things overly complicated and was trying to manipulate a list of list of lists (without fully realizing it) and things went downhill from there. Thanks to all to chimed in

Jose Cuervo
Aug 25, 2004

vikingstrike posted:

Try the indicator flag on the merge and then use it to see if it might lead you to where the extra rows are coming.

Just wanted to say you got me thinking about having duplicates, and df_one actually had non-unique ('ID', 'Date') tuples, so that is where the problem was. Thanks for the help.

Wallet
Jun 19, 2006

If I have a list of numbers, like so:
[0, 14, 18, 20, 36, 41, 62, 70, 72]

And I want to extract all longest possible lists from it where no two consecutive items are more than, say, 5 apart:
[[14, 18, 20], [36, 41], [70, 72]]

It's not hard to construct a loop to do this with a few conditionals, but is there an elegant python solution?

baka kaba
Jul 19, 2003

PLEASE ASK ME, THE SELF-PROFESSED NO #1 PAUL CATTERMOLE FAN IN THE SOMETHING AWFUL S-CLUB 7 MEGATHREAD, TO NAME A SINGLE SONG BY HIS EXCELLENT NU-METAL SIDE PROJECT, SKUA, AND IF I CAN'T PLEASE TELL ME TO
EAT SHIT

I think you'll have to post your code to know if there's a more elegant way, but I don't think Python really encourages anything besides the ol' for loops in this case

Some languages have some kind of window function, which you could use to produce a sequence of each element and its successor (so in your case (0, 14), (14, 18), (18, 20) etc). That way you get a stream of items and the thing you need to compare them to, and you can start to group them based on whether they meet your rule or not. Python doesn't have this though, for some reason

It does have a reduce function but again, I'm not sure I'd exactly call it elegant here - it's definitely not as Pythonic (i.e. readable to general Python users) as a bunch of loops and conditionals doing the same thing

Python code:
from functools import reduce

def group_close_items(groups, item):
  # groups is a list of lists - we get the last (most recent) list, compare to
  # the last item, and either add our new item to that list if it's close enough
  # or start a new list for a new group if it's not
  last_group = groups[-1]
  if item - last_group[-1] <= 5:
    last_group.append(item)
  else:
    groups.append([item])
  return groups # with a reduce function you always return the new state
  
items = [0, 14, 18, 20, 36, 41, 62, 70, 72]
# we need to create the initial state for the reduce function, so it has something
# to compare items with - so we stick the first item in its own group, and use the
# rest of the items as the iterable we're reducing
groups = reduce(group_close_items, items[1:], [[items[0]]])
# looks like you're dropping any groups with a single item, so here ya go
no_singles = filter(lambda x: len(x)>1, groups)
print(no_singles)
you could probably clean that up a bit but it's still probably more awkward than what you've got. You could mess around with other stuff too but it seems like overkill for what you're doing

Dr Subterfuge
Aug 31, 2005

TIME TO ROC N' ROLL

Wallet posted:

If I have a list of numbers, like so:
[0, 14, 18, 20, 36, 41, 62, 70, 72]

And I want to extract all longest possible lists from it where no two consecutive items are more than, say, 5 apart:
[[14, 18, 20], [36, 41], [70, 72]]

It's not hard to construct a loop to do this with a few conditionals, but is there an elegant python solution?

Unless that first term is always supposed to be ignored, it would seem that your output list should start with a [0].

e: No 62 either. Are you just ignoring all singleton lists?

Dr Subterfuge fucked around with this message at 23:57 on Feb 8, 2018

Wallet
Jun 19, 2006

baka kaba posted:

I think you'll have to post your code to know if there's a more elegant way, but I don't think Python really encourages anything besides the ol' for loops in this case

Some languages have some kind of window function, which you could use to produce a sequence of each element and its successor (so in your case (0, 14), (14, 18), (18, 20) etc). That way you get a stream of items and the thing you need to compare them to, and you can start to group them based on whether they meet your rule or not. Python doesn't have this though, for some reason
Yeah, I was hoping there was something in this vein that would make it cleaner.

baka kaba posted:

Python code:
from functools import reduce...
you could probably clean that up a bit but it's still probably more awkward than what you've got. You could mess around with other stuff too but it seems like overkill for what you're doing
Your solution might actually be more elegant. I've been using this:
Python code:
def get_distance_groups(list, distance):
    out = []
    group = [list[0]]
    for number in list[1:]:
        if number - group[-1] <= distance:
            group.append(number)
        else:
            if len(group) > 1:
                out.append(group)
            group = [number]
    # Catch final group
    if len(group) > 1:
        out.append(group)
    return out

list = [0, 14, 18, 20, 36, 41, 62, 70, 72]
print(get_distance_groups(list, 5))
It's probably cleaner to add 1 length matches and then filter instead of checking along the way. I appreciate the help.


Dr Subterfuge posted:

Unless that first term is always supposed to be ignored, it would seem that your output list should start with a [0].

e: No 62 either. Are you just ignoring all singleton lists?
Yeah, sorry. I should have specified.

Wallet fucked around with this message at 00:23 on Feb 9, 2018

baka kaba
Jul 19, 2003

PLEASE ASK ME, THE SELF-PROFESSED NO #1 PAUL CATTERMOLE FAN IN THE SOMETHING AWFUL S-CLUB 7 MEGATHREAD, TO NAME A SINGLE SONG BY HIS EXCELLENT NU-METAL SIDE PROJECT, SKUA, AND IF I CAN'T PLEASE TELL ME TO
EAT SHIT

The more pythony way might be to have a generator that yields lists (basically consumes the iterator, adding each item to a list it's building until the item is too big, yields the old list and creates a new one for the item). That way you can feed that into another generator that filters out the single lists, or have the first generator just not yield a single item list

Eela6
May 25, 2007
Shredded Hen

baka kaba posted:

The more pythony way might be to have a generator that yields lists (basically consumes the iterator, adding each item to a list it's building until the item is too big, yields the old list and creates a new one for the item). That way you can feed that into another generator that filters out the single lists, or have the first generator just not yield a single item list

I agree. I would do it like this:

Python code:
from typing import *
def get_distance_groups(a: List[int], tol:int) -> Iterator[List[int]]:
    start, prev = 0, a[0]
    for i, n in enumerate(a):
        if abs(prev-n) > tol: # important! n could be negative!
            yield a[start:i]
            start = i
        prev = n
    if start < len(a)-1:
        yield a[start:]

Then filter the output.

IN:
Python code:
groups = get_distance_groups(a= [0, 14, 18, 20, 36, 41, 62, 70, 72], tol=5)
print([x for x in groups if len(x) > 1])
OUT:
[[14, 18, 20], [36, 41], [70, 72]]

Eela6 fucked around with this message at 00:36 on Feb 9, 2018

Wallet
Jun 19, 2006

Eela6 posted:

Python code:
from typing import *
def get_distance_groups(a: List[int], tol:int) -> Iterator[List[int]]:
    start, prev = 0, a[0]
    for i, n in enumerate(a):
        if abs(prev-n) > tol: # important! n could be negative!
            yield a[start:i]
            start = i
        prev = n
    if start < len(a)-1:
        yield a[start:]


Now using a modified version of this that self-filters (the numbers are indexes and can't be negative, though that's a good catch).

I had no idea yield or generators were a thing. I did a little reading which left me slightly puzzled, but stepping through it made it a lot clearer what's going on. That was really interesting, and will probably be useful in a lot of other contexts. Thanks!

baka kaba
Jul 19, 2003

PLEASE ASK ME, THE SELF-PROFESSED NO #1 PAUL CATTERMOLE FAN IN THE SOMETHING AWFUL S-CLUB 7 MEGATHREAD, TO NAME A SINGLE SONG BY HIS EXCELLENT NU-METAL SIDE PROJECT, SKUA, AND IF I CAN'T PLEASE TELL ME TO
EAT SHIT

I was thinking more like
Python code:
def group_em(items, max_diff):
  group = []
  for item in items:
    # if the group's empty, or if the item is close enough to the
    # last item in the group, stick this new one in there
    # otherwise spit out the group and start a new one with the item
    if not group or item - group[-1] <= max_diff:
      group.append(item)
    else:
      yield group
      group = [item]
  # when there's nothing left, produce the current group
  # (it'll only be empty if the input list was empty)
  yield group

items = [0, 14, 18, 20, 36, 41, 62, 70, 72]
grouped = group_em(items, 5)
# this is the same as filter(lambda x: len(x) > 1, grouped)
big_groups = (group for group in grouped if len(group) > 1)
print(list(big_groups))
I think it's clearer and more readable if you're just working with the lists you're going to return instead of messing around with indexes - you're just building a list, until you hit an item that you can't put in it, so you shove your list out the door and start a new one for the item. When you run out of items you send out that list and then you're done

You can do a length check before you yield (so you only get lists with 2 or more items), but there's two problems - one is that you have to put that code in there twice (you're yielding in two different places), and it also ties that implementation detail into the grouping function. It doesn't just split your list into groups where each item is close to the last, it also does another job of removing lists below a certain size. Personally I'd argue those are two different things (you might not even want the filtering all the time, or you might want to change the size limit), so it makes sense to do that separately, running it on the results of the grouping function

The nice thing about generators it's they're on-demand, so you can put them together like a pipeline. Instead of producing a full list of groups, and then producing another list where some of those groups are missing, that code up there iterates over big_groups, asking it to produce a single group each time. (big_groups is a generator comprehension, like a list comprehension but in ( ) so it produces a generator.) So in turn, big_groups asks grouped for an item, which it yields when it's built one. If big_groups discards it (because it doesn't meet the 'big enough' condition) then it asks for another one, until it has something it wants to yield

That doesn't necessarily matter, but you can see how you can set up a big chain that yielded items have to pass through, getting tweaked or dropped or whatever, creating a machine that produces single, finished items on demand. And that can be good for working with very large amounts of data, or something slow where you want to drop things as early as possible if you're not going to be using them later in the pipeline

baka kaba fucked around with this message at 02:36 on Feb 9, 2018

Wallet
Jun 19, 2006

Your approach is much more readable, at least to me.

baka kaba posted:

You can do a length check before you yield (so you only get lists with 2 or more items), but there's two problems - one is that you have to put that code in there twice (you're yielding in two different places), and it also ties that implementation detail into the grouping function. It doesn't just split your list into groups where each item is close to the last, it also does another job of removing lists below a certain size. Personally I'd argue those are two different things (you might not even want the filtering all the time, or you might want to change the size limit), so it makes sense to do that separately, running it on the results of the grouping function

I take your point about them being different functions, though in this case there isn't really a context where you'd want length 1 groups.

You can do the filtering without duplicating the code twice with a little tweaking (only one extra line of code, I think?):

Python code:
def group_em(num_list, distance):
    group = []
    for index, item in enumerate(items):
        if not group or item - group[-1] <= distance:
            group.append(item)
            if index < len(num_list) - 1:
                continue
        if len(group) > 1:
            yield group
        group = [item]

items = [0, 14, 18, 20, 36, 41, 62, 70, 72]
grouped = [num for num in group_em(items, 5)]
print(grouped)

baka kaba posted:

That doesn't necessarily matter, but you can see how you can set up a big chain that yielded items have to pass through, getting tweaked or dropped or whatever, creating a machine that produces single, finished items on demand. And that can be good for working with very large amounts of data, or something slow where you want to drop things as early as possible if you're not going to be using them later in the pipeline

Yeah, there's some combinatorics stuff I was messing with the other day that this approach will probably make possible, where before it just ate all of my memory and poo poo its pants.

baka kaba
Jul 19, 2003

PLEASE ASK ME, THE SELF-PROFESSED NO #1 PAUL CATTERMOLE FAN IN THE SOMETHING AWFUL S-CLUB 7 MEGATHREAD, TO NAME A SINGLE SONG BY HIS EXCELLENT NU-METAL SIDE PROJECT, SKUA, AND IF I CAN'T PLEASE TELL ME TO
EAT SHIT

Wallet posted:

I take your point about them being different functions, though in this case there isn't really a context where you'd want length 1 groups.

You can do the filtering without duplicating the code twice with a little tweaking (only one extra line of code, I think?):

Sure, that works! I feel like you're introducing a lot of complexity just to have a single yield statement in there though, and it's less clear what job it's actually doing. Personal preference obviously

And the thing with putting the filtering outside... I guess it's more of a functional approach, where you're encouraged to write smaller, simpler single-task functions and then compose them together - not necessarily because you're gonna use them in isolation too, just that it's really readable when you look at each one or the code that chains them together

It's great for writing pipeliney stuff, especially if you name your functions well, because you can see exactly what it's doing as a set of steps - like I had to infer you were dropping those groups by the example result you posted, but adding a line that filters them out makes it explicit, and you don't need to rely on comments either. Personal preference again of course!

Sad Panda
Sep 22, 2004

I'm a Sad Panda.
I got to exercise 49 of Learn Python the Hard Way before deciding to ditch it and skip the last 3 exercises.

I've picked up Python Crash Course and now up to chapter 8 (functions). Just a very quick question, is there a more Pythonic way of doing this, given my beginner level? I'm especially thinking of the way that I added "The Great" to each string in make_great().

Python code:
# Great Magicians
# Start with 8-9
# Write make_great() which modifies the list by adding "The Great" to each name

def show_magicians(magicians):
    for magician in magicians:
        print(magician)

def make_great(magicians):
    for i in range (0, len(magicians)):
        magicians[i] = "The Great " + magicians[i] 


magicians = ["David Blaine", "Derren Brown", "David Copperfield"]

make_great(magicians)
show_magicians(magicians)

Sad Panda fucked around with this message at 17:55 on Feb 9, 2018

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

Sad Panda posted:

I got to exercise 49 of Learn Python the Hard Way before deciding to ditch it and skip the last 3 exercises.

I've picked up Python Crash Course and now up to chapter 8 (functions). Just a very quick question, is there a more Pythonic way of doing this, given my beginner level? I'm especially thinking of the way that I added "The Great" to each string in make_great().

code:
# Great Magicians
# Start with 8-9
# Write make_great() which modifies the list by adding "The Great" to each name

def show_magicians(magicians):
    for magician in magicians:
        print(magician)

def make_great(magicians):
    for i in range (0, len(magicians)):
        magicians[i] = "The Great " + magicians[i] 


magicians = ["David Blaine", "Derren Brown", "David Copperfield"]

make_great(magicians)
show_magicians(magicians)

Python code:
great_magicians = [f'{magician} The Great' for magician in magicians]
(if you're not on python 3.6, replace the f-string with magician + ' The Great' or whatever your favored string concatenation/formatting paradigm is.)


Also, don't do this when looping over the elements of a list:

Python code:
def make_great(magicians):
    for i in range (0, len(magicians)):
        magicians[i] = "The Great " + magicians[i] 
If you want the index of the particular item, do this:

Python code:
for i, magician in enumerate(magicians):

baka kaba
Jul 19, 2003

PLEASE ASK ME, THE SELF-PROFESSED NO #1 PAUL CATTERMOLE FAN IN THE SOMETHING AWFUL S-CLUB 7 MEGATHREAD, TO NAME A SINGLE SONG BY HIS EXCELLENT NU-METAL SIDE PROJECT, SKUA, AND IF I CAN'T PLEASE TELL ME TO
EAT SHIT

If that's not clear, instead of replacing each string in the list with a new "great" version, you're just creating a new list by taking elements from the original, doing something with them and sticking the results in your new list. Like

Python code:
greats = []
for magician in magicians:
    greats.append("The Great " + magician)
return greats
so like you're doing now, but you don't need to bother with indices or lengths or anything. That one-line list comprehension version is a nice compact version of the above code, but that's basically what it's doing

Also this is a little advanced, but you can print all your magicians in one line:
Python code:
print(*magicians, sep='\n')
basically you can print more than one object by calling print(thing1, thing2, thing3...) - trouble is you have one thing, a list, and if you print that you'll just get a representation of that list structure. But if you add the * star in front of it, it basically explodes the iterable into a sequence of objects, so you get magician1, magician2 and so on. (Try it with and without the star and you'll see). The sep keyword argument is the separator for the items you're printing, you want each on its own line so we're separating with a newline character

Not saying this is better in this case, but it's good to know!

Sad Panda
Sep 22, 2004

I'm a Sad Panda.

Thermopyle posted:

Also, don't do this when looping over the elements of a list:

Python code:
def make_great(magicians):
    for i in range (0, len(magicians)):
        magicians[i] = "The Great " + magicians[i] 
If you want the index of the particular item, do this:

Python code:
for i, magician in enumerate(magicians):

Thanks! Using 3.6 and f-strings are the best thing in the world.
What is the reason for enumerate() instead of the iterating through i like I did it? More efficient algorithm? Just more Pythonic?

Sad Panda fucked around with this message at 18:06 on Feb 9, 2018

Sad Panda
Sep 22, 2004

I'm a Sad Panda.

baka kaba posted:

Also this is a little advanced, but you can print all your magicians in one line:
Python code:
print(*magicians, sep='\n')
basically you can print more than one object by calling print(thing1, thing2, thing3...) - trouble is you have one thing, a list, and if you print that you'll just get a representation of that list structure. But if you add the * star in front of it, it basically explodes the iterable into a sequence of objects, so you get magician1, magician2 and so on. (Try it with and without the star and you'll see). The sep keyword argument is the separator for the items you're printing, you want each on its own line so we're separating with a newline character

Not saying this is better in this case, but it's good to know!

Fab! That definitely is better and a wonderful use of *args. Saves a line as that function gets shortened to

Python code:
def show_magicians(magicians):
    print(*magicians, sep = "\n")

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

Sad Panda posted:

What is the reason for enumerate() instead of the iterating through i like I did it? More efficient algorithm? Just more Pythonic?

More pythonic because it's more explicit.

You usually see programming beginners and programmers who are used to C-ish languages using the range way to iterate through an iterable.

baka kaba
Jul 19, 2003

PLEASE ASK ME, THE SELF-PROFESSED NO #1 PAUL CATTERMOLE FAN IN THE SOMETHING AWFUL S-CLUB 7 MEGATHREAD, TO NAME A SINGLE SONG BY HIS EXCELLENT NU-METAL SIDE PROJECT, SKUA, AND IF I CAN'T PLEASE TELL ME TO
EAT SHIT

Another way you can look at it is, usually you do for thing in stuff to get a sequence of items, easy. If for whatever reason you want to number those items too, just call enumerate and it'll handle the counter and pass you the number with each item. The work's already done for you, there's less chance of making a mistake, it's clear what's happening, and if you don't unpack it as count, item then you have a tuple ready to pass into something else, you don't need to build it yourself

Slimchandi
May 13, 2005
That finger on your temple is the barrel of my raygun
When I was learning Python basics, it took me a long time to realise that when using a list comprehension, you can use any name in the for part.

I saw examples of [letter.lower() for letter in name] and assumed you hadto use letter if you were iterating through a string. :(

I always try to avoid doing callable/in-place stuff in a list comprehension eg [print(letter) for letter in name]. Is there any particular reason to avoid this? It seems to be more concise than the indentation of a for-loop.

Rocko Bonaparte
Mar 12, 2002

Every day is Friday!
Is Python packaging really a shitshow of deprecated and inconsistent standards? I was trying to figure out the right want to conditionally install module dependencies in a wheel based on OS and Python version. I got to this:

https://hynek.me/articles/conditional-python-dependencies/

According to this, all the ways of expressing stuff in extras_require is, like, wrong. I found a means that works with what we are using internally now, but this smells like it'll all fall apart 6 months from now.

Over the past few years, my general experience has been:

1. Use easy_install!
2. Don't use easy_install! Use pip!
3. Use wheel!
4. Wait, no, use pip to install wheels!
5. Create wheels using this standard.
6. No! Create wheels using that standard!

Eela6
May 25, 2007
Shredded Hen

Slimchandi posted:

I always try to avoid doing callable/in-place stuff in a list comprehension eg [print(letter) for letter in name]. Is there any particular reason to avoid this? It seems to be more concise than the indentation of a for-loop.

Yes. You shouldn't use a list comprehension for it's side effects. A list comprehension should be used to create a list. In fact, generally speaking, any function which had side effects shouldn't be used in a comprehension; they're explicitly for a functional style of programming;

What you're talking about is the equivalent of these lines of code:

Python code:
a = []
for letter in name:
    a.append(print(letter))
Indeed,
IN:
Python code:
print([print(letter) for letter in 'word'])
OUT:
pre:
w
o
r
d
[None, None, None, None]

Thermopyle
Jul 1, 2003

...the stupid are cocksure while the intelligent are full of doubt. —Bertrand Russell

Rocko Bonaparte posted:

Is Python packaging really a shitshow of deprecated and inconsistent standards?

Yes. Blame history.



Everything you said is correct, but I just wanted to point out that the poster said "callable", and using a callable is fine in a list comprehension, it's just that the particular example the poster used wasn't great.

(of course, it could be the reverse and the example was on-point and the word "callable" wasn't exactly what he meant)

vikingstrike
Sep 23, 2007

whats happening, captain
Anybody had any issues with PyCharm skipping over breakpoints when debugging? My Google searches have failed me, and it's getting super annoying because I can't figure out how to replicate the issues.

Adbot
ADBOT LOVES YOU

Eela6
May 25, 2007
Shredded Hen

Thermopyle posted:

Yes. Blame history.


Everything you said is correct, but I just wanted to point out that the poster said "callable", and using a callable is fine in a list comprehension, it's just that the particular example the poster used wasn't great.

(of course, it could be the reverse and the example was on-point and the word "callable" wasn't exactly what he meant)

They used 'callable' as a synonym for in-place, so I assumed they meant 'mutable', given the example.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply