1
\$\begingroup\$

I want to send multiple QNetworkRequests in Python 3.12 that each downloads one image from a server, using a single callback function. To be able to distinguish between these images I also want to add some type of parameter that tells me what file just finished downloading, so I can display and save the data accordingly.

Version 1:

def downloadFiles(self):
    self.nam = QNetworkAccessManager()
    self.nam.finished.connect(self.downloadDone)
    request = QNetworkRequest(url)
    request.setAttribute(QNetworkRequest.User,"a request 1")
    self.nam.get(request)

def downloadDone(self,reply):
    print(reply.attribute(QNetworkRequest.User))

This prints "None", which is the expected behavior, according to the docs:

The default implementation of Network Access will ignore any request attributes in this range and it will not produce any attributes in this range in replies. The range is reserved for extensions of QNetworkAccessManager.

Version 2:

def downloadFiles(self):
    self.nam = QNetworkAccessManager()
    self.nam.finished.connect(self.downloadDone)
    request = QNetworkRequest(url)
    reply = self.nam.get(request)
    reply.setAttribute(QNetworkRequest.User,"a request 2")

def downloadDone(self,reply):
    print(reply.attribute(QNetworkRequest.User))

This prints "a request 2", as expected.

Version 3:

def downloadFiles(self):
    self.nam = QNetworkAccessManager()
    request = QNetworkRequest(url)
    reply = self.nam.get(request)
    reply.finished.connect(lambda: self.downloadDoneWithText(reply,"a request 3"))

def downloadDoneWithText(self,reply,text):
    print(text)

This prints "a request 3", as expected.

There are also reply.setProperty(...) (seems to work the same way the attribute does, I don't know what the difference is) and reply.url().toString() (gives you the original URL used to open the request but no custom text).

Both version 2 and version 3 (and the other two) work in my tests and they're approximately the same length. But as I'm new to Python, most information and code I've found about this so far are about/written in C++, and there isn't a lot of information about the Python version, I'm wondering:

Is this the way the attribute is meant to be used in Python if you don't subclass QNetworkAccessManager (would be a bit overkill in my case)?

Is there a reason to prefer one version over the other, or should I even use a completely different version instead because of potential problems with version 2 or 3 that won't show themselves until further down the line (memory leaks, performance problems,...)? I don't care if one version is 1ms faster than the other but I don't want to use something that's an obvious no-go for Python-pros for reasons that I don't see (yet).

\$\endgroup\$
7
  • \$\begingroup\$ Surely you aren't actually setting "a request 1"; what are you actually setting? A filename? Something else? \$\endgroup\$
    – Reinderien
    Commented Jul 11 at 11:48
  • \$\begingroup\$ If you do end up setting the attribute (and it isn't clear to me that you should until the question has more down-stream context), don't do #3; do do #2. A replacement to #3 would be to use functools.partial. \$\endgroup\$
    – Reinderien
    Commented Jul 11 at 11:50
  • \$\begingroup\$ Does self.nam.get() block until the download is done? Because that seems counter to the whole purpose of setting a callback. \$\endgroup\$
    – Reinderien
    Commented Jul 11 at 11:52
  • \$\begingroup\$ Also, you're using the wrong documentation; use doc.qt.io/qtforpython-6/PySide6/QtNetwork/QNetworkRequest.html \$\endgroup\$
    – Reinderien
    Commented Jul 11 at 11:53
  • \$\begingroup\$ 1) The actual file names are pretty similar, so I used "a request ..." for testing because it made it easier to see which version (1-3) works and which doesn't. 2a) More infos: Each downloaded image has its specific spot it should be displayed in but I don't know the order in which the downloads will finish, so I need something that tells me which request/reply just triggered the callback, without having to parse the full filename/URL (like with reply.url().toString()) every time. Any specific reason for #2 > #3? \$\endgroup\$
    – Neph
    Commented Jul 11 at 12:30

1 Answer 1

2
\$\begingroup\$

Do not use method 3. Lambdas are somewhat of a wart on the language. If you want to curry parameters, then use instead functools.partial. I consider this method acceptable.

You can use method 2, and I (marginally) prefer it over method 3 because I have a marginal preference for passing state through the API when it offers that. This distinction is not hugely important.

For method 2, it sounds like you care about multiple pieces of state (partial filename and "UI position"). You can bundle these up in a POPO NamedTuple.

A method somewhat related to currying is building a promise object. This would still be a NamedTuple, but in addition to the UI position and the partial filename, it would hold a reference to whatever UI object needs updating. It would also hold the callback method that accepts no other parameters than a bound self. The state-passing parameters of the reply would not be used.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Thanks for the infos! Okay, noted, no lambdas. Is there a specific reason people don't like them, other than looking "complicated"? \$\endgroup\$
    – Neph
    Commented Jul 11 at 13:45
  • \$\begingroup\$ The position depends on the name and is only set once the download of that specific image is done. Let's say I've got images of an apple, a banana and an orange with long, complicated filenames (I know those in advance) but I use "apple",... as the attribute, which I check in the callback function (with if ... elif or match ... case) and only then I create the UI element and set the position accordingly. I only need the reply and the shortened name (within the reply in v2) in the callback function, so there's no need for any tuple, correct? \$\endgroup\$
    – Neph
    Commented Jul 11 at 13:47
  • 1
    \$\begingroup\$ Lambdas are half-baked in Python. They only ever support one line, it's impossible to type-hint them. I know of at least one popular linter that has a specific rule recommending replacement of lambdas with functions. They expose closure scope mandatorily when functions promote healthier scope habits. \$\endgroup\$
    – Reinderien
    Commented Jul 12 at 2:49
  • \$\begingroup\$ As for your apple remark, you would still need an object for a promise, but only the string for currying. \$\endgroup\$
    – Reinderien
    Commented Jul 12 at 2:53
  • \$\begingroup\$ Got it, thanks. I think I'll just use version 2 as-is (probably the easiest). Any idea (or guess) why it's okay to use an attribute with a reply but it's ignored by default with a request? \$\endgroup\$
    – Neph
    Commented Jul 12 at 11:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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