Использование __getattribute__ или __getattr__ для вызова методов в Python - PullRequest
10 голосов
/ 30 августа 2011

Я пытаюсь создать подкласс, который действует как список пользовательских классов.Однако я хочу, чтобы список наследовал методы и атрибуты родительского класса и возвращал сумму количеств каждого элемента.Я пытаюсь сделать это, используя метод __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 ]

10 голосов
/ 30 августа 2011

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

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

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

4 голосов
/ 30 августа 2011

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

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

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

getattr(product, name)(arg)
2 голосов
/ 31 августа 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)
  )
...