Python: вызов метода члена экземпляра по имени с использованием __getattribute__ - PullRequest
2 голосов
/ 27 ноября 2009

У меня есть класс, который содержит объект-член. Я хотел бы вызывать методы члена, как если бы они были методами класса контейнера.

Следующий пример иллюстрирует (но в другом контексте), что я хотел бы сделать.

Предположим, у нас есть два класса, представляющих два разных вида товаров: Игрушки и Обувь . Каждый класс имеет два метода, скажем, weight() и description(), которые явно возвращают вес элемента и описание элемента.

Теперь я хочу разместить эти продукты в отдельных коробках - каждая коробка будет содержать либо один Toy, либо один Shoe.

Я хочу иметь возможность доступа к методам weight() и description(), однако я хочу, чтобы они были членами экземпляра Box. Кроме того, я не хочу специально реализовывать методы weight() и description() в классе Box, потому что я не хочу реализовывать больше методов в классе Box всякий раз, когда добавляется новый метод в игрушку и обувь.

Вот пример «конечного результата», который сделал бы меня счастливым:

# 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

Моя идея реализации заключается в следующем.

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)

Однако это не работает! Если я запускаю «нужный» пример, показанный в первом блоке кода, я получаю бесконечную рекурсию. Итак, как элегантный способ сделать это?

Обратите внимание, что Box имеет свой собственный параметр box_code и что в будущем я мог бы захотеть добавить новые методы к Toy и Shoes (например, промокод или что-то еще), не имея изменить класс Box.

Хорошо, ребята, я с нетерпением жду ваших ответов.


Обновление : ответ на проблему Спасибо Interjay и Майку Буру за помощь.

Мне просто нужно было заменить определение метода __getattribute__ в классе Box на __getattr__, и оно отлично работало, как это было предложено Interjay. Исправленное определение класса Box:

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)

Обновление : Завершение примера с использованием __setattr__

После того, как я исправил проблему с помощью метода getattr, я заметил, что у моих классов не было желаемого поведения, когда я хотел установить атрибут. Например, если я попытался:

box1.desc = 'plastic sword'

вместо изменения описания product1 (которое было инкапсулировано в box1), в box1 был создан новый элемент с именем "desc". Чтобы решить эту проблему, мне нужно было определить функцию __setattr__, как показано ниже:

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

Однако простое добавление этой функции создает рекурсивный вызов, потому что в __init__ я пытаюсь назначить self.product = product ', который вызывает функцию __setattr__, которая не находит члена self.product, поэтому вызывает функцию __setattr__ снова, рекурсивно до бесконечности.

Чтобы избежать этой проблемы, я должен изменить метод __init__, чтобы назначить нового члена, используя словарь класса. Полное определение класса Box таково:

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)

Теперь забавная вещь ... Если я сначала определю элемент self.product, как показано выше, программа работает нормально. Однако, если я попытаюсь сначала определить элемент self.box_code, программа снова столкнется с проблемой рекурсивного цикла. Кто-нибудь знает почему?

Ответы [ 3 ]

3 голосов
/ 27 ноября 2009

Вы можете использовать __getattr__ вместо __getattribute__, как было предложено. Это будет вызвано для любого доступа к атрибуту для атрибута, который не существует.

Если вы действительно хотите переопределить (почти) доступ к ВСЕМ атрибутам, продолжайте использовать __getattribute__ и определите его таким образом:

def __getattribute__(self, name):
    return getattr(object.__getattribute__(self, 'product'), name)
2 голосов
/ 27 ноября 2009

Используйте __getattr__ вместо __getattribute__. Если вы используете __getattribute__, вы получите бесконечную рекурсию при оценке self.product.

__getattr__ вызывается только тогда, когда атрибут не найден, поэтому не вызывает этого.

1 голос
/ 08 февраля 2013

Теперь забавная вещь ... Если я сначала определю элемент self.product, то как показано выше, программа работает нормально. Однако, если я попытаюсь определить сначала член self.box_code, программа переносит рекурсивный цикл проблема снова. Кто-нибудь знает почему?

Если вы сначала напишите self.box_code = box_code, вот что произойдет:

__setattr__ вызывается и пытается найти self.product, который не определен. Поскольку он не определен, __getattr__ вызывается для разрешения атрибута "product", но внутри __getattr__ есть еще один self.product, который превращает его в рекурсивный цикл.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...