1
def download():
    upgrade = True
    if upgrade:
        # do a download using tftp
    else:
        # do a download via HTTP

As you can see, I have a hard coded value of upgrade that is set to true. In this script, it always does a tftp download.

How do I change the script to do tftp download at the first iteration and in the next iteration when the function download is called, it does a http download?

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 3
    first... call in a single instance? Or do you want to actually persist state to storage such that the change spans multiple runs of the program? If it's the latter, you'll need to implement code to read and write from (disk/database/whatever). – Charles Duffy Feb 07 '18 at 21:13

7 Answers7

6

For completeness here is the class solution:

class Download(object):
    def __init__(self):
        self.executed = False

    def __call__(self):
        print('http' if self.executed else 'tftp')
        self.executed = True

download = Download()

download()  # tftp
download()  # http
download()  # http

This allows you to store state in a non-hackish way across invocations.

Alex
  • 18,484
  • 8
  • 60
  • 80
2

You can use a closure, that is, have an inner function which is returned, and this retains an outer state. This assumes python 3:

def init_download():
    upgrade = True

    def inner():
        nonlocal upgrade
        if upgrade:
            print('do a download using tftp')
            upgrade = False
        else:
            print('do a download via HTTP')

    return inner

download = init_download()

download()
download()
download()

Gives:

do a download using tftp
do a download via HTTP
do a download via HTTP
cdarke
  • 42,728
  • 8
  • 80
  • 84
  • 1
    OP is asking something that can be easily resolved with a default argument and you want to introduce him to closures? – Chrispresso Feb 07 '18 at 21:24
  • 3
    @ZWiki, eh? The "default argument" is just pushing the work to the outer scope; it doesn't solve the problem, which is of maintaining state across invocations, unless you make it a mutable one -- which is much more of a hack than this is. – Charles Duffy Feb 07 '18 at 21:24
  • 2
    I don't consider this to be a particularly difficult closure as they go, I think its quite a simple introduction to them. – cdarke Feb 07 '18 at 21:25
  • Oh I misunderstood the question then. I suppose a closure is a good way to do so since we don't have C static equivalent – Chrispresso Feb 07 '18 at 21:26
  • 3
    –1 Gross way to store state, because you can't access the state. Better to use a callable class if you want that. – wim Feb 07 '18 at 21:28
  • @ZWiki: yes, I always use this when what I really want is a C style `static` (or Perl `state`). You can't access the state from the outside - that's good (encapsulation), but you can access it from the inner function. – cdarke Feb 07 '18 at 21:30
  • You could just have an `initialized` variable in your main function/script and use that... then you can tell whether it has been initialized. Wrapping that in a closure seems like overkill. – TemporalWolf Feb 07 '18 at 21:55
  • @TemporalWolf: you could, but that means the function is not encapsulated. – cdarke Feb 08 '18 at 07:30
1

Restructure your code as below:

def download(upgrade=True):

  if upgrade:
     do a download using tftp
  else:
     do a download via HTTP

In your second iteration, when you call download, use upgrade=False as a parameter:

download(False)  # download via HTTP
Chrispresso
  • 3,660
  • 2
  • 19
  • 31
jpp
  • 159,742
  • 34
  • 281
  • 339
  • OP said 'iteration,' meaning the function is called multiple times but he doesn't know from the outset which number of call it is. – data princess Feb 07 '18 at 21:18
  • 2
    This *doesn't actually solve the problem* of changing the `upgrade` switch between the first and subsequent invocations; it just is an instruction to have the caller do that work between the invocations of the function. Is that the right thing to do as a best practice? Yes, absolutely. Does it answer the question? ...ehhhh... – Charles Duffy Feb 07 '18 at 21:26
  • Rereading the question, I would tend to agree. But I would like OP to confirm, still a bit vague to me. – jpp Feb 07 '18 at 21:28
  • 3
    This is the only answer that is sort of getting something towards the right approach. But I would go a bit further, and just write two different functions like `download_tftp` and `download_http`, and keep the logic about which to use outside of the function itself. – wim Feb 07 '18 at 21:33
  • @wim, what about `functools.partial` to split apart the 2 configurations? – jpp Feb 07 '18 at 21:34
1

Probably you can (and should) just take this logic outside of your function, but if you want to pass the same argument each time but still change the behaviour after the first call, you could use the default mutable argument:

from itertools import count 

def download(c=count()):
    if next(c) == 0:
        print('tftp')
    else:
        print('http')

download()
# tftp
download()
# http
download()
# http
download()
# http

The advantage of using itertools.count rather than say a list is that you don't accumulate memory with each call.

Chris_Rands
  • 38,994
  • 14
  • 83
  • 119
  • 1
    –1 just another bad way to store global state – wim Feb 07 '18 at 21:30
  • 1
    @Alex thanks and sorry your correction was right, I misread the question, thought they wanted a switch to go back and forth – Chris_Rands Feb 07 '18 at 21:35
  • @wim can you spell out to me why it's so bad? I agree it's not best practise but how often do you actually find a possible use case for the default mutable argument, could not resist! – Chris_Rands Feb 07 '18 at 21:36
  • Sure. It's because when you need stateful execution, the right tool for the job is not a function. – wim Feb 07 '18 at 21:39
  • @wim I agree with you generally but technically all non-trivial closures involve stateful execution. – Alex Feb 07 '18 at 21:41
  • 2
    I think @wim think this is an X-Y question, and he/she is probably right, but the problem becomes rather trivial if you take the logic outside the function. I'll add a note with that sentiment :) – Chris_Rands Feb 07 '18 at 21:55
  • 1
    @Chris_Rands Right. And when your problem becomes trivial it's **a good thing**. – wim Feb 07 '18 at 21:58
  • @wim of course! it can be can be instructive to think about these questions still IMO – Chris_Rands Feb 07 '18 at 22:06
0

If I understand you correctly in you only need the literal first call to do thtp, you could make upgrade a global variable. Then, when the download function is called, you globally set it to False, and it will remain that way for subsequent calls.

Note that this answer is based on a certain reading of OP's question. I am assuming that the script calling this function does not know how many times the function has been called.

upgrade = True

def download():
  global upgrade
  if upgrade:
     print('do a download using tftp')
     upgrade = False
  else:
     print('do a download using http')

download() # do a download using tftp
download() # do a download using http
data princess
  • 1,130
  • 1
  • 23
  • 42
  • 4
    Why the downvote? This does what the OP literally asked for. It's not good practice, but that's on the OP. – Charles Duffy Feb 07 '18 at 21:16
  • 1
    (That said, personally, I'd consider making this a class that implements `__call__()` rather than a function -- that way its state can live internally, as at `self.upgrade`, rather than as a separate global). – Charles Duffy Feb 07 '18 at 21:18
  • 5
    You can (and should) downvote if you think something is not good practice, regardless of what the OP asked about. – wim Feb 07 '18 at 21:18
  • @wim disagreed so long as you point out that it's not good practice. I learn a lot from reading hacky solutions to things and thinking about how they work. – Alex Feb 07 '18 at 21:19
  • 1
    btw it won't even work, this will be `SyntaxError`, isn't that a good enough reason? The global statement needs to go before the name is first used. – wim Feb 07 '18 at 21:19
  • True; the `global` declaration should move up to the top of the function body. – Charles Duffy Feb 07 '18 at 21:20
  • 4
    So a code that doesn't work and has a `SyntaxError` gets upvoted 4 times? How? – wim Feb 07 '18 at 21:23
  • 1
    It does work without error (corrected the global placement) and most closely addresses what OP was asking, in my opinion. – data princess Feb 07 '18 at 21:25
  • 3
    @wim If someone makes a simple syntax error, post a comment so they can fix it. It's not worth a downvote. – Barmar Feb 07 '18 at 21:33
  • 4
    @Barmar Don't tell other users how to use their downvotes. Btw I would -1 this idea even if it were correctly written in the first place. I would -2 it if I could. – wim Feb 07 '18 at 21:43
  • @wim Assume "IMHO" at the beginning of my comment. – Barmar Feb 07 '18 at 21:44
-1

Since @Alex 's answer is good, but it need a extra step to construct an instance. You will also to store the instance globally.

So I made some change to it. Then you can import it and use it as function.

util.py

class Downloader(object):
  _upgrade = False

  @classmethod
  def call(cls):
    if cls._upgrade:
      print('upgrade via http')
    else:
      print('download via tft')
      cls._upgrade = True

def download():
  Downloader.call()

main.py (using it)

from .util import download

download()
download()

This may not thread safe. And if you run it in different processes (e.g. webserver backend), you'd better use a remote storage for the flag (e.g. redis/oss).

Samuel Chen
  • 2,124
  • 14
  • 9
-2

You could structure your code in the following:

def download(upgrade = False):

    while upgrade == False:
            print("HTTP")
            break
    print("TFTP")

download()

This will go through them once and exit the program after. The print statements are just a guide to show you what's happening and when it ends.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 1
    The intent is to only do one operation per call. Maybe you could make a generator out of this, but that would require changing the calling convention. – Charles Duffy Feb 07 '18 at 21:43
  • Read the above updated code. It does one operation per call then exits. – White Wolf Feb 08 '18 at 06:50
  • No, it doesn't. If you call it with `upgrade=False`, then it prints `HTTP` inside the `while` loop, breaks out of the loop, and then prints `TFTP` from the same call. – Charles Duffy Feb 08 '18 at 16:43