Spielfeld Objektorientiert darstellen

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.
Mustii-i
User
Beiträge: 6
Registriert: Freitag 21. Dezember 2018, 23:14

Nochmals viel Dank das geht hier mit den antworten wirklich schnell was ich sonst nicht so kenne :D

__blackjack__ danke für deine ausfühliche hilfe ich schau mir den code selbst mal genau an VIELEN DANK :O :D
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Der erste Teil der tief hängenden Früchte: Die beiden `namedtuple()` in Klassen umgewandelt:

Code: Alles auswählen

#!/usr/bin/env python3
from enum import Enum
from itertools import cycle, islice
import random
import time

from attr import attrib, attrs

TRACK_LENGTH = 24
HOME_LENGTH = 6
#:
#: Pairs of point number and count of checkers for the setup of the game.
#: 
SETUP = [(1, 2), (12, 5), (17, 3), (19, 5)]
CHECKERS_PER_PLAYER = sum(count for _, count in SETUP)

Player = Enum('Player', 'BLACK WHITE')
PLAYER_TO_CHARACTER = {
    None: ' ',
    Player.BLACK: 'B',
    Player.WHITE: 'W',
}

Win = Enum('Win', 'NONE NORMAL GAMMON BACKGAMMON')


def roll_dice():
    result = [random.randint(1, 6), random.randint(1, 6)]
    if result[0] == result[1]:
        result *= 2
    return result


def get_opponent(player):
    if player == Player.WHITE:
        return Player.BLACK
    if player == Player.BLACK:
        return Player.WHITE
    
    raise ValueError(f'{player} has no opposing value')


@attrs(frozen=True)
class Point:
    player = attrib(default=None)
    count = attrib(default=0)

    def __str__(self):
        return (
            PLAYER_TO_CHARACTER[self.player]
            + (format(self.count, '2d') if self.count else '  ')
        )

    def __attrs_post_init__(self):
        if self.count < 0:
            raise ValueError('count must be positive')
        if self.count == 0 and self.player is not None:
            raise ValueError('empty point cannot have a player')
        if self.count != 0 and self.player is None:
            raise ValueError('non empty point must have a player')    

    def is_empty(self):
        return self.player is None and self.count == 0

    def has_blot(self, player):
        return self.player == get_opponent(player) and self.count == 1

    def is_possible_start(self, player):
        return self.player == player and self.count > 0

    def is_possible_target(self, player):
        return (
            self.player == player or self.is_empty() or self.has_blot(player)
        )

    def checkers_added(self, player, value):
        count = self.count + value
        if count < 0:
            raise ValueError('amount of checkers must not become negative')
        return Point(None if count == 0 else player, count)


def create_track():
    track = [Point()] * TRACK_LENGTH
    for player in Player:
        for point_number, count in SETUP:
            put_checkers(track, player, point_number, count)
    return track


def validate_point_number(track, point_number):
    if not 0 < point_number <= len(track):
        raise ValueError('point_number out of range')
    

def reverse_point_number(track, point_number):
    validate_point_number(track, point_number)
    return len(track) - point_number + 1


def point_number_to_index(track, player, point_number):
    validate_point_number(track, point_number)
    
    if player == Player.BLACK:
        return point_number - 1
    if player == Player.WHITE:
        return reverse_point_number(track, point_number) - 1
    
    raise ValueError(f'illegal player {player}')


def get_point(track, player, point_number):
    return track[point_number_to_index(track, player, point_number)]


def set_point(track, player, point_number, point):
    track[point_number_to_index(track, player, point_number)] = point


def put_checkers(track, player, point_number, count):
    point = get_point(track, player, point_number)
    if not point.is_empty():
        raise ValueError('point must be empty')
    set_point(track, player, point_number, Point(player, count))


def is_possible_target_in_track(track, player, point_number):
    return get_point(track, player, point_number).is_possible_target(player)


def get_start_point(track, player, point_number):
    point = get_point(track, player, point_number)
    if not point.is_possible_start(player):
        raise ValueError(f'{point} cannot be source for {player}')
    return point


def get_target_point(track, player, point_number):
    point = get_point(track, player, point_number)
    if not point.is_possible_target(player):
        raise ValueError(f'{point} already occupied by opponent')
    return point


def iter_track(track, player, max_count=TRACK_LENGTH, filter_for=None):
    if filter_for is None:
        filter_for = player
    
    if player == Player.WHITE:
        track = reversed(track)
    elif player != Player.BLACK:
        raise ValueError(f'illegal player {player}')
    
    return (
        (point_number, point)
        for point_number, point in enumerate(islice(track, max_count), 1)
        if point.player == filter_for
    )


def count_checkers_in_game(bar, track, player, point_count=TRACK_LENGTH):
    return (
        bar[player]
        + sum(
            point.count for _, point in iter_track(track, player, point_count)
        )
    )


def count_checkers_not_in_home(bar, track, player):
    return count_checkers_in_game(
        bar, track, player, TRACK_LENGTH - HOME_LENGTH
    )


def count_checkers_in_opponents_home(track, player):
    return sum(
        point.count
        for _, point in iter_track(
            track, get_opponent(player), HOME_LENGTH, player
        )
    )


@attrs
class Move:
    die_value = attrib()    
    start = attrib()
    target = attrib()
    
    def __attrs_post_init__(self):
        if self.start is None and self.target is None:
            raise ValueError('start and target are `None`')

    def is_bar_move(self):
        return self.start is None and self.target is not None

    def is_bear_off_move(self):
        return self.start is not None and self.target is None

    def is_track_move(self):
        return self.start is not None and self.target is not None

    def _execute_track_move(self, bar, track, player):
        if bar[player] != 0:
            raise ValueError('checker from bar must be moved first')
        
        remove_checker(track, player, self.start)
        add_checker(bar, track, player, self.target)

    def _execute_bar_move(self, bar, track, player):
        if bar[player] == 0:
            raise ValueError(f'no checkers on bar for {player}')
        
        bar[player] -= 1
        add_checker(bar, track, player, self.target)

    def _execute_bear_off_move(self, bar, track, player):
        if bar[player] != 0:
            raise ValueError('checker from bar must be moved first')    

        remove_checker(track, player, self.start)

    def execute(self, bar, track, player):
        # 
        # TODO Find a more object oriented solution, replacing the
        #   ``if`` cascade by object behaviour.
        # 
        if self.is_track_move():
            self._execute_track_move(bar, track, player)
        elif self.is_bar_move():
            self._execute_bar_move(bar, track, player)
        elif self.is_bear_off_move():
            self._execute_bear_off_move(bar, track, player)
        else:
            ValueError(f'unexpected move {self}')

    @classmethod
    def new_bar_move(cls, target_point_number):
        return cls(target_point_number, None, target_point_number)
        
    @classmethod
    def new_bear_off_move(cls, die_value, start_point_number):
        return cls(die_value, start_point_number, None)


def move_handle_blot(bar, player, target_point):
    if target_point.has_blot(player):
        bar[target_point.player] += 1
        target_point = Point()
    
    return target_point


def remove_checker(track, player, point_number):
    start_point = get_start_point(track, player, point_number)
    start_point = start_point.checkers_added(player, -1)
    set_point(track, player, point_number, start_point)


def add_checker(bar, track, player, point_number):
    target_point = get_target_point(track, player, point_number)
    target_point = move_handle_blot(bar, player, target_point)
    target_point = target_point.checkers_added(player, 1)
    set_point(track, player, point_number, target_point)


def iter_legal_bar_moves(track, player, dice_values):
    for die_value in dice_values:
        if is_possible_target_in_track(track, player, die_value):
            yield Move.new_bar_move(die_value)


def iter_legal_track_moves(bar, track, player, dice_values):
    all_in_home = count_checkers_not_in_home(bar, track, player) == 0
    for die_value in dice_values:
        for point_number, point in iter_track(track, player):
            assert point.is_possible_start(player), point
            target_point_number = point_number + die_value
            
            if target_point_number > len(track):
                move = Move.new_bear_off_move(die_value, point_number)
            else:
                move = Move(die_value, point_number, target_point_number)
            
            if (
                all_in_home and move.is_bear_off_move()
                or (
                    move.is_track_move()
                    and is_possible_target_in_track(track, player, move.target)
                )
            ):
                yield move


def iter_legal_moves(bar, track, player, dice_values):
    dice_values = set(dice_values)
    if bar[player] > 0:
        yield from iter_legal_bar_moves(track, player, dice_values)
    else:
        yield from iter_legal_track_moves(bar, track, player, dice_values)


def check_for_simple_win(bar, track, player):
    return count_checkers_in_game(bar, track, player) == 0


def check_for_gammon(bar, track, player):
    return (
        count_checkers_in_game(bar, track, get_opponent(player))
        == CHECKERS_PER_PLAYER
    )


def check_for_backgammon(bar, track, player):
    return (
        bar[get_opponent(player)] > 0
        or count_checkers_in_opponents_home(track, get_opponent(player)) > 0
    )


def get_win_type(bar, track, player):
    result = Win.NONE
    
    for check_func, win_type in [
        (check_for_simple_win, Win.NORMAL),
        (check_for_gammon, Win.GAMMON),
        (check_for_backgammon, Win.BACKGAMMON),
    ]:
        if check_func(bar, track, player):
            result = win_type
        else:
            break
    
    return result


def bar_to_string(bar):
    return ', '.join(
        '{}: {}'.format(PLAYER_TO_CHARACTER[player], bar[player])
        for player in Player
    )


def print_game_state(bar, track, player):
    time.sleep(0.5)
    print('\033[2J\033[1;1H', end='')  # ANSI code sequence to clear the screen.
    
    half_length = len(track) // 2
    for track_half in track[:half_length], reversed(track[half_length:]):
        print('|'.join(map(str, track_half)))
    print('{} | Bar: {}\n'.format(player, bar_to_string(bar)))


def play_game():
    bar = dict.fromkeys(Player, 0)
    track = create_track()
    players = cycle(Player)
    
    if random.randint(0, 1) == 1:
        next(players)

    for player in players:
        print_game_state(bar, track, player)
        
        dice_values = roll_dice()
        while dice_values:
            legal_moves = list(
                iter_legal_moves(bar, track, player, dice_values)
            )
            if not legal_moves:
                break
            
            move = random.choice(legal_moves)
            move.execute(bar, track, player)
            dice_values.remove(move.die_value)
            
            win_type = get_win_type(bar, track, player)
            if win_type != Win.NONE:
                return player, win_type

            
def main():
    winner, win_type = play_game()
    print('End', winner, win_type)


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Die Liste `track` und das Wörterbuch `bar` in eigene Datentypen gekapselt. Nun sind die einfachsten/offensichtlichsten Klassen umgesetzt. Nun kann man als nächsten Schritt schauen welche Argumente herumgereicht werden und ob/welche in in einer oder mehreren Klassen zusammengefasst werden können.

Code: Alles auswählen

#!/usr/bin/env python3
from enum import Enum
from itertools import cycle, islice
import random
import time

from attr import attrib, attrs

TRACK_LENGTH = 24
HOME_LENGTH = 6
#:
#: Pairs of point number and count of checkers for the setup of the game.
#: 
SETUP = [(1, 2), (12, 5), (17, 3), (19, 5)]
CHECKERS_PER_PLAYER = sum(count for _, count in SETUP)

Player = Enum('Player', 'BLACK WHITE')
PLAYER_TO_CHARACTER = {
    None: ' ',
    Player.BLACK: 'B',
    Player.WHITE: 'W',
}

Win = Enum('Win', 'NONE NORMAL GAMMON BACKGAMMON')


def roll_dice():
    result = [random.randint(1, 6), random.randint(1, 6)]
    if result[0] == result[1]:
        result *= 2
    return result


def get_opponent(player):
    if player == Player.WHITE:
        return Player.BLACK
    if player == Player.BLACK:
        return Player.WHITE
    
    raise ValueError(f'{player} has no opposing value')


@attrs(frozen=True)
class Point:
    player = attrib(default=None)
    count = attrib(default=0)

    def __str__(self):
        return (
            PLAYER_TO_CHARACTER[self.player]
            + (format(self.count, '2d') if self.count else '  ')
        )

    def __attrs_post_init__(self):
        if self.count < 0:
            raise ValueError('count must be positive')
        if self.count == 0 and self.player is not None:
            raise ValueError('empty point cannot have a player')
        if self.count != 0 and self.player is None:
            raise ValueError('non empty point must have a player')    

    def is_empty(self):
        return self.player is None and self.count == 0

    def has_blot(self, player):
        return self.player == get_opponent(player) and self.count == 1

    def is_possible_start(self, player):
        return self.player == player and self.count > 0

    def is_possible_target(self, player):
        return (
            self.player == player or self.is_empty() or self.has_blot(player)
        )

    def checker_added(self, player):
        return Point(player, self.count + 1)
    
    def checker_removed(self, player):
        return Point(None if self.count == 1 else player, self.count - 1)
            

class Track:
    
    def __init__(self):
        self.points = [Point()] * TRACK_LENGTH
        for player in Player:
            for point_number, count in SETUP:
                self._put_checkers(player, point_number, count)

    def __len__(self):
        return len(self.points)
    
    def __iter__(self):
        return iter(self.points)
    
    def __reversed__(self):
        return reversed(self.points)

    def __str__(self):
        lines = list()
        half_length = len(self) // 2
        for track_half in [
            self.points[:half_length], reversed(self.points[half_length:])
        ]:
            lines.append('|'.join(map(str, track_half)))
        return '\n'.join(lines)

    def validate_point_number(self, point_number):
        if not 0 < point_number <= len(self):
            raise ValueError('point_number out of range')

    def reverse_point_number(self, point_number):
        self.validate_point_number(point_number)
        return len(self) - point_number + 1

    def _point_number_to_index(self, player, point_number):
        self.validate_point_number(point_number)
        
        if player == Player.BLACK:
            return point_number - 1
        if player == Player.WHITE:
            return self.reverse_point_number(point_number) - 1
        
        raise ValueError(f'illegal player {player}')

    def get_point(self, player, point_number):
        return self.points[self._point_number_to_index(player, point_number)]

    def set_point(self, player, point_number, point):
        self.points[self._point_number_to_index(player, point_number)] = point

    def _put_checkers(self, player, point_number, count):
        point = self.get_point(player, point_number)
        if not point.is_empty():
            raise ValueError('point must be empty')
        self.set_point(player, point_number, Point(player, count))

    def is_possible_target(self, player, point_number):
        return self.get_point(player, point_number).is_possible_target(player)

    def get_start_point(self, player, point_number):
        point = self.get_point(player, point_number)
        if not point.is_possible_start(player):
            raise ValueError(f'{point} cannot be source for {player}')
        return point

    def get_target_point(self, player, point_number):
        point = self.get_point(player, point_number)
        if not point.is_possible_target(player):
            raise ValueError(f'{point} already occupied by opponent')
        return point

    def iter_points(self, player, max_count=TRACK_LENGTH, filter_for=None):
        if filter_for is None:
            filter_for = player
        track = self
        if player == Player.WHITE:
            track = reversed(track)
        elif player != Player.BLACK:
            raise ValueError(f'illegal player {player}')
        
        return (
            (point_number, point)
            for point_number, point in enumerate(islice(track, max_count), 1)
            if point.player == filter_for
        )
    
    def count_checkers(self, player, point_count=TRACK_LENGTH):
        return sum(
            point.count for _, point in self.iter_points(player, point_count)
        )


def count_checkers_in_game(bar, track, player, point_count=TRACK_LENGTH):
    return (
        bar.get_checker_count(player)
        + track.count_checkers(player, point_count)
    )


def count_checkers_not_in_home(bar, track, player):
    return count_checkers_in_game(
        bar, track, player, TRACK_LENGTH - HOME_LENGTH
    )


def count_checkers_in_opponents_home(track, player):
    return sum(
        point.count
        for _, point in track.iter_points(
            get_opponent(player), HOME_LENGTH, player
        )
    )


@attrs
class Move:
    die_value = attrib()    
    start = attrib()
    target = attrib()
    
    def __attrs_post_init__(self):
        if self.start is None and self.target is None:
            raise ValueError('start and target are `None`')

    def is_bar_move(self):
        return self.start is None and self.target is not None

    def is_bear_off_move(self):
        return self.start is not None and self.target is None

    def is_track_move(self):
        return self.start is not None and self.target is not None

    def _execute_track_move(self, bar, track, player):
        if bar.get_checker_count(player) != 0:
            raise ValueError('checker from bar must be moved first')
        
        remove_checker(track, player, self.start)
        add_checker(bar, track, player, self.target)

    def _execute_bar_move(self, bar, track, player):
        if bar.get_checker_count(player) == 0:
            raise ValueError(f'no checkers on bar for {player}')
        
        bar.remove_checker(player)
        add_checker(bar, track, player, self.target)

    def _execute_bear_off_move(self, bar, track, player):
        if bar.get_checker_count(player) != 0:
            raise ValueError('checker from bar must be moved first')    

        remove_checker(track, player, self.start)

    def execute(self, bar, track, player):
        # 
        # TODO Find a more object oriented solution, replacing the
        #   ``if`` cascade by object behaviour.
        # 
        if self.is_track_move():
            self._execute_track_move(bar, track, player)
        elif self.is_bar_move():
            self._execute_bar_move(bar, track, player)
        elif self.is_bear_off_move():
            self._execute_bear_off_move(bar, track, player)
        else:
            ValueError(f'unexpected move {self}')

    @classmethod
    def new_bar_move(cls, target_point_number):
        return cls(target_point_number, None, target_point_number)
        
    @classmethod
    def new_bear_off_move(cls, die_value, start_point_number):
        return cls(die_value, start_point_number, None)


class Bar:
    
    def __init__(self):
        self.player2checker_count = dict.fromkeys(Player, 0)

    def __str__(self):
        return ', '.join(
            '{}: {}'.format(
                PLAYER_TO_CHARACTER[player], self.get_checker_count(player)
            )
            for player in Player
        )
    
    def get_checker_count(self, player):
        return self.player2checker_count[player]
    
    def is_possible_start(self, player):
        return self.get_checker_count(player) > 0
    
    def add_checker(self, player):
        self.player2checker_count[player] += 1
    
    def remove_checker(self, player):
        if self.player2checker_count[player] <= 0:
            raise ValueError('number of checkers cannot be negative')
        self.player2checker_count[player] -= 1


def move_handle_blot(bar, player, target_point):
    if target_point.has_blot(player):
        bar.add_checker(target_point.player)
        target_point = Point()
    
    return target_point


def remove_checker(track, player, point_number):
    start_point = track.get_start_point(player, point_number)
    start_point = start_point.checker_removed(player)
    track.set_point(player, point_number, start_point)


def add_checker(bar, track, player, point_number):
    target_point = track.get_target_point(player, point_number)
    target_point = move_handle_blot(bar, player, target_point)
    target_point = target_point.checker_added(player)
    track.set_point(player, point_number, target_point)


def iter_legal_bar_moves(track, player, dice_values):
    for die_value in dice_values:
        if track.is_possible_target(player, die_value):
            yield Move.new_bar_move(die_value)


def iter_legal_track_moves(bar, track, player, dice_values):
    all_in_home = count_checkers_not_in_home(bar, track, player) == 0
    for die_value in dice_values:
        for point_number, point in track.iter_points(player):
            assert point.is_possible_start(player), point
            target_point_number = point_number + die_value
            
            if target_point_number > len(track):
                move = Move.new_bear_off_move(die_value, point_number)
            else:
                move = Move(die_value, point_number, target_point_number)
            
            if (
                all_in_home and move.is_bear_off_move()
                or (
                    move.is_track_move()
                    and track.is_possible_target(player, move.target)
                )
            ):
                yield move


def iter_legal_moves(bar, track, player, dice_values):
    dice_values = set(dice_values)
    if bar.is_possible_start(player):
        yield from iter_legal_bar_moves(track, player, dice_values)
    else:
        yield from iter_legal_track_moves(bar, track, player, dice_values)


def check_for_simple_win(bar, track, player):
    return count_checkers_in_game(bar, track, player) == 0


def check_for_gammon(bar, track, player):
    return (
        count_checkers_in_game(bar, track, get_opponent(player))
        == CHECKERS_PER_PLAYER
    )


def check_for_backgammon(bar, track, player):
    opponent = get_opponent(player)
    return (
        bar.get_checker_count(opponent) > 0
        or count_checkers_in_opponents_home(track, opponent) > 0
    )


def get_win_type(bar, track, player):
    result = Win.NONE
    
    for check_func, win_type in [
        (check_for_simple_win, Win.NORMAL),
        (check_for_gammon, Win.GAMMON),
        (check_for_backgammon, Win.BACKGAMMON),
    ]:
        if check_func(bar, track, player):
            result = win_type
        else:
            break
    
    return result


def print_game_state(bar, track, player):
    time.sleep(0.5)
    print('\033[2J\033[1;1H', end='')  # ANSI code sequence to clear the screen.
    print(track)
    print(f'{player} | Bar: {bar}\n')


def play_game():
    bar = Bar()
    track = Track()
    players = cycle(Player)
    
    if random.randint(0, 1) == 1:
        next(players)

    for player in players:
        print_game_state(bar, track, player)
        
        dice_values = roll_dice()
        while dice_values:
            legal_moves = list(
                iter_legal_moves(bar, track, player, dice_values)
            )
            if not legal_moves:
                break
            
            move = random.choice(legal_moves)
            move.execute(bar, track, player)
            dice_values.remove(move.die_value)
            
            win_type = get_win_type(bar, track, player)
            if win_type != Win.NONE:
                return player, win_type

            
def main():
    winner, win_type = play_game()
    print('End', winner, win_type)


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@__blackjack__: Dein Code sieht für mich ziemlich fortgeschritten aus. Einige der unbekannten Sachen konnte ich mit Google und Doku einigermaßen verstehen, aber anderes erschließt sich mir nicht.
  1. Welche Art von Zuweisung wird mit `attrib()` ohne und mit Argumenten `attrib(default=None)` bzw. `attrib(default=0)` durchgeführt?
  2. Warum benötigt man für die Zuweisung von `attrib()` den Dekorator `@attrs`?
Gruß
Atalanttore
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: Ad 1.: Die Frage verstehe ich nicht so wirklich. Was meinst Du mit „Art von Zuweisung“? Wenn man keine Argumente an `attrib()` übergibt, dann hat die Funktion Defaultwerte die dann genommen werden. Wenn man Beispielsweise nichts für `default` übergibt, dann ist der Defaultwert `attr.NOTHING`, damit die `__init__()` später erkennen kann, dass nichts übergeben wurde und dann meckern kann das nichts übergeben wurde.

Ad 2.: Mit `attrib()` werden ja nur Attribute auf der Klasse erzeugt. Irgendwie muss man ja von da zu den erzeugten Methoden kommen. Und das macht der Klassendekorator. Der geht durch die Klassenattribute und erzeugt die ganzen Methoden auf der Klasse die dann dafür sorgen das es die Attribute auf den Objekten gibt und das die vergleich- und hashbar sind, eine nette `repr()` haben, und so weiter.

Man hätte das auch mit einer Basisklasse oder einer Metaklasse lösen können, aber als Dekorator ist es flexibler. Zum Beispiel wenn man es mit einer Klasse verwenden möchte, wo man schon ähnliche ”Magie” verwendet wie bei den meisten ORMs.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@__blackjack__:
Zu 2. Also der Dekorator `@attrs` liest die Klassenattribute ein und führt dann alle Methoden in der Klasse aus, damit die Klassenattribute auch Werte bekommen. Ist das so einigermaßen richtig verstanden?

Gruß
Atalanttore
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein. Der dekorator verarbeitet die in der KLASSE deklarierten Attribute, und fügt der Klasse dann neue Methoden hinzu. Das Schlüsselwort ist hinzufügen. Nicht ausführen. Es ist eine Modifikation der Klasse, so als ob man eben setter und getter für Properties geschrieben hätte.
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

Warum muss man Methoden, die innerhalb einer Klasse definiert sind, anschließend noch zur Klasse hinzufügen?

Gruß
Atalanttore
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Weil es keine Methoden sind. Wie sieht denn eine solche Deklaration aus? Sieht

player = attrib(...)

aus wie eine Methode?
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

`player = attrib(...)` sieht aus wie eine „Art von Zuweisung“. :wink:

Also sorgt der Dekorator `@attrs` dafür, dass die den Attributen (u.a. `player`) zugewiesenen `attrib()`-Funktionen zu Methoden der jeweiligen Klasse werden. Stimmt das so?

Gruß
Atalanttore
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

attrib() sind auch keine Funktionen, sondern Instanzen.

Code: Alles auswählen

def foobar():
    pass
ist eine Funktion.

Code: Alles auswählen

a = foobar()
ist *keine* Funktion, sondern a enthaelt nun, was der AUFRUF der Funktion foobar zurueckgeliefert hat. In diesem Fall None. Im Fall einer Klasse foobar eine Instanz. Oder was auch immer foobar macht. Nun kann ich natuerlich in foobar eine Funktion zurueckgeben.... aber das ist in diesem Fall ja eher unwahrscheinlich.

Das Objekt, das von attrib zurueckgeben wird, beschreibt nunmal was ich mir von diesem Attribut verspreche. Den Defaultwert, und ggf.welchen Typ es hat, etc... da hilft einem eine Funktion ja herzlich wenig bei. Was das ist kannst du selbst ausprobieren, aber es wird einfach wohl eine Instanz eben eines attrib-Objektes sein.

Aber ja, DANN wertet der Dekorator diese Deklarationen aus, und erzeugt entsprechend Methoden und Properties. Du kannst auch einfach mal in den Code schauen. Der ist ja open source.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Einfach mal live aufrufen bringt das hier:

Code: Alles auswählen

In [156]: a = attr.attrib(default=42)

In [157]: a
Out[157]: _CountingAttr(counter=14, _default=42, repr=True, cmp=True, hash=None, init=True, metadata={})
Die Funktion `attrib()` liefert also ein `_CountintAttr`-Objekt wo die Informationen drin stehen die der Klassendekorator dann später verwendet.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

Vielen Dank für die Erklärungen. So ganz ist es mir aber immer noch nicht klar:
__deets__ hat geschrieben: Montag 25. Februar 2019, 16:22 attrib() sind auch keine Funktionen, sondern Instanzen.
__blackjack__ hat geschrieben: Montag 25. Februar 2019, 17:52 Die Funktion `attrib()` liefert also ein `_CountintAttr`-Objekt wo die Informationen drin stehen die der Klassendekorator dann später verwendet.
:?

Gruß
Atalanttore
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du schreibst das die "zugewiesenen `attrib()`-Funktionen" ausgewertet werden. Ist ein _CountintAttr-Objekt eine Funktion? Nein. Es ist eine Instanz. Wo auch immer du da jetzt einen Widerspruch konstruierst... es ist keiner. Ich sage ja sogar explizit, das ich nicht weiss, ob attrib eine Klasse ist, oder eine Funktion, die Objekte erzeugt. Und __blackjack__ hat dann belegt, dass es halt das letztere ist.
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

Die Theorie dahinter habe ich dann verstanden.

In der Praxis hatte ich noch nie das Bedürfnis nach einer Funktion, die ein Objekt zurückliefert, mit dem ein Klassendekorator anschließend Methoden und Properties zu einer Klasse hinzufügt.

Gruß
Atalanttore
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich hatte bis vor kurzem noch nie das Beduerfnis, einen 3D-Druck anzufertigen. Hat sich durch ein Projekt an dem ich gearbeitet habe geaendert. Die wenigsten Beduerfnisse die man so hat sind von Anfang an in einem vorhanden. Aber nur in der Hoehle liegen, Mammutkeule mampfen und sich fortpflanzen reicht halt den wenigsten auf Dauer ;)
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: Ich selbst habe das auch noch nicht gemacht, aber schon mehrere Bibliotheken verwendet die das machen. Ist halt praktisch für ”deklarative” Sachen wie eben hier oder bei ORMs oder Validierungs-/Konvertierungsbibliotheken. Wobei es nicht immer ein Klassendekorator ist. Ableiten von einer Klasse beziehungsweise eine Metaklasse sind ja auch Möglichkeiten das Verarbeiten der ”speziellen” Klassenattribute zu realisieren.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

__blackjack__ hat geschrieben: Freitag 22. Februar 2019, 12:52 Man hätte das auch mit einer Basisklasse oder einer Metaklasse lösen können, aber als Dekorator ist es flexibler. Zum Beispiel wenn man es mit einer Klasse verwenden möchte, wo man schon ähnliche ”Magie” verwendet wie bei den meisten ORMs.
Verwendet man `attrib()`-Funktionen immer dann, wenn man etwas einbindet, wo `attrib()`-Funktionen auch verwendet werden?

Nebenfrage: Sollte der eigene Code grundsätzlich ähnlich aufgebaut sein wie der Code eingebundener Bibliotheken?

Gruß
Atalanttore
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: Die erste Frage verstehe ich nicht. Was sind `attrib()`-Funktionen? `attr.attrib()` ist letztendlich eine ganz normale Funktion.

Bei der Nebenfrage würde ich sagen nein, denn eine eingebundene Bibliothek kann ja auch ganz schlecht aufgebaut sein. Das muss man ja nicht übernehmen für den eigenen Code.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten