简体   繁体   中英

Getting a Generator Object returned when I want the data

I am very very new to Python 3, so please be nice.

I have looked at all the documentation I could find regarding this - there does seem to be a lot, it's just I still cannot figure out what to do.

This is my code:

poly = [[36606.0,53223.0],[37332.0,52224.0],[39043.0,53223.0],
[41603.0,53223.0],[42657.0,53278.0],[43123.0,52060.0],
[44054.0,51156.0],[45806.0,51498.0],[46751.0,53237.0],
[46983.0,54606.0],[45861.0,57057.0],[44971.0,58836.0],
[44054.0,60616.0],[43451.0,58138.0],[41850.0,59179.0],
[40850.0,60370.0],[39906.0,59233.0],[38674.0,59179.0],
[35566.0,56249.0],[37592.0,57536.0]]

def perimeter():
     return (sum((abs((x1 - x0)**2)+abs((y1 - y0)**2))**.5)
                        for ((x0, y0), (x1, y1)) in parts1(poly))

def parts1(poly):
    return poly[0:] + [poly[0]]

print(perimeter())

It is running without any errors but I am getting a return of:

generator object perimeter.locals.genexpr at 0x7f47d8671c50

How do I make it so that it gives me the value/answer? I am really not sure what is going on.

Wrapping an item for item in iterable construct in parentheses makes it a lazy generator expression, which has the appearance you see. Now, the reason it does this instead of giving you an error that you would get from trying to send a non-iterable to sum() is because it doesn't evaluate anything. If you sent this generator expression to list() to evaluate it, you would get an error.

You need to move some parentheses around:

sum((abs((x1 - x0)**2)+abs((y1 - y0)**2))**.5
                        for ((x0, y0), (x1, y1)) in parts1(poly))

Now you have sum(expression for element in parts1(poly)) rather than (sum(expression) for element in parts1(poly)) .

Small test:

>>> x1, x0, y1, y0 = 3, 1, 5, 4
>>> sum((abs((x1 - x0)**2)+abs((y1 - y0)**2))**.5
...     for ((x0, y0), (x1, y1)) in [((x0,y0), (x1, y1))])
2.23606797749979

The first problem is that you have your parentheses in the wrong places. You want to call sum() on a generator expression, but instead you have written a generator expression using sum() . So you might try this:

def perimeter():
    return sum(((x1 - x0) ** 2) + ((y1 - y0) ** 2) ** .5
                 for (x0, y0), (x1, y1) in parts1(poly))

(I have also taken out your abs() calls, since squaring a number will make it positive, making abs() irrelevant, and deleted some unnecessary parentheses.)

But this still doesn't work: now you get 'float' object is not iterable .

This is because you are trying to unpack four values from each element of the list, but each element contains only two. Python unpacks each element to two floats, then tries to unpack each float to two variables. This is where the error message rears its ugly head.

So you need to change parts1() to return a list of a list of lists of lists. That is, each item in the list is a list, which contains two lists, each containing the coordinates of a point (a given point and its successor). One way to do this is to use the built-in zip() function with an offset or rotated copy of the list.

def parts1(poly):
    return zip(poly, poly[1:] + poly[:1])

Finally, you don't really need the separate function parts1() —it can go right in the perimeter() function. And you should pass poly into perimeter() .

def perimeter(poly):
    return sum(((x1 - x0) ** 2) + ((y1 - y0) ** 2) ** .5
                 for (x0, y0), (x1, y1) in zip(poly, poly[1:] + poly[:1]))

You could do this without the extra copy of the list by iterating over the coordinates in poly and keeping track of the last items you saw. But you couldn't write it as a generator expression. Instead you'd use a regular for loop.

def perimeter(poly):
    x0, y0 = poly[-1][0], poly[-1][1]
    per = 0.0
    for x1, y1 in poly:
        per += ((x1 - x0) ** 2) + ((y1 - y0) ** 2) ** .5
        x0, y0 = x1, y1
    return per 

Not quite as succinct, and not as fast either, but doesn't use as much memory.

I would try something a bit more explicit like this, this matches your expected answer

import itertools

def calculate_length(x0, x1, y0, y1):
    return ((x1 - x0)**2+(y1 - y0)**2)**.5

def make_point_pairs(poly):
    pairs = zip(poly, poly[1:])
    # Close the shape
    chain = itertools.chain(pairs, [[poly[-1],poly[0]]])
    return chain

def perimeter(poly):
     return sum([calculate_length(x0, x1, y0, y1) for ((x0, y0), (x1, y1)) in make_point_pairs(poly)])

Your code has two problems. One is due to parentheses as @TigerhawkT3 pointed out, and the other is you're not iterating through the points properly. The code below addresses both these issues.

poly = [[36606.0,53223.0],[37332.0,52224.0],[39043.0,53223.0],
        [41603.0,53223.0],[42657.0,53278.0],[43123.0,52060.0],
        [44054.0,51156.0],[45806.0,51498.0],[46751.0,53237.0],
        [46983.0,54606.0],[45861.0,57057.0],[44971.0,58836.0],
        [44054.0,60616.0],[43451.0,58138.0],[41850.0,59179.0],
        [40850.0,60370.0],[39906.0,59233.0],[38674.0,59179.0],
        [35566.0,56249.0],[37592.0,57536.0]]

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = iter(iterable), iter(iterable)
    next(b, None)
    return zip(a, b)

def parts1(poly):
    return poly[0:] + [poly[0]]

def perimeter():
    return sum((abs((x1 - x0)**2)+abs((y1 - y0)**2))**.5
                    for ((x0, y0), (x1, y1)) in pairwise(parts1(poly)))

print(perimeter())  # -> 41095.327046386046

Also note that you could simplify (and speed up) the calculation you're doing by using the built-in math.hypot() function:

import math

def perimeter2():
    return sum(math.hypot(x1-x0, y1-y0) 
                for ((x0, y0), (x1, y1)) in pairwise(parts1(poly)))

print(perimeter2())  # -> 41095.327046386046

I am also a little confused about your question, it seems that you are not intentionally using the generator and just wants to get the perimeter? I think it would be better to clean up the function perimeter() a little bit and not use a generator there, and simply iterate through the list poly and taking every adjacent pair and calculate it this way.

poly = [[36606.0,53223.0],[37332.0,52224.0],[39043.0,53223.0],
[41603.0,53223.0],[42657.0,53278.0],[43123.0,52060.0],
[44054.0,51156.0],[45806.0,51498.0],[46751.0,53237.0],
[46983.0,54606.0],[45861.0,57057.0],[44971.0,58836.0],
[44054.0,60616.0],[43451.0,58138.0],[41850.0,59179.0],
[40850.0,60370.0],[39906.0,59233.0],[38674.0,59179.0],
[35566.0,56249.0],[37592.0,57536.0]]

def perimeter():
    #return (sum((abs((x1 - x0)**2)+abs((y1 - y0)**2))**.5) \
                        #for ((x0, y0), (x1, y1)) in parts1(poly))
    peri=0
    for [x0, y0], [x1, y1] in get_pair(poly):
        peri+=((x1-x0)**2 + (y1-y0)**2)**0.5
    return peri

def get_pair(poly):
    i=0
    while i<len(poly):
        yield [poly [i],poly [(i+1)%len(poly)]] #The modulo takes the pair of last and first coordinates into account
        #So that when poly [i] is the last coordinate it is returned with the first coordinate
        i+=1

print(perimeter())

I used a generator function for get_pair() function, but you can also do it with a loop or some other way. It basically just returns a new pair in the list every time you call it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM