Использование __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.

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

4 ответа

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

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потому что всю свою продуктивность он получает от делегирования до элементов списка. Не использовать in dir() чтобы решить, имеет ли вещь атрибут, используйте hasattr,

Этот код ужасен и на самом деле совсем не 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

Явное лучше, чем неявное. Также состав, как правило, лучше, чем наследство.

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

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

Они могут быть объединены и временная переменная func устранены:

getattr(product, name)(arg)

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

def __getattribute__(self, attr):
  return (
    lambda *args: self.sum_with(attr, *args) 
    if hasattr(Product, attr)
    else super(Package, self).__getattribute__(attr)
  )
Другие вопросы по тегам