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_code
i ż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?