指向另一个对象的属性并添加自己的属性

时间:2011-09-22 19:04:08

标签: python oop design-patterns

假设我有一个班级:

class Car(object):
    def __init__(self, name, tank_size=10, mpg=30):
        self.name = name
        self.tank_size = tank_size
        self.mpg = mpg

我把我正在看的车列表放在一起:

cars = []
cars.append(Car("Toyota", 11, 29))
cars.append(Car("Ford", 15, 12))
cars.append(Car("Honda", 12, 25))

如果我为当前收藏夹指定一个名称(如果你愿意的话,将一个“指针”放入列表中):

my_current_fav = cars[1]

我可以轻松访问我当前最喜欢的属性:

my_current_fav.name       # Returns "Ford"
my_current_fav.tank_size  # Returns 15
my_current_fav.mpg        # Returns 12

这是我开始变得模糊的地方。我想仅为我当前的收藏提供额外的“计算”属性(让我们假设这些属性太“昂贵”,无法存储在原始列表中,并且更容易计算):

my_current_fav.range      # Would return tank_size * mpg = 180
                          # (if pointing to "Ford")

在我的情况下,我只是投降并添加'range'作为Car()的属性。但是,如果在每个Car()实例中存储'range'是昂贵但计算它很便宜呢?

我考虑过让'my_current_fav'成为Car()的一个子类,但我无法找到办法做到这一点,并且仍然能够将'my_current_favorite'简单地“指向”'汽车中的一个条目'list。

我还考虑使用装饰器来计算和返回'范围',但是无法找到一种方法来提供对属性'name','mpg'等的访问。

是否有一种优雅的方式指向列表'cars'中的任何项目,提供对所指向实例的属性的访问,以及提供在Car类中找不到的其他属性?

其他信息:
在阅读了许多答案之后,我看到我应该在原始问题中加入背景信息。我不会单独评论许多答案,而是将其他信息放在这里。

这个问题是对更复杂问题的简化。最初的问题涉及对现有库的修改。虽然使用方法调用而不是属性是一个很好的方法,但是改变

some_var = my_current_favorite.range

some_var = my_current_favorite.range()

在许多现有的用户脚本中都很昂贵。哎呀,追踪这些用户脚本会很贵。

同样,我目前对每辆车的计算范围的方法在Python术语中并不“昂贵”,但在运行时非常昂贵,因为真实世界的模拟需要慢速调用(嵌入式)OS。虽然Python本身并不慢,但这些调用是,所以我正在寻求最小化它们。

7 个答案:

答案 0 :(得分:6)

对于您的示例,这是最简单的做法,不会更改Car,并使用__getattr__尽可能少地进行更改:

class Car(object):
    def __init__(self, name, tank_size=10, mpg=30):
        self.name = name
        self.tank_size = tank_size
        self.mpg = mpg

class Favorite(object):
    def __init__(self, car):
        self.car = car
    def __getattr__(self, attr):
        return getattr(self.car, attr)
    @property
    def range(self):
        return self.mpg * self.tank_size

cars = []
cars.append(Car("Toyota", 11, 29))
cars.append(Car("Ford", 15, 12))
cars.append(Car("Honda", 12, 25))

my_current_fav = Favorite(cars[1])

print my_current_fav.range

Favorite实例上找不到的任何属性都将在Favorite实例car属性中查找,您在创建Favorite时设置该属性。

range的示例对于添加到Favorite的内容并不是特别好,因为它应该只是汽车的property,但为了简单起见我使用了它。

修改:请注意,此方法的一个好处是,如果您更改自己喜爱的汽车,并且未在Favorite上存储任何特定于汽车的汽车,则可以更改现有的收藏与另一辆车:

my_current_fav.car = cars[0] # or Car('whatever')

答案 1 :(得分:0)

如果你有权访问该类(听起来像你这样做),只需在类中创建一个函数。

def range(self):
    return self.tank_size * self.mpg

答案 2 :(得分:0)

关于您的示例,您可以使range成为可以按需计算的类Car的只读property。不需要额外的课程。

答案 3 :(得分:0)

为什么不创建一个方法:

class Car(object):
    def __init__(self, name, tank_size=10, mpg=30):
        self.name = name
        self.tank_size = tank_size
        self.mpg = mpg

    def range(self):
        return self.tank_size * self.mpg

答案 4 :(得分:0)

range()之类的声音应该是班级的一种方法。方法非常便宜 - 对象不存储它们。缺点是每次访问range的值时都会计算出来。

class Car(object):
   def __init__(self, name, tank_size=10, mpg=30):
       [AS ABOVE]
   def range(self):
      return self.tank_size * self.mpg

如果您希望它的行为类似于字段,即只计算一次,则可以在range方法中将值存储在对象中:

  def range(self):
     if not hasattr(self,'_rangeval'):
         self._rangeval = self.tank_size * self.mpg
     return self._rangeval

这利用了您可以在对象中动态创建字段的事实。

我不明白为什么当默认的计算值为300时,默认值为180.如果这种奇怪的行为很重要,则需要设置另一个标志以查看其他参数是否已初始化为默认值或不。

答案 5 :(得分:0)

我不确定我理解你要做什么,所以我将对你正在思考的内容进行一些不同的理解。

  

在我的情况下,我只是投降并添加'range'作为Car()的属性。但是,如果在每个Car()实例中存储'range'是昂贵但计算它很便宜呢?

首先,担心“昂贵”的事情通常不是Pythonic。如果真的很重要,那么大部分时间你都会使用低级语言。但一般来说,如果你想要计算而不是存储某些东西,那么自然的方法就是使用一种方法。将它添加到Car类。每个对象不花费,除非您基于每个对象显式替换该方法。

以下是它的工作原理:当您拨打a_car.range()时,首先会在range中查找a_car属性。如果在那里找不到,那么(在这里跳过许多详细信息!a_car的类被标识为Car,并在那里查找属性。您将range定义为Car方法,因此可以在那里找到它,并确定它是实际可调用的东西,因此它被调用。作为一种特殊的语法规则,a_car作为方法的第一个参数传递(所有这些都部分解释了为什么你需要有一个显式参数 - 按惯例命名为self - 对于Python中的方法,不同于许多其他语言具有隐式this)。

  

我考虑过让'my_current_fav'成为Car()的一个子类,但我无法找到办法做到这一点,并且仍然能够将'my_current_favorite'简单地“指向”'汽车中的一个条目'list。

您绝对可以将Car的子类存储在与一堆普通Car相同的列表中。没问题。哎呀,如果你愿意的话,你可以将Banana存储在同一个列表中,作为一堆Car s; Python是动态类型的,并不关心。它将确定什么样的对象在它变得相关的确切时刻。

您不能轻易做到的是使现有Car成为MyFavouriteCar。如果你创建了MyFavouriteCar引用的新my_favourite_car(请不要说“指向”,请对象引用是更高级别的抽象,与Java不同,没有“空指针”) Python - None是一个对象),然后你可以用它替换列表中的现有汽车,但它仍然是一个不同的对象(即使它以某种方式基于它替换的原始Car)。你可以这样设计,这无关紧要;或者你可以诉诸邪恶的hackery(我不会在这里解释);或者你可以(在你的情况下好多了)只为所有汽车提供功能,因为它真的是免费的

来自Python的禅宗:特殊情况不够特别

答案 6 :(得分:0)

你谈到my_current_fav是一个'指针' - 我只是想确保你意识到这一点,实际上,

my_current_fav = cars[1]

将名称绑定到cars[1] - 换句话说,my_current_fav is cars[1] == True。没有任何指点。如果你真的想要一个指针stlye,你可以做这样的事情:

class Favorite(object):
    def __init__(self, car_list, index):
        self.car_list = car_list
        self.index = index
    def __getattr__(self, attr):
        return getattr(self.car_list[self.index], attr)
    def __index__(self):
        return self.index
    @property
    def range(self):
        return self.mpg * self.tank_size

my_current_fav = Favorite(cars, 1)

print my_current_fav.name
print my_current_fav.range
print cars[my_current_fav]