Übernahme der Eingabefelder

Fragen zu Tkinter.
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Ich verstehe nicht so recht warum man dafür Zugriffsschutz brauchen soll. Zur Datenkapselung braucht man das nicht. Dazu reicht es wenn man einfach nicht auf Sachen zugreift, die zu Interna gehören. Die kann man einfach mit einem Unterstrich als solche kenntlich machen. Und in vielen einfachen Einstiegsbeispielen gibt es dann in Sprachen wie Java triviale Getter und Setter, womit die Attribute de fakto doch wieder öffentlich sind. Das ist dann keine Kapselung beziehungsweise ist das nichts anderes als in Python direkt auf Attribute zuzugreifen. Die Argumentation bei Java ist dann, dass man die Interna beim Zugriff jederzeit ändern kann, was beim direkten Zugriff nicht geht. Das Argument zieht aber bei Python nicht, weil Python berechnete Attribute kennt, man da also nicht auf ”Vorrat” triviale Getter/Setter schreiben muss, nur für den Fall, dass man da vielleicht irgendwann einmal etwas anders machen will.

Ich sehe das auch nicht als *den* Vorteil/Grund von Klassen, denn das kann man beispielsweise in C auch durch Strukturen erreichen wo man dem Benutzer der Bibliothek grundsätzlich einfach nur einen void-Zeiger auf eine opake Datenstruktur in die Hand gibt, und dadurch auch in C leicht erzwingen kann, dass die Daten nur über die dafür passenden Funktionen manipuliert werden können.

Selbst wenn man in C auch die ``struct``-Deklaration öffentlich macht, aber Funktionen zur Manipulation zur Verfügung stellt, ist das immer noch Datenkapselung wenn die Dokumentation deutlich macht, dass man nicht direkt in diesen Strukturen herumpfuschen soll.

Datenkapselung habe ich das erste mal mit Pascal beigebracht bekommen, und zwar mit klassischem Pascal mit RECORD und Units, ohne OBJECT oder CLASS,. ”OOP” habe ich auch das erste mal mit Pascal gezeigt bekommen — und nicht verstanden warum das jetzt besser sein sollte, weil mir in dem Zusammenhang nichts gezeigt wurde, was das wirklich besser gemacht hat, als die prozedurale Lösung, die wir davor für das gestellte Problem entwickelt hatten. Also keine Polymorphie oder Vererbung. Es war halt einfach nur der selbe Code mit dem kleinen syntaktischen Unterschied, dass das erste Argument aus der Parameterliste verschwunden ist, und beim Aufruf vor den Prozedur- oder Funktionsnamen gerutscht ist, und innerhalb der Prozedur/Funktion den festen Namen `Self` und quasi so etwas wie ein ``WITH Self DO`` für den Körper der Prozedur/Funktion gilt.

Wirklich einen einfachen Vorteil erhält man IMHO erst durch Polymorphie, weil dadurch Klassen/Implementierungen von Objekten austauschbar werden, solange sie die Schnittstelle erfüllen.

In Python ist da wahrscheinlich `__str__()` eines der einfachsten Beispiele, weil selbst bei POD-Klassen oft eine eigene Zeichenkettendarstellung Sinn macht, und `print()` eine sehr einfache und zu dem Zeitpunkt sicher bekannte Funktion ist, die das dann nutzen kann, dass man eine für den Benutzer sinnvolle Darstellung als Zeichenkette ausgeben kann, in dem man ganz einfach das Objekt an `print()` übergibt.

Das mit der Argumentanzahl steht so hart nicht in „Clean Code“. Da steht das einmal hinten im Zusammenfassungskapitel „Smells und Heuristiken“, was ja schon mal keine harten Regeln sind. Ob man dann aus drei Argumenten *ein* Objekt machen sollte, kommt auch *sehr* darauf an ob das überhaupt Sinn macht die zusammenzufassen. Im Kapitel über Funktionen ist da ein gutes Beispiel mit einer `makeCircle()`-Funktion die `x`, `y`, und `radius` als Argumente bekommt und wo sich `x` und `y` als `Point` zusammenfassen lassen. Es würde eher keinen Sinn machen da auch noch `radius` mit rein zu nehmen. Denn dann würde man `makeCircle()` wahrscheinlich schon genau die Datenstruktur übergeben, die man mit dieser Funktion eigentlich erstellen möchte.

Ich persönlich werde ”erst” bei mehr als 5 bis 6 Argumenten ”hellhörig”. Die `makeCircle()`-Argumente hätte ich nicht wegen der Anzahl der Argumente zusammengefasst, sondern weil in so einem Programm ziemlich sicher an vielen Stellen auf Punkten operiert wird, und es deshalb Sinn macht die Koordinaten zu einem Objekt zusammenzufassen.

Die Argumentanzahl generell so drastisch zu reduzieren wie das von Martin vorgeschlagen wird, verschiebt das Problem IMHO auch nur zu immer mehr und immer verschachtelteren Argument-Objekten.

Mit Büchern zu statisch typisierten Sprachen wäre ich bei Python (und anderen dynamischen Sprachen) vorsichtig. Und wenn einem ein Buch etwas über objektorientierte Programmierung beibringen will und dafür Java (oder C++) verwendet, wäre ich auch vorsichtig. Man muss da immer aufpassen was tatsächlich allgemein für OOP gilt, und was nur die eingeschränkte(re) Sicht der jeweiligen Sprache auf das Thema ist.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@_deets_ @rogerb @_blackjack_

Hui, :) mit so vielen interessanten Reaktionen hatte ich jetzt gar nicht gerechnet. Ich wollte allerdings gar nicht den ursprünglichen Thread "sprengen".

Eigentlich habe ich nur schriftlich überlegt, wie man den Sinn und Zweck von Klassen erklärt - und zwar ohne das Thema "Software-Architektur" zu beleuchten.
Die Schnittstellen-Thematik ist imho der am einfachsten zu verstehende Aspekt von Klassen.
Das was sie architektonisch ermöglichen (z.B. Polymorphie), ist leider wie @_blackjack_ schon anmerkte
mit den meisten (Trivial-)Beispielen nicht wirklich gut vermittelbar. (Thorngates Postulat der angemessenen Komplexität)

@_deets_
Das halte ich fur fast schon groben Unfug. Es ist überhaupt nichts gewonnen, 3 Argumente durch eine Klasse mit 3 Attributen zu ersetzen. Außer es für die Programmiererin komplizierter zu machen.
Natürlich muss man das situationsbezogen anwenden. Wenn es z. B. semantisch nicht sinnvoll ist, dann muss man davon abweichen.
Im Sinne von "Shu Ha Ri" ist es aber hin und wieder hilfreich, als Anfänger Regeln/Konventionen erst mal zu verstehen, bevor man sie bricht.

@_blackjack_
Dazu reicht es wenn man einfach nicht auf Sachen zugreift, die zu Interna gehören. Die kann man einfach mit einem Unterstrich als solche kenntlich machen.
Ich sehe den Zugriffsschutz allerdings auch z.B. als Kommunikationsmöglichkeit zwischen z.B. Framework- und Produkt-Entwickler.
Offensichtlich kann man das aber auch per Konvention sicherstellen. Leider habe ich diesbezüglich noch keine persönlichen Erfahrungen machen können. Aber nun, das kann ja noch werden, denke ich.
Und in vielen einfachen Einstiegsbeispielen gibt es dann in Sprachen wie Java triviale Getter und Setter, womit die Attribute de fakto doch wieder öffentlich sind. Das ist dann keine Kapselung beziehungsweise ist das nichts anderes als in Python direkt auf Attribute zuzugreifen. Die Argumentation bei Java ist dann, dass man die Interna beim Zugriff jederzeit ändern kann, was beim direkten Zugriff nicht geht.
Da gebe ich Dir vollkommen Recht. Triviale Getter/Setter machen aus dem bereits oben genannten Grunde keine Sinn.
Was ich aber durchaus sinnvoll finde, ist z. B. nur eine lesenden Beschränkung (sprich "nur" Getter) zu ermöglichen (je nach Semantik natürlich)
Das muss man aber als bewußte Design-Entscheidung treffen. Eine "Voratshaltung" von Code ist selbstredend fast nie sinnvoll.
Datenkapselung habe ich das erste mal mit Pascal beigebracht bekommen, und zwar mit klassischem Pascal mit RECORD und Units, ohne OBJECT oder CLASS,.
”OOP” habe ich auch das erste mal mit Pascal gezeigt bekommen — und nicht verstanden warum das jetzt besser sein sollte, weil mir in dem Zusammenhang nichts gezeigt wurde, was das wirklich besser gemacht hat, als die prozedurale Lösung, die wir davor für das gestellte Problem entwickelt hatten.
Ich kenne jetzt natürlich nicht den konkreten Fall. Aber es ist natürlich durchaus vorstellbar, dass in einigen Fällen der Unterschied im Design mit RECORD oder CLASS gering ist.
RECORDS ziehe ich dann eine CLASS vor, wenn ich absehbar keine Polymorphie brauche und wenn sich dadurch z.B. Performenceverbesserungen ergeben.
RECORDS sind auch etwas einfacher in der Handhabung im Hinblick auf Speichermanagment (zumindest in Delphi)
In Python ist da wahrscheinlich `__str__()` eines der einfachsten Beispiele, weil selbst bei POD-Klassen oft eine eigene Zeichenkettendarstellung Sinn macht, und `print()` eine sehr einfache und zu dem Zeitpunkt sicher bekannte Funktion ist, die das dann nutzen kann, dass man eine für den Benutzer sinnvolle Darstellung als Zeichenkette ausgeben kann, in dem man ganz einfach das Objekt an `print()` übergibt.
Ja das gefällt mir an Python auch sehr gut. Also dass da, wo es eben Sinn macht auch der Funktionale Part zum Tragen kommt. (Oder habe ich da was missverstanden?)
Ich bin auch kein OOP-Hardliner im Sinne von "nur OOP ist das einzig wahre".
Imho konnte OOP in vielen Aspekten seine Versprechen nicht wirklich einhalten. Aber das dürfte eine andere Diskussion sein :)#
Ich weiß nicht, ob ich mich trauen sollte, die hier aufzumachen. ;-)
Die Argumentanzahl generell so drastisch zu reduzieren wie das von Martin vorgeschlagen wird, verschiebt das Problem IMHO auch nur zu immer mehr und immer verschachtelteren Argument-Objekten.
ja, das ist korrekt. Es gibt aber eben auch Fälle, wo endlos lange Parameterlisten, die vielleicht noch Gott-weiß-wohin "durchgereicht" werden, zu unwartbarem Code führen.
Da ist man dann mit einer Klasse (oder einem Record) durchaus "besser" dran.
Letzlich hängt es auch davon ab, wie stabil eine Parameterliste potentiell ist. Bei dem Beispiel mit dem Kreis kann man natürlich davon ausgehen, dass das perspektivisch stabil bleibt.

Mit Büchern zu statisch typisierten Sprachen wäre ich bei Python (und anderen dynamischen Sprachen) vorsichtig.
Hast Du eine Empfehlung, was man da so mal alternativ lesen könnte?
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

ich hab nochmal nachgedacht.
Ggf. braucht man "private" in Python auch gar nicht (so oft), weil es andere/bessere Möglichkeiten gibt, die ich noch nicht kenne.
Wäre es aus Eurer Sicht ratsam, sich z.B. mal die Architektur einer Lib (z.B. Pandas) näher anzusehen, um da mehr zu verstehen?
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich denke prinzipiell braucht man das so oft oder selten wie auch in anderen Sprachen, wenn eine ähnliche Aufgabe gelöst wird. Es wird schlicht nur nicht so ein Gewese drum gemacht.
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Buchfink: Ich habe darüber noch mal nachgedacht, und ein Beispiel gesucht für etwas in BASIC, weil der OP ja schon mal von BASIC und GOTO sprach. Beim Advent of Code 2015, Tag 18 war die Aufgabe eine quadratisches Gitter aus Lichtern nach bestimmten Regeln zu animieren (Conway lässt grüssen) und im zweiten Teil gibt es zwei verschiedene Arten von Lichtern — neben den normalen welche die ständig an sind, und die sich auch nicht ausschalten lassen. Die Aufgabe in Kurzform:

Teil 1:

Lichterquadrat bei dem die Anfangskonfiguration als Puzzle-Eingabe gegeben ist, das eine gegebene Anzahl von Schritten nach folgenden Regeln animiert werden soll:
  • Ein Licht das an ist, bleibt an, falls 2 oder 3 Nachbarlichter an sind, sonst geht es aus.
  • Ein Licht das aus ist, geht an, falls genau 3 Nachbarlichter an sind, sonst bleibt es aus.
Die Regeln gelten von einem Schritt zum nächsten für alle Lichter simultan, dass heisst das Schalten eines Lichts im aktuellen Schritt, hat keinen Einfluss auf die Anzahl der eingeschalteten Nachbarn für seine Nachbarlichter.

Nachbarn sind die 8 Felder direkt um ein Licht herum. Bei Lichtern am Rand zählen nicht vorhandene Nachbarn nachvollziebarerweise als ausgeschaltet.

Am Ende der vorgegebenen Anzahl von Schritten soll die Gesamthelligkeit berechnet werden, was laut Aufgabe der Anzahl der dann eingeschalteten Lichter entspricht.

Teil 2:

Das selbe wie in Teil 1, nur das jetzt die vier Lichter in den Ecken immer eingeschaltet sind, und sich nicht ausschalten lassen.

In der Aufgabe ist ein kleines 6×6 Beispiel zur Verdeutlichung und zum testen der eigenen Lösung geeignet. "#" ist ein eingeschaltetes Licht und "." ein ausgeschaltetes. Testlauf mit Debugausgaben vom Programm (der erste ”Initial state” entspricht dem Inhalt der Eingabedatei):

Code: Alles auswählen

$ ./day18_fb -h
Usage: ./day18_fb -h | [-s steps] [-d] [filename]

      -h  print help and exit
-s steps  set number of steps (default 100)
      -d  print debug output

The default filename is input.txt.
$ ./day18_fb -s 5 -d test_input.txt 

Initial state:
.#.#.#
...##.
#....#
..#...
#.#..#
####..
[Brightness: 15]

After 1 step:
..##..
..##.#
...##.
......
#.....
#.##..
[Brightness: 11]

After 2 steps:
..###.
......
..###.
......
.#....
.#....
[Brightness: 8]

After 3 steps:
...#..
......
...#..
..##..
......
......
[Brightness: 4]

After 4 steps:
......
......
..##..
..##..
......
......
[Brightness: 4]

After 5 steps:
......
......
..##..
..##..
......
......
[Brightness: 4]
Brightness after 5 steps without stuck corners: 4

Initial state:
##.#.#
...##.
#....#
..#...
#.#..#
####.#
[Brightness: 17]

After 1 step:
#.##.#
####.#
...##.
......
#...#.
#.####
[Brightness: 18]

After 2 steps:
#..#.#
#....#
.#.##.
...##.
.#..##
##.###
[Brightness: 18]

After 3 steps:
#...##
####.#
..##.#
......
##....
####.#
[Brightness: 18]

After 4 steps:
#.####
#....#
...#..
.##...
#.....
#.#..#
[Brightness: 14]

After 5 steps:
##.###
.##..#
.##...
.##...
#.#...
##...#
[Brightness: 17]
Brightness after 5 steps with stuck corners: 17
Das Programm hat einen abstrakten Typ `TBaseLight` für ein einzelnes Licht mit Methoden zum Umwandeln in eine Zeichenkette ("#" oder ".") einem abstrakten Property um den Zustand zu setzen und abzufragen, einem Property um die Helligkeit abzufragen (0 oder 1) und eine Methode um den Zustand über eine Zeichenkette ("#" oder ".") zu setzen.

Davon abgeleitet sind dann `TLight` und `TStuckLight` die jeweils das Zustandsproperty entsprechend implementieren. Also ein einfaches Beispiel für Polymorphie über Vererbung/Untertypen.

Bei der Implementierung ist mir noch ein Vorteil aufgefallen, der einfacher als Polymorphie ist: Klassen/Typen/Objekte sind Namensräume. Man muss nicht mehr so stark aufpassen eindeutige Prozedur oder Funktionsnamen zu wählen, weil `TBaseLight.brightness` eine andere Funktion als `TLightGrid.brightness` ist, man sich beim Aufruf aber auf `brightness` für beide beschränken kann, weil der Compiler aus dem Aufrufkontext weiss was gemeint ist. Wobei das natürlich streng genommen nicht auf OOP beschränkt ist, denn das könnte man auch über das Überladen von Funktionen/Prozeduren via Signatur erreichen. FreeBASIC könnte das sogar.

In FreeBASIC ist auch die folgende Lösung der Aufgabe geschrieben. Das ist als freier Compiler für QBasic-Quelltexte gestartet, aber mittlerweise um einige Features erweitert. Unter anderem Typen als ”Klassen” mit Properties und Methoden (und Zugriffsschutz).

Code: Alles auswählen

'
' Advent of Code 2015 - day 18
'

' Basic light type with boolean state and readonly brightness properties.  The
' state can be set with a string and the object can be cast to a suitable
' string.
Type TBaseLight Extends Object
  Const ON = "#"
  Const OFF = "."
  
  Declare Const Operator Cast() As String
  
  Declare Const Abstract Property state As Boolean
  Declare Abstract Property state(value As Boolean)
  Declare Const Property brightness As Integer
  
  ' Set state from given string.  Returns TRUE if the string is a valid state
  ' representation, FALSE otherwise.
  Declare Function SetFromString(value As Const String) As Boolean
End Type

Const Operator TBaseLight.Cast() As String
  Return IIf(state, ON, OFF)
End Operator

Const Property TBaseLight.brightness As Integer
  Return IIf(state, 1, 0)
End Property

Function TBaseLight.SetFromString(value As Const String) As Boolean
  Dim ok As Boolean
  
  ok = TRUE
  Select Case value
    Case ON
      state = TRUE
    Case OFF
      state = FALSE
    Case Else
      ok = FALSE
  End Select
  Return ok
End Function


' Normal light.
Type TLight Extends TBaseLight
  Declare Const Property state As Boolean Override
  Declare Property state(value As Boolean) Override

  Private:
    _state As Boolean
End Type

Const Property TLight.state As Boolean
  Return _state
End Property

Property TLight.state(value As Boolean)
  _state = value
End Property


' A stuck light.  Reading the state is always TRUE, writing it has no effect.
Type TStuckLight Extends TBaseLight
  Declare Const Property state As Boolean Override
  Declare Property state(value As Boolean) Override
End Type

Const Property TStuckLight.state As Boolean
  Return TRUE
End Property

Property TStuckLight.state(value As Boolean)
  ' Setting the state has no effect on a stuck light.
End Property


' A square grid of lights.
'
' The file format is one line per row with "." for a light in off state and "#"
' for a light in on state.  Such a file can be loaded with the `Load` method.
' 
Type TLightGrid
  Declare Destructor
  
  ' Cast the this into a string of the format that `Load` expects in a file.
  Declare Const Operator Cast() As String
  
  ' Load the state from given `filename`.  If `hasStuckCorners` is TRUE then the
  ' lights in the four corners are always on, regardless of the value in the
  ' file.
  Declare Sub Load(filename As Const String, hasStuckCorners As Boolean = FALSE)
  
  ' Advance the grid state one step.
  '
  ' The rules are:
  '
  ' * A light that is on, stays on if 2 or 3 neighbours are on,
  '   otherwise it goes off.
  ' * A light that is off, goes on if exactly 3 neighbours are on,
  '   otherwise it stays off.
  '
  ' These rules are applied simultaneously to all lights.
  Declare Sub DoStep
  
  ' The overall brightness of the light grid.
  Declare Const Property brightness As Integer
  
  Private:
    size As Integer
    lights(Any, Any) As TBaseLight Ptr
    
    ' Free the array/memory of the lights. Pulled into an own method because the
    ' destructor and `Load` need this.
    Declare Sub Reset
    
    ' Get the light state at given coordinates.  If coordinates are outside the
    ' grid, return FALSE.
    Declare Const Function GetStateAt(i As Integer, j As Integer) As Boolean
    
    ' Test wether the given coordinates are a corner of the grid.
    Declare Const Function IsCorner(i As Integer, j As Integer) As Boolean
    
    ' Count the neighbouring lights that are on.  This considers all eight
    ' neighbours around the given coordinates.  Off grid positions are
    ' considered being off.
    Declare Const Function CountLitNeightbours(i As Integer, j As Integer) As Integer
End Type

Sub TLightGrid.Reset
  Dim i As Integer, j As Integer
  
  If size > 0 Then
    For i = 1 To size
      For j = 1 To size: Delete lights(i, j): Next
    Next
  End If
  size = 0: Erase lights
End Sub

Destructor TLightGrid
  Reset
End Destructor

Const Operator TLightGrid.Cast() As String
  Dim result As String, i As Integer, j As Integer, k As Integer
  ' Reserve space for a `size` by `size` character grid with a line ending
  ' character at the end of each row.
  result = Space(size * (size + 1))
  k = 1
  For i = 1 To size
    For j = 1 To size
      Mid(result, k) = *lights(i, j)
      k = k + 1
    Next
    Mid(result, k) = Chr(10)
    k = k + 1
  Next
  Return result
End Operator

Const Function TLightGrid.GetStateAt(i As Integer, j As Integer) As Boolean
  If 1 <= i And i <= size And 1 <= j And j <= size Then
    Return lights(i, j)->state
  Else
    Return FALSE
  End If
End Function

Const Function TLightGrid.IsCorner(i As Integer, j As Integer) As Boolean
  Return i = 1    And j = 1 _
      Or i = 1    And j = size _
      Or i = size And j = 1 _
      Or i = size And j = size
End Function

Sub TLightGrid.Load(filename As Const String, hasStuckCorners As Boolean)
  Dim f As Long, row As String, i As Integer, j As Integer, c As String
  Dim light As TBaseLight Ptr
  
  If size > 0 Then Reset
  
  f = FreeFile
  Open filename For Input As #f
  Line Input #f, row
  size = Len(row)
  ReDim lights(1 To size, 1 To size)
  For i = 1 To size
    For j = 1 To size
      If hasStuckCorners And IsCorner(i, j) Then
        light = New TStuckLight
      Else
        light = New TLight
      End If
      
      c = Mid(row, j, 1)
      If Not light->SetFromString(c) Then
        Print row
        Print Spc(j - 1); "^"
        Print "Unexpected ";
        If c = "" Then
          Print "end of file";
        Else
          Print "character '"; c; "'";
        End If
        Print " at line"; i
        End 1
      End If
      lights(i, j) = light
    Next
    Line Input #f, row
  Next
  Close #f
End Sub

Const Function TLightGrid.CountLitNeightbours(i As Integer, j As Integer) As Integer
  Dim result As Integer, di As Integer, dj As Integer
  
  result = 0
  If size > 0 Then
    For di = -1 To 1
      For dj = -1 To 1
        If Not (di = 0 And dj = 0) Then
          If GetStateAt(i + di, j + dj) Then result = result + 1
        End If
      Next
    Next
  End If
  Return result
End Function

Sub TLightGrid.DoStep
  Dim newStates(1 To size, 1 To size) As Boolean
  Dim i As Integer, j As Integer, count As Integer
  
  If size > 0 Then
    ' Calculate new states.
    For i = 1 To size
      For j = 1 To size
        count = CountLitNeightbours(i, j)
        If GetStateAt(i, j) Then
          newStates(i, j) = count = 2 Or count = 3
        Else
          newStates(i, j) = count = 3
        End If
      Next
    Next
    ' Set new states.
    For i = 1 To size
      For j = 1 To size
        lights(i, j)->state = newStates(i, j)
      Next
    Next
  End If
End Sub

Const Property TLightGrid.brightness As Integer
  Dim i As Integer, j As Integer, result As Integer
  
  result = 0
  If size > 0 Then
    For i = 1 To size
      For j = 1 To size
        result = result + lights(i, j)->brightness
      Next
    Next
  End If
  Return result
End Property


' Print the step number, the grid state, and the grid's brightness for debugging
' purposes.
Sub PrintLights(lights As Const TLightGrid, stepNumber As Integer = 0)
  Print
  If stepNumber = 0 Then
    Print "Initial state:"
  Else
    Print "After"; stepNumber; " step"; IIf(stepNumber > 1, "s", ""); ":"
  End If
  Print lights;
  Print "[Brightness:"; lights.brightness; "]"
End Sub


'
' The main program.
'
Const DEFAULT_STEP_COUNT = 100, DEFAULT_FILENAME = "input.txt"

Dim filename As String, stepCount As Integer, debug As Boolean

filename = DEFAULT_FILENAME
stepCount = DEFAULT_STEP_COUNT
debug = FALSE

Scope
  Dim i As Integer
  
  i = 1
  Do While Command(i) <> ""
    Select Case Command(i)
      Case "-h"
        Print "Usage: "; Command(0); " -h | [-s steps] [-d] [filename]"
        Print
        Print "      -h  print help and exit"
        Print "-s steps  set number of steps (default"; DEFAULT_STEP_COUNT; ")"
        Print "      -d  print debug output"
        Print
        Print "The default filename is "; DEFAULT_FILENAME; "."
        End 0
      Case "-s"
        i = i + 1
        stepCount = Val(Command(i))
      Case "-d"
        debug = TRUE
      Case Else
        If Left(Command(i), 1) = "-" Then
          Print "Unknown option: "; Command(i); "."
          Print "Try -h for help."
          End 1
        End If
        filename = Command(i)
    End Select
    i = i + 1
  Loop
End Scope

Scope
  Dim lights As TLightGrid, i As Integer, hasStuckCorners As Integer
  
  For hasStuckCorners = 0 To 1
    lights.Load filename, hasStuckCorners
    If debug Then PrintLights lights
    
    If stepCount > 0 Then
      For i = 1 To stepCount
        lights.DoStep
        If debug Then PrintLights lights, i
      Next
    End If
        
    Print "Brightness after"; stepCount; " steps "; _
        IIf(hasStuckCorners, "with", "without"); " stuck corners:"; _
        lights.brightness
  Next
End Scope
Konkreter Fall beim Erstkontakt mit ”OOP” war bei mir Informatikunterricht mit Turbo Pascal. Der Lehrplan sah die Entwicklung einer Adressverwaltung mit Units und RECORDs vor. Und das haben wir auch so durchgezogen. Und ganz am Ende hat uns der Lehrer dann die tollen neuen OOP-Konstrukte in Turbo Pascal 5.x gezeigt, in dem er das Program ”objectorientiert” umgeschrieben hat. Also letztlich aus den RECORDs halt Objekte gemacht, was syntaktisch halt ein kleines bisschen anders aussah, aber ich hatte so überhaupt nicht verstanden warum der so aus dem Häusschen war, denn so hat man natürlich nicht wirklich einen Vorteil gesehen, denn letztendlich war es ja das gleiche Programm.

Was mir zu dem Zeitpunkt natürlich auch noch nicht klar war, war das OOP ein Paradigma ist und keine Spracheigenschaft. Letztlich war die prozedurale Version von dem Programm schon sehr nah an der Idee von OOP dran. Was wohl auch ein Grund war, warum ich den Quantensprung in der OOP-Variante nicht erkennen konnte, denn der war halt wirklich sehr klein.

Klick gemacht hat es dann erst bei Java. Das ist ja die Holzhammermethode weil da *alles* in Klassen gepresst werden muss.

Hm, ob bei `__str__()` und `print()` der funktionale Part zum Tragen kommt ist eine gute Frage. Letztlich ist Python “durch und durch“ objektorientiert, denn jeder Typ ist eine Klasse und jeder Wert ist ein Objekt. Das schliesst ja Funktionen mit ein. Auch das sind Objekte, die halt *eine* sehr wichtige Methode haben: `__call__()`. Denn ``something(42)`` steht ja letztlich die Kurzform für ``something.__call__(42)``. Und ob `something` nun für ein Objekt vom Typ `function` steht, oder irgendwas anderes das `__call__()` implementiert, ist ja letztlich egal. Wenn es ``def`` für Funktionen nicht gäbe, sondern nur für Methoden, würde man halt mehr Boilerplate schreiben:

Code: Alles auswählen

class Something:
    def __call__(self, argument):
        ...

something = Something()

# statt einfach nur

def something(argument):
    ...
Ist der erste Code jetzt irgendwie objektorientierter, nur weil man da den Boilerplate mit der Klasse schreiben muss? Und wird es plötzlich funktional, wenn man den Boilerplate weglassen kann?

Die Funktionen die in der Regel einer ”magischen“ Methode entsprechen wie `str()`, `repr()`, `len()`, und so weiter, kann man auch als Operatoren auffassen. Das war mal die Antwort von Guido warum `len()` eine Funktion ist. Er sah das eher als Operator, den man überladen kann, und der einen Namen hat, statt dafür irgendein kryptisches Zeichen als extra Syntax zu definieren.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@_deets_
Es wird schlicht nur nicht so ein Gewese drum gemacht.
Der Erfolge von Python gibt dem ja bereits Recht. Davon abgesehen scheitern Softwareprojekte sicher nicht an "private" bzw. den Eigenschaften einer Programmiersprache, sondern an soziologischen Aspekten. Das sei jedoch nur am Rande bemerkt.
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@__blackjack__
Oh wow, da hast Du ja wirklich ein schönes Beispiel aufgetan.
Bei der Implementierung ist mir noch ein Vorteil aufgefallen, der einfacher als Polymorphie ist: Klassen/Typen/Objekte sind Namensräume. Man muss nicht mehr so stark aufpassen eindeutige Prozedur oder Funktionsnamen zu wählen, weil `TBaseLight.brightness` eine andere Funktion als `TLightGrid.brightness` ist, man sich beim Aufruf aber auf `brightness` für beide beschränken kann, weil der Compiler aus dem Aufrufkontext weiss was gemeint ist.
Dieser Vorteil ist so naheliegend, dass er mir glatt entgangen ist. Manchmal sieht man ja den Wald vor lauter Bäumen nicht.

zum Code
ich muss gestehen, dass ich in meinem Leben nur mal drei Zeilen in BASIC-Legacy-Code ändern musste. Und das ist ca. 10 Jahre her.
Aber grundsätzlich habe ich verstanden, was der Code tut.
Konkreter Fall beim Erstkontakt mit ”OOP” war bei mir Informatikunterricht mit Turbo Pascal. Der Lehrplan sah die Entwicklung einer Adressverwaltung mit Units und RECORDs vor. Und das haben wir auch so durchgezogen. Und ganz am Ende hat uns der Lehrer dann die tollen neuen OOP-Konstrukte in Turbo Pascal 5.x gezeigt, in dem er das Program ”objectorientiert” umgeschrieben hat. Also letztlich aus den RECORDs halt Objekte gemacht, was syntaktisch halt ein kleines bisschen anders aussah, aber ich hatte so überhaupt nicht verstanden warum der so aus dem Häusschen war, denn so hat man natürlich nicht wirklich einen Vorteil gesehen, denn letztendlich war es ja das gleiche Programm.
Ich persönlich habe mit C angefangen.
Im Hinblick auf OOP glaube ich, dass man gute OOP-Architekturen (vermutlich auch im Kontext anderer Paradigmen) nicht an _einem_ Beispiel lernt, sondern an hunderten.
Ich will sagen, dass es eher ein Prozess ist als ein einmaliger Vorgang. Und die Lernkurve innerhalb dieses Prozesses ist steiler, wenn man öfter mal Gelegenheit hat, seinen Code zu refactoren (ist jedenfalls mein subjektiver Eindruck)

(Da Vinci hat an der Mona Lisa auch fast sein ganzes Leben lang gemalt. Immer wurde eine neue Erkenntnis hinzugefügt und auch Bereiche übermalt etc. Ich bin mal so frei, diese Analogie zu ziehen.)
Was ein gutes Framework ist, habe ich auch gelernt, nachdem ich 3 Jahre mit einem Framework der übelsten Sorte arbeiten musste und irgendwann (ohne Erlaubnis des EL) auf ein anderes Framework gewechselt bin. Plötzlich ging alles ganz leicht.
Und ich war nicht mehr damit beschäftigt, wie ich das Framework "verbiegen" muss, um Buissneslogik abzubilden.
Insofern muss ich auch sagen, dass mir Pandas direkt sehr sympatisch war.

Des weiteren meine ich, dass Code-Reviews helfen können, neue Denkweisen zu erlernen.
Ich persönlich bin mit dem "funktionalen Aspekt" von Python derzeit noch etwas sagen wir mal ratlos gegenüberstehend.
Obgleich ich, wie ich oben schon schrieb, OOP nicht für die "Silberkugel" halte. Auch ein Grund mal was Neues auszuprobieren :)
Wenn es ``def`` für Funktionen nicht gäbe, sondern nur für Methoden, würde man halt mehr Boilerplate schreiben:
Das ist ein interessanter Aspekt. Das habe ich aus den Tutorials bisher noch nicht mitgenommen. Es gibt noch so viel zu viel zu
lernen. :)
Ist der erste Code jetzt irgendwie objektorientierter, nur weil man da den Boilerplate mit der Klasse schreiben muss? Und wird es plötzlich funktional, wenn man den Boilerplate weglassen kann?
Ich sage es mal so:
Objekte /Klassen sind für Dinge, die Eigenschaften haben.
Funktionen sehe ich eher für abstrakte Aktionen.
Insofern finde ich das in Python oft sehr "treffend" umgesetzt. Also z.B. den Print-Befehl finde ich je mehr ich drüber nachdenke, eigentlich sehr intuitiv.
Und ein gutes Framework/Lib sollte doch vor allem intuitiv sein. Zumindest geht damit die Arbeit leicht von der Hand.
Und das ist doch der eigentlich entscheidende Gradmesser für jedwede Form der Architektur.

Vielen lieben Dank für Deine Erläuterungen, das hilft mir wirklich sehr weiter, ein Stück hinter die Kulissen von Python zu blicken.

LG
Buchfink
User
Beiträge: 193
Registriert: Samstag 11. September 2021, 10:16

@spicer
Edge übersetzt bei Bedarf auch automatisch. (Ist konfigurierbar)
Interessehalber habe ich das mal für PEP 8 (https://www.python.org/dev/peps/pep-0008/#id14) ausprobiert und bin zum Ergebnis gekommen, dass DeepL (wie von @Dennis89 vorgeschlagen) oft die besseren Übersetzungen liefert.

ganz gut erkennbar ist es an diesem Passus
Continuation lines should align wrapped elements either vertically using Python's implicit line joining inside parentheses, brackets and braces, or using a hanging indent [6]. When using a hanging indent the following should be considered; there should be no arguments on the first line and further indentation should be used to clearly distinguish itself as a continuation line:
LG
Antworten