Enseigner les décorateurs Python

Identifier et expliquer les décorateurs dans le code.

Vous pouvez interroger un modèle directement et tester les résultats renvoyés lors de l'utilisation de différentes valeurs de paramètre en utilisant la console Cloud ou en appelant directement l'API Vertex AI.

Instructions système

Vous êtes un développeur Python de premier plan et un tuteur spécialisé dans l'enseignement de Python et des concepts de programmation fondamentaux aux nouveaux développeurs. Vous expliquez chaque concept clairement et en termes simples, en décomposant les idées complexes étape par étape.

Requête Freeform

Je suis débutant en Python et j'essaie d'apprendre les fonctionnalités de syntaxe Python illustrées dans le code suivant. Pouvez-vous identifier les trois instructions @, puis expliquer leur signification ? Lorsque vous expliquez les choses, vous devez générer des exemples de code pour illustrer leur fonctionnement. N'incluez pas de blocs de code qui ne sont pas des exemples de code que vous avez générés.

AdventurerGame.py :

from dataclasses import dataclass, field
from typing import List
from enum import Enum
from collections import namedtuple

class LootCategory(Enum):
    WEAPON = "Weapon"
    ARMOR = "Armor"
    POTION = "Potion"

LootAttributes = namedtuple('LootAttributes', ['attack_boost', 'defense_boost'])

@dataclass
class Loot:
    name: str
    category: LootCategory
    attributes: LootAttributes = LootAttributes(attack_boost=0, defense_boost=0)

class LootForge:
    @staticmethod
    def craft_weapon(name: str, attack_boost: int) -> Loot:
        return Loot(name, LootCategory.WEAPON, LootAttributes(attack_boost, 0))

    @staticmethod
    def craft_armor(name: str, defense_boost: int) -> Loot:
        return Loot(name, LootCategory.ARMOR, LootAttributes(0, defense_boost))

    @staticmethod
    def brew_potion(name: str) -> Loot:
        return Loot(name, LootCategory.POTION)

@dataclass
class Ability:
    name: str
    impact: int
    energy_cost: int

class AbilityTome:
    @staticmethod
    def create_offensive_ability(name: str, impact: int, energy_cost: int) -> Ability:
        return Ability(name, impact, energy_cost)

    @staticmethod
    def create_healing_ability(name: str, healing: int, energy_cost: int) -> Ability:
        return Ability(name, -healing, energy_cost)

@dataclass
class Adventurer:
    name: str
    health: int
    stamina: int
    base_damage: int
    base_resilience: int
    backpack: List[Loot] = field(default_factory=list)
    abilities: List[Ability] = field(default_factory=list)

    @property
    def calculate_attack_damage(self) -> int:
        return self.base_damage + sum(item.attributes.attack_boost for item in self.backpack)

    @property
    def total_resilience(self) -> int:
        return self.base_resilience + sum(item.attributes.defense_boost for item in self.backpack)

class BackpackOrganizer:
    @staticmethod
    def add_loot(adventurer: Adventurer, loot: Loot):
        adventurer.backpack.append(loot)

class AbilityMaster:
    @staticmethod
    def teach_ability(adventurer: Adventurer, ability: Ability):
        adventurer.abilities.append(ability)

    @staticmethod
    def use_ability(user: Adventurer, ability_name: str, target: Adventurer) -> str:
        ability = next((a for a in user.abilities if a.name == ability_name), None)
        if ability and user.stamina >= ability.energy_cost:
            user.stamina -= ability.energy_cost
            target.health -= ability.impact
            return f"{user.name} invokes {ability_name} on {target.name} with an impact of {ability.impact}!"
        return f"{user.name} attempted to invoke {ability_name} but failed due to insufficient energy or lack of knowledge."

class BattleController:
    @staticmethod
    def execute_attack(attacker: Adventurer, defender: Adventurer) -> str:
        impact = max(0, attacker.calculate_attack_damage - defender.total_resilience)
        defender.health -= impact
        return f"{attacker.name} strikes {defender.name} and inflicts {impact} damage!"

class AdventurerCrafter:
    @staticmethod
    def create_adventurer(name: str, health: int, stamina: int, base_damage: int, base_resilience: int) -> Adventurer:
        return Adventurer(name, health, stamina, base_damage, base_resilience)

class AdventurerLogger:
    @staticmethod
    def log_adventurer(adventurer: Adventurer) -> str:
        loot = ', '.join(item.name for item in adventurer.backpack) or "No loot"
        abilities = ', '.join(ability.name for ability in adventurer.abilities) or "No abilities"
        return (f"Adventurer({adventurer.name}, Health: {adventurer.health}, Stamina: {adventurer.stamina}, "
                f"Damage: {adventurer.calculate_attack_damage}, Resilience: {adventurer.total_resilience}, "
                f"Loot: [{loot}], Abilities: [{abilities}])")

if __name__ == "__main__":
    champion = AdventurerCrafter.create_adventurer(name="Champion", health=100, stamina=100, base_damage=10, base_resilience=10)
    rogue = AdventurerCrafter.create_adventurer(name="Rogue", health=65, stamina=80, base_damage=35, base_resilience=2)

    sword = LootForge.craft_weapon("Sword", 10)
    shield = LootForge.craft_armor("Shield", 10)
    fireball = AbilityTome.create_offensive_ability("Fireball", 20, 10)
    heal = AbilityTome.create_healing_ability("Heal", 15, 8)

    BackpackOrganizer.add_loot(champion, sword)
    AbilityMaster.teach_ability(champion, fireball)
    BackpackOrganizer.add_loot(rogue, shield)
    AbilityMaster.teach_ability(rogue, heal)

    print(AdventurerLogger.log_adventurer(champion))
    print(AdventurerLogger.log_adventurer(rogue))

    print(BattleController.execute_attack(champion, rogue))
    print(AbilityMaster.use_ability(rogue, "Heal", rogue))
    print(AbilityMaster.use_ability(champion, "Fireball", rogue))

Réponse

Les trois symboles @ du code fourni représentent des décorateurs en Python. Examinons-les :

  1. @dataclass : ce décorateur permet de générer automatiquement du code récurrent pour les classes. Il simplifie la création de classes qui stockent principalement des données. Il ajoute automatiquement des méthodes telles que __init__, __repr__ et d'autres, en fonction des champs que vous définissez dans la classe.

    from dataclasses import dataclass
    
    @dataclass
    class Dog:
        name: str
        breed: str
        age: int
    
    my_dog = Dog("Buddy", "Golden Retriever", 3)
    print(my_dog)  # Output: Dog(name='Buddy', breed='Golden Retriever', age=3)
    

    Sans @dataclass, il faudrait écrire manuellement la méthode __init__ et éventuellement d'autres :

    class Dog:
        def __init__(self, name, breed, age):
            self.name = name
            self.breed = breed
            self.age = age
    
    my_dog = Dog("Buddy", "Golden Retriever", 3)
    print(my_dog) # Output: <__main__.Dog object at 0x...> (less informative)
    
  2. @staticmethod : ce décorateur définit une méthode liée à la classe et non à l'instance de la classe. Il n'a pas accès à self (l'instance) ou à cls (la classe elle-même) de manière implicite. Les méthodes statiques sont essentiellement des fonctions utilitaires qui appartiennent logiquement à une classe, mais qui n'ont pas besoin d'interagir avec l'état de la classe.

    class MathHelper:
        @staticmethod
        def add(x, y):
            return x + y
    
    result = MathHelper.add(5, 3)  # No need to create a MathHelper object
    print(result)  # Output: 8
    

    Si vous essayez d'appeler add en tant que méthode d'instance, une erreur s'affiche, car elle n'utilise pas self :

    class MathHelper:
        def add(self, x, y): # Incorrect - instance method needs self
            return x + y
    
    helper = MathHelper()
    result = helper.add(5,3) # This would work, but is not a static method
    
  3. @property : ce décorateur transforme une méthode en attribut en lecture seule. Il vous permet d'accéder à une méthode à l'aide de la syntaxe d'attribut (par exemple, object.attribute), mais il exécute le code de la méthode en arrière-plan. Cette option est utile pour les attributs calculés ou les attributs qui nécessitent un traitement avant d'être renvoyés.

    class Circle:
        def __init__(self, radius):
            self._radius = radius  # Note the underscore - convention for internal attributes
    
        @property
        def area(self):
            return 3.14159 * self._radius * self._radius
    
    my_circle = Circle(5)
    print(my_circle.area)  # Output: 78.53975 (no parentheses needed)
    

    Sans le décorateur @property, vous devez appeler area en tant que méthode :

    class Circle:
        def __init__(self, radius):
            self._radius = radius
    
        def area(self):
            return 3.14159 * self._radius * self._radius
    
    my_circle = Circle(5)
    print(my_circle.area()) # Output: 78.53975 (parentheses are needed)
    
Modèle : gemini-1.5-flash-002
Température : 0,2
Nombre maximal de jetons de sortie : 8 192
TopK : 40,0
TopP : 0,95