Klasse verschiedene varianten

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.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

- Überschreiben von Builtins vermeiden (z.B. von map)
- List Comprehensions nutzen, wenn möglich
- pythonische Lösungen (z.B. zip() und any()) gegenüber klassischen Ansätzen bevorzugen
- bei Factory-Methoden das erstellte Objekt liefern oder None anstatt True/False
- ...

Bei weitem nicht alles, was ich verbessern würde, aber hier ein paar Ansätze (ungetestet):

Code: Alles auswählen

#!/usr/bin/python3
from random import randrange
 
def _randpos(size):
    return randrange(size), randrange(size)

 
class Game():
    def __init__(
        self, game_size, planet_size, num_planets, num_players, building_types
    ):
        if num_players > num_planets:
            raise ValueError('num_players must not exceed num_planets')
        
        self.planets = [
            Planet(_randpos(game_size), planet_size)
            for _ in range(num_planets)
        ]
        for i, planet in zip(range(num_players), self.planets):
            planet.place_commando_center(i)
        
        self.building_types = {tp.__name__: tp for tp in building_types}
        self.turn = 0
        self.undetermined_commands = []
           
    def _show_all_planets(self):
        # Only meant for testing purposes
        for plnt in self.planets:
            plnt._show()
   
    def get_home_planet(self, player):
        return self.planets[player]
 
    def build_object(self, planet_id, player_id, position, object_type):
        planet = self.planets[planet]
        if object_type.is_buildable(planet, player_id, position):
            field = planet.get_field(position)
            obj = object_type(planet_id, player, position)
            planet.set_field(position, (field[0], obj))
            return obj
 
    def turn_end(self):
        while self.undetermined_commands:
            command = self.undetermined_commands.pop(random.randrange(len(
                self.undetermined_commands)))
            self.run_command(command)
        self.undetermined_commands = []
 
    def add_command(self, command):
        self.undetermined_commands.append(command)
 
    def run_command(self, command):
        name = command[0]
        if name == "build":
            self.build_object(command[1], command[2], command[3],
                    self.registered_buildings[command[4]])
 
 
class Planet():
    def __init__(self, position, size):
        self.position = position
        self.size = size
        self.map = [
            [('d', None) for _ in range(size)] for _ in range(size)
        ]
 
    def __repr__(self):
        class_name = type(self).__name__
        x, y = self.position
        return '{}(x={}, y={})'.format(class_name, x, y)
 
    def place_commando_center(self, player_id):
        position = _randpos(self.size)
        field = self.get_field(pos)
        commando_center = CommandoCenter(self, player_id, position)
        self.set_field(position, (field[0], commando_center))
 
    def has_commando_center(self, player_id):
        return any(
            field[1].owner == player_id and isinstance(field[1], CommandoCenter)
            for row in self.map for field in row
        )
 
    def get_field(self, position):
        x, y = position
        return self.map[x][y]
   
    def set_field(self, position, field):
        x, y = position
        self.map[x][y] = field
   
    def _show(self):
        print(self)
        print('\n'.join(self.map))
   
 
class Building():
    __name__ = "Building"
    def __init__(self, planet, owner, pos):
        self.owner = owner
        self.planet = planet
   
    @staticmethod
    def is_buildable(planet, owner, pos):
        return True
 
class CommandoCenter(Building):
    __name__ = "Commando Center"
 
class House(Building):
    __name__ = "House"
    @staticmethod
    def is_buildable(planet, owner, pos):
        #TODO: make it only buildable near CommandoCenter
        return True
       
 
buildings = []
buildings.append(CommandoCenter)
buildings.append(House)
 
class SpaceShip():
    def __init__(self, pos, target):
        self.dir_vector = dir_vector
        self.pos = pos
        self.target = target
 
    def on_step():
        pass#NotImplementedYet
 
 
 
 
 
##test section
print(buildings)
g = Game(10, 3, 5, 3, buildings)
print("NEW GAME", end="\n"*3)
g.add_command(("build", 0, 0, (0, 0), "House"))
g.turn_end()
g._show_all_planets()
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Vielleicht sollte ich mir mal ein cheat sheet für die häufigsten Python Funktionen erstellen ... any z.B. hatte ich mir lange nicht mehr angeschaut :)
Danke für die Tipps. Ich glaub ich werde die Gebäude insgesamt in eine andere Datei auslagern, auf welche dann als Modul zugegriffen wird.
Dann kann ich mir den ganzen register-Quatsch sparen :), das wird dann mit ausgelagert ...
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Pygoscelis papua: für den Ablauf ist es egal, wie Du Dein Programm strukturierst; auch wenn die Gebäudetypen in einem eigenen Modul sind, brauchst Du irgendeinen „register-Quatsch“. Weil building_classes building heißen, macht es für mich schwer lesbar. register_building ist auch überflüssig, weil damit sowieso nur ein Wörterbuch in ein anderes kopiert wird. Bei build_object übergibst Du ein object, das aber eine Klasse ist. Wäre auch sonst komisch, einer Funktion, die build_object heißt, schon ein Objekt zu übergeben. Besser als direkt die Klasse wäre es, den Gebäudetypnamen zu übergeben, wozu hast Du denn registered_building_types.
Die Methode sollte dann auch build_building heißen. Du arbeitest auch noch mit zu vielen unstrukturierten Listen und magischen Indizes. Warum muß man einem set_field erst das 0. Element des ursprünglichen Feldes übergeben? Für turn_end schau Dir mal random.shuffle an. Bei run_command benutzt Du wieder eine Liste anstelle eines strukturierten Objekts. Am besten schreibst Du für jeden Befehlstyp eine eigene Klasse. In has_commando_center ist y für eine Feldzeile der falsche Name, x für ein Feld ebenso. type sollte man nicht zum Vergleichen benutzen, isinstance ist besser, eine eigene Methode is_commando_center noch besser. __name__ ist ein internes Attribut, das beim Erzeugen einer Klasse automatisch gesetzt wird. Das selbst zu setzen ist ein Fehler, da es keine Auswirkung hat. Nenn es doch einfach BUILDING_TYPE. Bisher sind Planeten und Spieler nur Nummern, was dem Game-Objekt die Aufgabe aufbürdet das alles zu verwalten. Arbeite mehr mit Objekten.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Genau, das wollte ich auch noch sagen: Anstatt mit 2-elementrigen Listen zu arbeiten, die man über den Index ansprechen muss, sind namedtuples viel besser. Also wie schon vorgeschlagen wurde, ruhig zu Beginn ein paar Containerklassen definieren, jeweils für Koordinaten, Feldinfo und sowas. Denkbar sind auch mehrschichtige Strukturen, sodass man ein benanntes Tupel für ein Feld hat und dieses besitzt als Attribute die Koordinaten (wieder als eigener Typ) sowie eine Info zur Bebauung.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

snafu hat geschrieben:Also wie schon vorgeschlagen wurde, ruhig zu Beginn ein paar Containerklassen definieren, jeweils für Koordinaten, Feldinfo und sowas.
Noch eine Stufe vorher empfehle ich ein großes Blatt Papier, einen Bleistift, sowie einen *großen* Radiergummi. :)
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Und im Hinblick auf den "Register-Quatsch" hatte ich ja schon im Code angedeutet, dass man die Registierung der Gebäudetypen in __init__ als Dictionary Comprehension per Einzeiler abfrühstücken kann. Eine nachträgliche Registrierung von Gebäudetypen während des Spiels ist doch sowieso unrealistisch. Oder geht es um die Freischaltung weiterer Gebäudetypen? Dann würde ich das wahrscheinlich eher über ein activated-Atrribut lösen. Denn ich kenne es so, dass Gebäude aus höheren Stufen zunächst ausgegraut sind. Dass ein angefangenes Spiel plötzlich komplett neue Gebäudetypen erhält, habe ich bisher noch nicht gesehen. Wenn das irgendwann möglich sein soll, dann wäre eine Plugin-API für diesen Punkt vielleicht geeigneter.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

kbr hat geschrieben:
snafu hat geschrieben:Also wie schon vorgeschlagen wurde, ruhig zu Beginn ein paar Containerklassen definieren, jeweils für Koordinaten, Feldinfo und sowas.
Noch eine Stufe vorher empfehle ich ein großes Blatt Papier, einen Bleistift, sowie einen *großen* Radiergummi. :)
Wobei man IMHO schon viel Erfahrung braucht, um so ein Projekt quasi auf dem Papier durchplanen zu können. Der OP kommt mir so vor als wenn er zwar nicht gänzlich neu auf dem Feld der Programmierung ist, aber noch recht am Anfang bei der Progammierung komplexer Spiele steht. Vieles ist dann auch einfach Learning By Doing mit dem häufigen auf die Nase fallen inklusive.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

snafu hat geschrieben:Wobei man IMHO schon viel Erfahrung braucht, um so ein Projekt quasi auf dem Papier durchplanen zu können.
Etwas im Vorfeld fehlerfrei durchplanen zu können halte ich für unrealistisch. Es geht vielmehr darum einen Einstieg zu finden, der einem später möglichst wenig Kopfschmerzen bereitet. Ohne Plan zu starten ist schlecht - selbst bei agiler Entwicklung :)
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Also das Blatt Papier existiert schon :D
Ich habe: - eine Liste Für die meilensteine,
- ein Klassendiagramm
- ein Groben schriftlichen Text für die Anforderungen
- einen Plan für die Graphische Oberfläche

Und die Änderungen von Snafu sind auch schon übernommen,
die Registrierung ist jetzt eben nicht mehr in der Game-Klasse sondern in dem building_types.py Modul, von welchem man das importieren kann.
Den Rest schaue ich mir heute mal an.
Vieleicht habe ich in letzter Zeit zu viel lua und pascal programmiert und muss mich in Python erst wieder reinfinden :)
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

Ich bin gerade noch dabei die Anmerkungen welche ihr mir gegeben habt um zu setzten.
Dabei ist aber etwas sehr seltsames passiert. Ich habe die klasse Planet wie folgt definiert:

Code: Alles auswählen

class Planet():
    def __init__(self, position, size):
        self.position = position
        self.size = size
        self.map = [
            [MapField((x, y), ground_types["G"]) for x in range(size)] 
            for y in range(size)
        ]
        print(type(self))
 
    def __repr__(self):
        class_name = type(self).__name__
        x, y = self.position
        return '{}(x={}, y={})'.format(class_name, x, y)
 
    def place_commando_center(self, owner):
        position = _randpos(self.size)
        return self.build_building(position, "Commando Center", owner)
 
    def has_commando_center(self, player): #must be tested!
        return any(
            field.get_owner() == player and 
            isinstance(field.get_building(), CommandoCenter)
            for field in self._get_all_fields()
        )
        
    def get_field(self, position):
        x, y = position
        return self.map[x][y]
   
    def set_ground(self, position, ground):
        field = self.get_field(position)
        field.set_ground(ground)

    def build_building(self, position, building_name, owner):
        field = self.get_field(position)
        return field.build_building(self, building_name, owner)

    def _show(self):
        print(self)
        print('\n'.join([str(i) for i in self.map]))

    def _get_all_fields(self):
        return [field for row in self.map for field in row]
und jetzt wird mir immer der __name__ von allen Planet-instanzen auf "instance" gesetzt, wobei die Methoden gleich verwendet werden können.
Das bedeutet die ausgabe von planetinstance.__repr__() ist dann "instance{x=..., y=...}".
wie kann das passiert sein?
Zuletzt geändert von Anonymous am Samstag 14. Januar 2017, 18:41, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Pygoscelis papua hat geschrieben:

Code: Alles auswählen

class Planet():
    def __repr__(self):
        class_name = type(self).__name__
        x, y = self.position
        return '{}(x={}, y={})'.format(class_name, x, y)
jetzt wird mir immer der __name__ von allen Planet-instanzen auf "instance" gesetzt
die ausgabe von planetinstance.__repr__() ist dann "instance{x=..., y=...}".
wie kann das passiert sein?
__repr__ hast du ja selbst definiert und vorgegeben, was da bei return zurückkommt. Neben x und y auch class_name. Praktischerweise wird class_name genau 2 Zeilen drüber festgelegt. Wenn du dir das anschaust, welche Ausgabe würdest du erwarten, wenn nicht "instance"? Solche Fragen kann man gut im interaktiven Interpreter ausprobieren und Schritt für Schritt leichter oder komplizierter werden, bis alle Unklarheiten beseitigt sind. :wink:
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
BlackJack

@Kebap: Also mich würde auch interessieren warum da 'instance' bei raus kommen soll:
[codebox=pycon file=Unbenannt.txt]>>> class Planet():
... def __repr__(self):
... class_name = type(self).__name__
... x, y = self.position
... return '{}(x={}, y={})'.format(class_name, x, y)
...
>>> p = Planet()
>>> p
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __repr__
AttributeError: 'Planet' object has no attribute 'position'
>>> p.position = (1,2)
>>> p
Planet(x=1, y=2)
>>> type(p)
<class '__main__.Planet'>
>>> type(p).__name__
'Planet'[/code]

@Pygoscelis papua: Konvention für `__repr__()` ist das da entweder etwas raus kommt das man im Quelltext so schreiben könnte um ein gleiches Objekt zu erhalten, oder das die `repr()`-Darstellung in spitze Klammern eingefasst wird. Beispielsweise '<Planet x=… y=…>'.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Der Sinn, warum man den Namen dynamisch über das __name__ Attribut des Typen ermittelt anstatt ihn einfach direkt hinzuschreiben, ist übrigens der Gedanke an Vererbung. Wenn jetzt jemand einen RedPlanet ableiten würde, dann zeigt __repr__() einem automatisch RedPlanet(...) an, ohne dass man die Methode anpassen müsste. Ausgenommen sind natürlich Fälle, wo neue Attribute eingeführt werden. Die werden dann nicht automatisch angezeigt. Wenn man das auf die Spitze treiben möchte, dann ginge auch so etwas:

Code: Alles auswählen

class Planet(object):
    def __init__(self, position, size):
        self.position = position
        self.size = size
        self._repr_names = ['position', 'size']

    def __repr__(self):
        class_name = type(self).__name__
        signature = ', '.join(
            '{}={!r}'.format(name, getattr(self, name))
            for name in self._repr_names
        )
        return '{}({})'.format(class_name, signature)


class ColoredPlanet(Planet):
    def __init__(self, position, size, color):
        Planet.__init__(self, position, size)
        self.color = color
        self._repr_names.append('color')


def main():
    planet = Planet((23, 42), 2342)
    red_planet = ColoredPlanet((23, 42), 2342, 'red')
    print(planet)
    print(red_planet)

if __name__ == '__main__':
    main()
Aber das nur nebenbei. Gibt ja bereits nametuple(), sodass man diese Dinge eigentlich nicht nochmal neu erfinden muss. In echtem Code würde ich das wahrscheinlich als Mixin implementieren, wenn nametuple() mir aus irgendwelchen Gründen nicht geeignet erscheint.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

die Ausgabe instance ist aber nicht nur bei __repr__() sondern auch bei
type(Planet).__name__ bzw. wenn ich schreibe Planer(parameter).name
Woran kann das denn liegen? Ich kapier es einfach nicht.
Und die __repr__() funktion hat snafu geschrieben nicht ich :)
@kepab ich hätte dort die ausgabe "Planet" erwartet wie bei allen anderen klassen
meine klasse heißt ja nicht instance.
hier mal dazu eine Ausgabe aus meinem Interpreter:

Code: Alles auswählen

[GCC 4.9.3] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class House():
...     pass
... 
>>> House.__name__
'House'
>>> 
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Da stand nicht type(Planet).__name__, sondern type(self).__name__

Und niemand zwingt dich, Code zu übernehmen, dessen Sinn dir nicht klar ist. Dann kann man auch fragen oder es weglassen.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Übrigens, type(Planet()).__name__ ergibt "instance", wenn man die Klasse unter Python 2.x ohne Basisklasse definiert. Daher sollte man immer von object erben, wenn man keine andere Basisklasse hat. Sonst erhält man unter Python 2 oldstyle-Klassen.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

was ist denn da der Unterschied?
hier eine andere Ausgabe aus meinem interpreter:

Code: Alles auswählen

>>> class House:
...     def __repr__(self):
...         return type(self).__name__
... 
>>> a = House()
>>> a
House
>>> a.__repr__()
'House'
und ich verwende ja nur Python3
EDIT: ich habe gerade gesehen das meine IDE irgendwie auf Python2 umgestellt hatte, daher rührte der Fehler also :)
Da hätte ich jetzt auch noch wochen im code suchen können :D nächstes mal teste ich es auch außerhalb der IDE ...
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich kenne deine Entwicklungsumgebung nicht, aber "instance" kann bei type(self).__name__ AFAIK nur unter Python 2.x herauskommen, wenn wie gesagt eine "nackte" Klasse ohne Basisklasse definiert wird. Python 3.x verwendet immer automatisch Newstyle-Klassen, auch ohne "object". Ich schreibe es trotzdem immer hin, weil es nicht wehtut und den Code damit kompatibel zu Python 2.x hält.

EDIT:
Dann hat sich das ja aufgeklärt. Und sorry, dass ich dich gerade so angefahren habe. Ich hatte das "ist nicht von mir -- snafu hat das so gezeigt" wohl ich den falschen Hals gekriegt... ;)
Zuletzt geändert von snafu am Samstag 14. Januar 2017, 22:26, insgesamt 1-mal geändert.
Pygoscelis papua
User
Beiträge: 206
Registriert: Freitag 13. März 2015, 18:36

ok dann mache ich das auch.
import this
hidden python features

JAVA = Just Another Vulnerability Announcement :D
Antworten