Anfängerfrage - Zählvariable

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
sunshine10
User
Beiträge: 2
Registriert: Donnerstag 14. November 2019, 14:57

Hallo,

ich unterrichte fachfremd Informatik und scheitere an dieser Aufgabe, für die ich leider keine Lösung habe. Ich habe schon gegoogelt und ausprobiert, aber bin nicht zum Ziel gekommen.

Freiwillige Knobel - Challenge:
Schaffen Sie es, die Funktion hallo() so zu erweitern, dass es eine Zählvariable gibt, die bei jedem Aufruf der Funktion um den Wert 1 erhöht wird und die dazu benutzt wird, als neue Ausgabe „Hallo Nutzer 1“, „Hallo Nutzer 2“ etc. zu erzeugen?

Beispiel:
1 # Hier ist die Funktion:
2 def hallo():
3 print("Hallo miteinander!")
4
5 # Hier beginnt das Hauptprogramm:
6 hallo()
7 hallo()
8 hallo()

Datei: L2_5_1_1_funktion_ohne_parameter.py

Viele Grüße
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wer hat denn diese Aufgabe gestellt? Sie zu loesen ist an sich nicht schwer, aber laeuft auf dem aktuellen Lernstand auf die Nutzung einer globalen Variablen hinaus. Und das ist ein Konzept, das man genau NICHT verwenden sollte, und als Anfaenger erst gar nicht ueben. Ich wuerde das also ignorieren. Und in Frage stellen, wie gut das Kursmaterial ist.
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Aufgabe ist eigentlich nicht ohne die Hilfe von Klassen lösbar, da es einen internen Zustand gibt, der zwischen den Aufrufen erhalten bleiben muß:

Code: Alles auswählen

class Hallo:
    def __init__(self):
        self.count = 0
    
    def __call__(self):
        self.count += 1
        print(f"Hallo Nutzer {self.count}!")

# Hier beginnt das Hauptprogramm:
def main():
    hallo = Hallo()
    
    hallo()
    hallo()
    hallo()

if __name__ == '__main__':
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 14051
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Unter Ausnutzung das Defaultwerte nur einmal, beim Ausführen der ``def``-Anweisung erstellt werden:

Code: Alles auswählen

#!/usr/bin/env python3
from itertools import count


def hallo(counter=count(1)):
    print(f"Hallo Nutzer {next(counter)}")


def main():
    hallo()
    hallo()
    hallo()


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Naja. Das ist am Ende auch nur eine globale Variable. Die Diskussion ob so ein Trick denn global waere oder nicht hatten wir damals mit dem guten Alfons M auch diverse male. When it walks and talks like a duck...
Benutzeravatar
__blackjack__
User
Beiträge: 14051
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ja klar ist das eine globale Variable/globaler Zustand und damit keine gute Idee. Würde da auch nichts anderes behaupten. Die Aufgabe verlangt aber danach. Und so wie die Aufgabenstellung den Dateinamen vorgibt, wird das sehr wahrscheinlich automatisiert überprüft, das heisst mit der besseren Lösung von Sirius3 wird man da nicht durchkommen.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Klar. Aber dann wuerde ich auch einfach eine globale Variable machen, und der Drops ist gelutscht. Moeglichst kreativ bei der Verschleierung globalen Zustands zu sein macht das Problem ja nur *noch* aetzender.
Benutzeravatar
ThomasL
User
Beiträge: 1379
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Hier noch eine Möglichkeit. Die Sinnhaftigkeit sei dahin gestellt. Ist eher grenzwertig.

Code: Alles auswählen

# Hier ist die Funktion:
def hallo(counter=[1]):
    print(f"Hallo Nutzer {counter[0]}")
    counter[0] += 1

# Hier beginnt das Hauptprogramm:
hallo()
hallo()
hallo()

Code: Alles auswählen

Hallo Nutzer 1
Hallo Nutzer 2
Hallo Nutzer 3
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
__blackjack__
User
Beiträge: 14051
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Mir ging's da weniger um kreativ globalen Zustand zu verschleiern sondern mehr darum den globalen Zustand wenigstens nicht ”frei” auf Modulebene herum liegen zu haben wo man nicht weiss von wo aus der alles geändert werden könnte. So ist er wenigstens an das Funktionsobjekt gebunden und man kommt von aussen nicht ohne Magie (`__defaults__`) heran, die man in normalen Programmen eher nicht verwendet.

Die klassische funktionale Lösung mit Closure:

Code: Alles auswählen

#!/usr/bin/env python3


def make_counting_greeter():
    counter = 0
    
    def counting_greeter():
        nonlocal counter
        counter += 1
        print(f"Hallo Nutzer {counter}")

    return counting_greeter


hallo = make_counting_greeter()


def main():
    hallo()
    hallo()
    hallo()


if __name__ == "__main__":
    main()[code]
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
__blackjack__
User
Beiträge: 14051
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Einer geht noch… 🙂

Code: Alles auswählen

#!/usr/bin/env python3
from functools import partial
from itertools import count

hallo = partial(
    next, (print(f"Hallo Nutzer {counter}") for counter in count(1))
)


def main():
    hallo()
    hallo()
    hallo()


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ach komm, irgendwie geht doch auch bestimmt was mit selbstmodifizierendem Code.
Benutzeravatar
__blackjack__
User
Beiträge: 14051
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Hm, würde da das Verändern des Default-Wertes schon darunter fallen‽ 🤔

Code: Alles auswählen

#!/usr/bin/env python3


def hallo(counter=1):
    hallo.__defaults__ = (hallo.__defaults__[0] + 1,)
    print(f"Hallo Nutzer {counter}")


def main():
    hallo()
    hallo()
    hallo()


if __name__ == "__main__":
    main()
Oder muss ich dafür tatsächlich an den Bytecode ran? 😄
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Code zu patchen ist ganz schön kompliziert:

Code: Alles auswählen

from types import CodeType

def patch(func, old_value, new_value):
    fn_code = func.__code__
    consts = fn_code.co_consts
    consts = tuple(new_value if c is old_value else c for c in consts)
    func.__code__ = CodeType(fn_code.co_argcount,
        fn_code.co_kwonlyargcount, fn_code.co_nlocals,
        fn_code.co_stacksize, fn_code.co_flags, fn_code.co_code,
        consts, fn_code.co_names, fn_code.co_varnames,
        fn_code.co_filename, fn_code.co_name, fn_code.co_firstlineno,
        fn_code.co_lnotab, fn_code.co_freevars, fn_code.co_cellvars,
    )

def hallo():
    text = "Hallo Nutzer 1" 
    print(text)
    first, second = text.rsplit(' ',1)
    patch(hallo, text, f"{first} {int(second)+1}")
Ich werfe noch Decoratoren in den Ringe:

Code: Alles auswählen

def count_decorator(func):
    func.counter = 0
    def wrapper():
        func.counter += 1
        return func(func.counter)
    return wrapper

@count_decorator
def hallo(counter):
    print(f"Hallo Nutzer {counter}")
nezzcarth
User
Beiträge: 1764
Registriert: Samstag 16. April 2011, 12:47

Stark inspiriert durch einen 15 Jahre alten Thread hier aus dem Forum könnte man auch so etwas versuchen:

Code: Alles auswählen

In [1]: def hallo(): 
   ...:     result = getattr(hallo, 'i', -1) 
   ...:     setattr(hallo, 'i', result+1) 
   ...:     print("Hallo Nutzer", hallo.i) 
   ...:                                                                       

In [2]: hallo()                                                               
Hallo Nutzer 0

In [3]: hallo()                                                               
Hallo Nutzer 1

In [4]: hallo()                                                               
Hallo Nutzer 2
Ähnelt etwas __blackjacks__ erstem Vorschlag, jedoch mit dem angesprochenen Nachteil, dass man noch leichter von außen dran kommt.
Benutzeravatar
snafu
User
Beiträge: 6867
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich würde da auf itertools.count() zurückgreifen und stumpf eine globale Variable anlegen:

Code: Alles auswählen

from itertools import count

_user_counter = count(1)

def hallo():
    print(f"Hallo Nutzer {next(_user_counter)}")
Der Zugriffsschutz vor möglichen Änderungen von außen spielt für mich keine Rolle, oder sind wir hier bei Java...? :o
Benutzeravatar
__blackjack__
User
Beiträge: 14051
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@snafu: Das ist IMHO keine Frage des Zugriffsschutzes sondern das man wenn es auf Modulebene ist, das ganze Programm im Blick haben muss um am Ende zu sehen, dass es nur von dieser einen Funktion verwendet wird. Bei meiner ersten Lösung, so wie ich das wohl auch tatsächlich lösen würde, kommt man ja auch über `__defaults__` an das `count`-Objekt und kann es auch austauschen wenn man will/muss.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Sirius3
User
Beiträge: 18272
Registriert: Sonntag 21. Oktober 2012, 17:20

Das Default-Argument hat den zusätzlichen Vorteil, dass man zwar den Default-Zähler verwenden kann, sich aber auch eigene Counter anlegen kann und diese benutzen.
sunshine10
User
Beiträge: 2
Registriert: Donnerstag 14. November 2019, 14:57

Hallo nochmal,

danke für die vielen Antworten! Dann werde ich die freiwilige Knobel-Challenge für die Schüler rausnehmen, da es wohl zum jetztigen Zeitpunkt doch zu weit führen würde.

viele Grüße
Antworten