Python: wywoływanie metody członka instancji według nazwy przy użyciu __getattribute__

Mam klasę zawierającą obiekt członkowski. Chciałbym wywołać metody członka tak, jakby były metodami klasy kontenera.

Poniższy przykład ilustruje (jednak w innym kontekście), co chciałbym zrobić.

Załóżmy, że mamy dwie klasy reprezentujące dwa różne rodzaje produktów:Zabawki iButy. Każda klasa ma dwie metody, powiedzmy,weight() idescription(), które zwracają bezwzględnie wagę przedmiotu i opis przedmiotu.

Teraz chcę umieścić te produkty indywidualniePudła - każde pole będzie zawierać pojedynczyToy albo singielShoe.

Chcę nadal mieć dostęp do metodweight() idescription(), jednak chcę, żeby byli członkamiBox instancja. Nie chcę też specjalnie implementować tych metodweight() idescription() wBox klasa, ponieważ nie chcę implementować więcej metod wBox klasa, gdy do zabawki i buta dodawana jest nowa metoda.

Oto przykład „wyniku końcowego”, który uszczęśliwi mnie:

# Define our products
product1 = Toy('plastic gun', 200)
product2 = Shoe('black leather shoes', 500)

# Now place them in boxes
box1 = Box(product1, box_code='A1234')
box2 = Box(product2, box_code='V4321')

# I want to know the contents of the boxes
box1.description()
box2.description()

# Desired results
>> plastic gun
>> black leather shoes

Mój pomysł wdrożenia jest taki.

class Product():
    def __init__(self, description, weight):
        self.desc = description
        self.wght = weight

    def description(self):
        return self.desc

    def weight(self):
        return self.weight

class Toy(Product):
    pass

class Shoe(Product):
    pass

class Box(object):
    def __init__(self, product, box_code):
        self.box_code = box_code
        self.product = product

    def __getattribute__(self, name):
        # Any method that is accessed in the Box instance should be replaced by
        # the correspondent method in self.product 
        return self.product.__getattribute__(name)

Jednak to nie działa! Jeśli uruchomię „pożądany” przykład pokazany w pierwszym bloku kodu, otrzymam nieskończoną rekursję. Jak więc wygląda ten elegancki sposób?

Zauważ, żeBox ma swój własny parametrbox_codei że w przyszłości mogę chcieć dodać nowe metodyToy iShoes (na przykład kod promocyjny lub cokolwiek innego) bez konieczności modyfikowania klasyBox.

Dobra chłopaki, z niecierpliwością czekam na wasze odpowiedzi.

Aktualizacja: Odpowiedz na problem Dzięki Interjay i Mike'owi Boerowi za pomoc.

Musiałem po prostu zastąpić definicję metody__getattribute__ w klasieBox przez__getattr__ i działało idealnie, jak sugeruje Interjay. Poprawiona definicja klasy Box to:

class Box(object):
    def __init__(self, product, box_code):
        self.box_code = box_code
        self.product = product

    def __getattr__(self, name):
        # Any method that is accessed in the Box instance should be replaced by
        # the correspondent method in self.product 
        return self.product.__getattribute__(name)

Aktualizacja: Uzupełnianie przykładu za pomocą__setattr__

Po poprawieniu problemu za pomocą metody getattr zauważyłem, że moje klasy nie miały pożądanego zachowania, gdy chciałem ustawić atrybut. Na przykład, jeśli spróbuję:

box1.desc = 'plastic sword'

zamiast zmiany opisu produktu1 (który został umieszczony w polu 1), w polu1 utworzono nowego członka o nazwie „desc”. Aby rozwiązać ten problem, musiałem zdefiniować__setattr__ funkcja, jak pokazano poniżej:

def __setattr__(self, name, value):
    setattr(self.product, name, value)

Jednak dodanie tej funkcji powoduje wywołanie rekurencyjne, ponieważ in__init__ Próbuję przypisaćself.product = product'który wywołuje funkcję__setattr__, który nie znajduje członkaself.product, więc wywołuje funkcję__setattr__ ponownie, rekurencyjnie ad infinitum.

Aby uniknąć tego problemu, muszę zmienić__init__ metoda przypisywania nowego członka za pomocą słownika klasy. Pełna definicja klasyBox jest wtedy:

class Box(object):
    def __init__(self, product, box_code):
        self.__dict__['product'] = product
        self.box_code = box_code

    def __getattr__(self, name):
        # Any method that is accessed in the Box instance should be replaced by
        # the correspondent method in self.product 
        return getattr(self.product, name)

    def __setattr__(self, name, value):
        setattr(self.product, name, value)

Teraz zabawna rzecz ... Jeśli najpierw zdefiniuję członkaself.product, jak pokazano powyżej, program działa normalnie. Jeśli jednak spróbuję najpierw zdefiniować członkaself.box_code, program ponownie ma problem z pętlą rekurencyjną. Czy ktoś wie dlaczego?

questionAnswers(3)

yourAnswerToTheQuestion