-1
\$\begingroup\$

I want to create a list of the different response headers I get when querying an URL, I succeeded in doing this this way:

import requests
l= []
for _ in(5):
    a = requests.get("https://google.com", allow_redirects=True).headers['Origin']

    if a not in l:
        l.append(a)

This is readable but I feel like this is very basic, how would an advanced Pythonista handle this?

\$\endgroup\$
9
  • 2
    \$\begingroup\$ I am not convinced that this code is on-topic. It seems like a snippet that would never be run in isolation. CodeReview does not support the reviewing of snippets. Is this all of the code in your program, or is there more? Why are you gathering origins? Will it actually be from Google, or is that just an example? (Hypotheticals are also off-topic.) \$\endgroup\$
    – Reinderien
    Commented Apr 29, 2020 at 15:20
  • 3
    \$\begingroup\$ That is a very good question. In theory, all of the code reviewers should have known that this is off-topic and should not have posted answers; this would have been voted-to-close, you would have improved it, and it would have then re-opened. In practice I'm going to ask on meta. \$\endgroup\$
    – Reinderien
    Commented Apr 29, 2020 at 16:11
  • 4
    \$\begingroup\$ @KateVelasquez In the meantime I think it is safe for you to post a new question with more code and context, after having read codereview.meta.stackexchange.com/questions/2436/… . I do not think it would be considered a duplicate. Thank you for your patience. \$\endgroup\$
    – Reinderien
    Commented Apr 29, 2020 at 16:26
  • 2
    \$\begingroup\$ I'm sorry that your first experience is so poor Kate. Since this has now been closed please feel free to edit the post to become on-topic, as Reinderien has suggested. Additionally the more code you proved the better we can help you improve your code. However in the future please do not edit the code in the question to address answers, this is because it causes icky situations like this where you're stuck with for _ in(5). When you edit the question so that it includes a complete script please feel free to ping me to help kick start a potential reopen. \$\endgroup\$
    – Peilonrayz
    Commented Apr 29, 2020 at 19:00
  • 1
    \$\begingroup\$ @Peilonrayz Considering more and more people are answering off-topic questions (which they shouldn't) and the mess it has created on this one, I'm in favour of putting the revised code in a new question this time. \$\endgroup\$
    – Mast
    Commented May 4, 2020 at 12:46

2 Answers 2

2
\$\begingroup\$

As mentioned in Thomas Weller's post, your for loop only executes a single time. You want to loop over a range(5).


You may wish to use a set to collect your unique results. Test for containment using a not in l is an \$O(N)\$ operation when l is a list, but is \$O(1)\$ when l is a set, so can be significantly faster.

Moreover, since a set naturally cannot contain duplicate entries, the test for containment before appending the item can be entirely removed and replace with a simple .add(a) call.


The PEP-8 -- Style Guide for Python Code recommends a single space on both sides of the = assignment operator. So l= [] should be written as l = [].


The variable names l and a are not at all descriptive. You need to heavily comment the code to explain what these variables are, and what they are for, which you have not done.

Using descriptive names significantly reduces the need to comment the code, as great naming will make the code self documenting. l could be list_of_origin_headers and a could be an_origin_header. While those names are very self documenting, they may be too long; brevity is also important. origin_headers and origin might be sufficient.


First reworking, using above points:

import requests

origin_headers = set()

for _ in range(5):
    origin = requests.get("https://google.com", allow_redirects=True).headers['Origin']
    origin_headers.add(origin)

# Convert set back into a list, to match the original design type
origin_headers = list(origin_headers)

The only tricky point in this code is origin_headers starts off as a set, but becomes a list at the end of the code, so a comment was necessary.


The code does something interesting enough to warrant its own function, which improves organization, and provides additional opportunity for documentation.

import requests

def fetch_origin_headers(url: str, repeat: int) -> list:
    """
    Fetch the given URL several times, and return a list containing the unique
    'Origin' headers.
    """

    if repeat < 1:
        raise ValueError("Invalid repeat.  Must be at least 1")

    origin_headers = set()

    for _ in range(repeat):
        origin = requests.get(url, allow_redirects=True).headers['Origin']
        origin_headers.add(origin)

    # Return a list instead of the temporary set
    return list(origin_headers)

if __name__ == '__main__':

    google_origins = fetch_origin_headers("https://google.com", 5)

In addition to the new function, I've added some basic type-hints and the beginnings of a """docstring""" to the function. I've also added a main-guard, so this "test code"(?) fetching the results of https://google.com is not always executed.


Now that we've raise the bar a little, your Pythonista would look at the inner loop and think "set comprehension":

def fetch_origin_headers(url: str, repeat: int) -> list:
    """
    Fetch the given URL several times, and return a list containing the unique
    'Origin' headers.
    """

    if repeat < 1:
        raise ValueError("Invalid repeat.  Must be at least 1")

    # Accumulate results in a set, to eliminate duplicates
    origin_headers = { requests.get(url, allow_redirects=True).headers['Origin']
                       for _ in range(repeat) }

    # Return a list instead of the temporary set
    return list(origin_headers)
\$\endgroup\$
2
  • \$\begingroup\$ Beat me to it! Funnily, our responses are very similar. Your explanations are way better however. Do you have a pointer/links as to how set containment is \$ O(1) \$? I did not know this. \$\endgroup\$
    – Alex Povel
    Commented Apr 29, 2020 at 15:55
  • 1
    \$\begingroup\$ @AlexPovel wiki.python.org/moin/TimeComplexity#set \$\endgroup\$
    – AJNeufeld
    Commented Apr 29, 2020 at 17:43
2
\$\begingroup\$

Your code does not run as it is shown there.

The problem, as mentioned in the comments, is that int 5 is not iterable. range(5) is, which would perform the action five times.

Looking for

if a not in l:
    l.append(a)

in each loop requires looking through that list each time.

Using sets instead, this operation is still required somewhere under the hood, but likely faster (implemented on a lower level). We also no longer have to care for it. In the case here, a set can be used like the list, with the added benefit that sets can only hold unique entries. Since you care for the different values, and filter duplicates out manually with the code above, this is a step in the right direction.

Generally, comprehensions are faster than manual looping and appending in Python. There are also set comprehensions.

One last issue I had was a KeyError for "Origin". However, "Date" was available in the response, so I used that for demonstration purposes:

def get_google():
    return requests.get("https://google.com", allow_redirects=True)

dates = {get_google().headers["Date"] for _ in range(5)}

print(dates)

My machine took more than one second for the five tries, so the output was:

{'Wed, 29 Apr 2020 15:43:12 GMT', 'Wed, 29 Apr 2020 15:43:13 GMT'}

Two unique/different date entries (str) from five requests.

If you want to build onto that, you can for example check if the response was ok (200) with the ok attribute of the response.get() object.

\$\endgroup\$
1
  • \$\begingroup\$ It is not int 5, it's in(5) and was in (1,5) before edits. Probably a tupel. When editing, he broke the code. Maybe wanted it to be in (1,) or something \$\endgroup\$ Commented Apr 29, 2020 at 16:52

Not the answer you're looking for? Browse other questions tagged or ask your own question.