10

我正在尝试创建一个充当自定义类列表的子类。但是,我希望列表继承父类的方法和属性,并返回每个项目的数量之和。我正在尝试使用该__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

4 回答 4

10

这段代码很糟糕,而且根本不是 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

显式优于隐式。组合通常也比继承好。

于 2011-08-30T18:28:08.980 回答
9

您在这里有几点困惑:

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.

于 2011-08-30T18:26:21.290 回答
4

要回答您的直接问题,您可以使用getattr()调用任何函数的相同方式调用检索到的函数或方法:通过将参数(如果有)放在对函数的引用之后的括号中。对函数的引用来自getattr()而不是属性访问这一事实没有任何区别。

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

这些可以组合起来并func消除临时变量:

getattr(product, name)(arg)
于 2011-08-30T18:26:32.460 回答
2

除了 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)
  )
于 2011-08-30T20:04:11.120 回答