Calling a function a second time with only slight changes

3188 views python
5

I have a function called get_next_train:

def get_next_train(params):
    res =requests.get(base, params=params)
    parsed_json = res.json()

    #Zeiten aus parsed_json extrahieren
    time_strings = [d["from"]["prognosis"]["departure"]
        for d in parsed_json["connections"]]

    #String, um Zeiten in time_strings nach ISO 8601 zu parsen
    iso_format = "%Y-%m-%dT%H:%M:%S%z"

    # Time Strings zu datetime Objekten konvertieren
    times = [datetime.strptime(ts, iso_format) 
        for ts in time_strings if ts is not None]

    # Checken, ob times leer sind
    if not times:
    return None # CHANGE: return None if no times found

    #Zeitzone der ersten zeit in Times speichern
    tz = times[0].tzinfo

    #jetztige Zeit mit Zeitzone tz, Mikrosekunden löschen
    nowtime = datetime.now(tz).replace(microsecond=0)


    time = min(t for t in times[0:3] if t > nowtime) # CHANGE: use min
    return time, time - nowtime

I want it to be executed twice: The first time just the way it is now. But the second time, time_strings should be this:

time_strings = [d["from"]["departure"]
    for d in parsed_json["connections"]]

I've tried countless ways of doing it but failed every time. The most common error was:

name 'parsed_json' is not defined.

I could of course just copy paste this whole code and change the bit I need to, but is there any better way of doing this?

answered question

please add the code that calls the function

3 Answers

3

The general principle of factoring is to keep common code the same and turn differences into function parameters.

The difference between the two versions of the function is which subelement is extracted in the list comprehension. We can abstract the difference out into a function:

def get_next_train(params, select_element):
    res = requests.get(base, params=params)
    parsed_json = res.json()

    # extract times from parsed_json
    time_strings = [select_element(d)
        for d in parsed_json["connections"]]

    ...

Now you can call

get_next_train(params, lambda d: d["from"]["prognosis"]["departure"])
get_next_train(params, lambda d: d["from"]["departure"])

Alternatively we could have used

    time_strings = [select_element(d["from"])["departure"]
        for d in parsed_json["connections"]]

in the function and

get_next_train(params, lambda x: x["prognosis"])
get_next_train(params, lambda x: x)

in the call, which removes all redundancy but is a lot harder to understand and to generalize IMHO.

posted this
12

You can try adding a second parameter when you call the function:

def get_next_train(params, flag):
res =requests.get(base, params=params)
parsed_json = res.json()

#Zeiten aus parsed_json extrahieren
if (flag):
    time_strings = [d["from"]["prognosis"]["departure"]
        for d in parsed_json["connections"]]
else:
    time_strings = [d["from"]["departure"]
        for d in parsed_json["connections"]]        

#String, um Zeiten in time_strings nach ISO 8601 zu parsen
iso_format = "%Y-%m-%dT%H:%M:%S%z"

# Time Strings zu datetime Objekten konvertieren
times = [datetime.strptime(ts, iso_format) 
    for ts in time_strings if ts is not None]

# Checken, ob times leer sind
if not times:
return None # CHANGE: return None if no times found

#Zeitzone der ersten zeit in Times speichern
tz = times[0].tzinfo

#jetztige Zeit mit Zeitzone tz, Mikrosekunden löschen
nowtime = datetime.now(tz).replace(microsecond=0)


time = min(t for t in times[0:3] if t > nowtime) # CHANGE: use min
return time, time - nowtime 

Here the 'flag' parameter is just a boolean but it can be whatever you want...

posted this
11

Encapsulate what stays the same. Encapsulate what varies.

You could define two different functions to get time strings. One to get prognosis:

def time_strings_from_prognosis(json):
    return [d["from"]["prognosis"]["departure"]
            for d in json["connections"]]

and another to get other:

def time_strings_other(json):
    return [d["from"]["departure"]
            for d in json["connections"]]

Then pass the function to get time strings to your original function:

def get_next_train(params, get_time_strings):
    ...
    time_strings = get_time_strings(parsed_json)

And call it as:

get_next_train(params, time_strings_from_prognosis)

or:

get_next_train(params, time_strings_other)

posted this

Have an answer?

JD

Please login first before posting an answer.