Использование __getattribute__ или __getattr__ для вызова методов в Python

Я пытаюсь создать подкласс, который действует как список пользовательских классов. Однако я хочу, чтобы список наследовал методы и атрибуты родительского класса и возвращал сумму количества каждого элемента. Я пытаюсь сделать это с помощью метода __getattribute__, но не могу понять, как передать аргументы вызываемым атрибутам. Сильно упрощенный код ниже должен объяснять более ясно.

class Product:
    def __init__(self,price,quantity):
        self.price=price
        self.quantity=quantity
    def get_total_price(self,tax_rate):
        return self.price*self.quantity*(1+tax_rate)

class Package(Product,list):
    def __init__(self,*args):
        list.__init__(self,args)
    def __getattribute__(self,*args):
        name = args[0]
    # the only argument passed is the name...
        if name in dir(self[0]):
            tot = 0
            for product in self:
                tot += getattr(product,name)#(need some way to pass the argument)
            return sum
        else:
            list.__getattribute__(self,*args)

p1 = Product(2,4)
p2 = Product(1,6)

print p1.get_total_price(0.1) # returns 8.8
print p2.get_total_price(0.1) # returns 6.6

pkg = Package(p1,p2)
print pkg.get_total_price(0.1) #desired output is 15.4.

На самом деле у меня есть много методов родительского класса, которые должны вызываться. Я понимаю, что я мог бы вручную переопределить каждый для подкласса, подобного списку, но я хотел бы избежать этого, поскольку в будущем к родительскому классу может быть добавлено больше методов, и мне нужна динамическая система. Любые советы или предложения приветствуются. Спасибо!


person AJ Medford    schedule 30.08.2011    source источник
comment
это так неправильно на многих уровнях. что вы на самом деле пытаетесь сделать?   -  person    schedule 30.08.2011


Ответы (4)


Этот код ужасен и на самом деле совсем не Pythonic. У вас нет возможности передать дополнительный аргумент в __getattribute__, поэтому вам не следует пытаться делать какую-либо неявную магию, подобную этой. Лучше было бы написать так:

class Product(object):
    def __init__(self, price, quantity):
        self.price    = price
        self.quantity = quantity

    def get_total_price(self, tax_rate):
        return self.price * self.quantity * (1 + tax_rate)

class Package(object):
    def __init__(self, *products):
        self.products = products

    def get_total_price(self, tax_rate):
        return sum(P.get_total_price(tax_rate) for P in self.products)

Если вам нужно, вы можете сделать оболочку более общей, например

class Package(object):
    def __init__(self, *products):
        self.products = products

    def sum_with(self, method, *args):
        return sum(getattr(P, method)(*args) for P in self.products)

    def get_total_price(self, tax_rate):
        return self.sum_with('get_total_price', tax_rate)

    def another_method(self, foo, bar):
        return self.sum_with('another_method', foo, bar)

    # or just use sum_with directly

Явное лучше неявного. Также композиция обычно лучше, чем наследование.

person Cat Plus Plus    schedule 30.08.2011
comment
Спасибо за совет. Я исправил проблему, используя композицию, как вы предложили - очень похоже на то, что предложил Нед. Я понимаю, что явное обычно лучше, но в реальном приложении класс Product имеет много методов, которые необходимо суммировать, и, что более важно, я могу добавить больше методов в будущем и хотел бы, чтобы все изменения автоматически фиксировались классом Package . Я не думаю, что есть явное решение для этого... - person AJ Medford; 02.09.2011

У вас есть несколько путаницы здесь:

1) __getattribute__ перехватывает доступ ко всем атрибутам, а это не то, что вам нужно. Вы хотите, чтобы ваш код вмешивался только в том случае, если реальный атрибут не существует, поэтому вам нужно __getattr__.

2) Ваш __getattribute__ вызывает метод для элементов списка, но он не должен выполнять реальную работу, он должен только возвращать вызываемую вещь. Помните, что в Python x.m(a) на самом деле состоит из двух шагов: сначала получить x.m, а затем вызвать эту штуку с аргументом a. Ваша функция должна выполнять только первый шаг, а не оба шага.

3) Я удивлен, что все методы, которые вам нужно переопределить, должны суммироваться. Действительно ли существует так много методов, что все они должны быть суммированы, чтобы сделать это стоящим?

Этот код работает, чтобы делать то, что вы хотите, но вы можете рассмотреть более явные подходы, как предлагают другие:

class Product:
    def __init__(self,price,quantity):
        self.price = price
        self.quantity = quantity

    def get_total_price(self,tax_rate):
        return self.price*self.quantity*(1+tax_rate)

class Package(list):
    def __init__(self,*args):
        list.__init__(self,args)

    def __getattr__(self,name):
        if hasattr(self[0], name):
            def fn(*args):
                tot = 0
                for product in self:
                    tot += getattr(product,name)(*args)
                return tot
            return fn
        else:
            raise AttributeError

Что следует отметить в этом коде: я сделал Package не производным от Product, потому что все его Product-ness он получает от делегирования элементам списка. Не используйте in dir(), чтобы решить, есть ли у вещи атрибут, используйте hasattr.

person Ned Batchelder    schedule 30.08.2011

Чтобы ответить на ваш непосредственный вопрос, вы вызываете функцию или метод, полученный с помощью getattr(), так же, как вы вызываете любую функцию: помещая аргументы, если они есть, в круглые скобки после ссылки на функцию. Тот факт, что ссылка на функцию исходит из getattr(), а не из доступа к атрибуту, не имеет никакого значения.

func   = getattr(product, name)
result = func(arg)

Их можно объединить и исключить временную переменную func:

getattr(product, name)(arg)
person kindall    schedule 30.08.2011

В дополнение к тому, что сказал Cat Plus Plus, если вы все равно действительно хотите вызывать магию (пожалуйста, не надо! При таком подходе вас ждет невероятно много тревожных сюрпризов на практике), вы можете проверить наличие атрибута в Product и динамически создайте оболочку sum_with:

def __getattribute__(self, attr):
  return (
    lambda *args: self.sum_with(attr, *args) 
    if hasattr(Product, attr)
    else super(Package, self).__getattribute__(attr)
  )
person Karl Knechtel    schedule 30.08.2011