Указывать на атрибуты другого объекта и добавлять свои собственные - PullRequest
2 голосов
/ 22 сентября 2011

Предположим, у меня есть класс:

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")

В моемВ этом случае я просто капитулировал и добавил «диапазон» как атрибут Car ().Но что, если хранить 'range' в каждом экземпляре Car () было дорого, но вычислять его было дешево?

Я подумал о том, чтобы сделать my_current_fav подклассом Car (), но я не мог понятьспособ сделать это и по-прежнему поддерживать мою способность просто "указывать" my_current_favorite на запись в списке "cars".

Я также рассматривал возможность использования декораторов для вычисления и возврата 'range', но не смогнайти способ также предоставить доступ к атрибутам «имя», «mpg» и т. д.

Существует ли элегантный способ указать на любой элемент в списке «автомобили», предоставить доступ к атрибутамна указанный экземпляр, а также для предоставления дополнительных атрибутов, не найденных в классе Car?

Дополнительная информация:
После прочтения многих ваших ответов я вижу, что есть справочная информация.должен был поставить в исходный вопрос.Вместо того, чтобы комментировать многие ответы по отдельности, я добавлю здесь дополнительную информацию.

Этот вопрос является упрощением более сложной проблемы.Первоначальная проблема заключается в модификации существующей библиотеки.Несмотря на то, что использование диапазона является вызовом метода, а не атрибута, это хороший способ, но изменение

some_var = my_current_favorite.range

на

some_var = my_current_favorite.range()

во многих существующих пользовательских сценариях будет дорогостоящим.Черт возьми, отслеживание этих пользовательских сценариев будет дорого.

Аналогично, мой нынешний подход к вычислительной дальности для каждого автомобиля не является "дорогим" с точки зрения Python, но - это дорого во время выполнения, потому что для реального аналога требуются медленные вызовы (встроенной) ОС.Хотя сам Python не медленный, эти вызовы, поэтому я стараюсь свести их к минимуму.

Ответы [ 7 ]

6 голосов
/ 22 сентября 2011

Это проще всего сделать в вашем примере, не изменяя 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 instances car, который вы устанавливаете при создании Favorite.

Ваш пример range не особенно хорош для добавления чего-либодо Favorite, потому что это должен быть property автомобиль, но я использовал его для простоты.

Редактировать : обратите внимание, что преимущество этого метода заключается в том, что вы измените свойлюбимая машина, и вы не сохранили ничего специфичного для машины на Favorite, вы можете изменить существующую любимую на другую машину с помощью:

my_current_fav.car = cars[0] # or Car('whatever')
0 голосов
/ 23 сентября 2011

Вы говорите о my_current_fav как о «указателе» - я просто хочу убедиться, что вы понимаете, что на самом деле

my_current_fav = cars[1]

связывает имя с cars[1] - другими словамиmy_current_fav is cars[1] == True.Там нет указания происходит.Если вы действительно хотите указатель-стиль, вы можете сделать что-то вроде этого:

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]
0 голосов
/ 22 сентября 2011

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

В моем случае,Я просто капитулировал и добавил «диапазон» как атрибут Car ().Но что, если хранить 'range' в каждом экземпляре Car () было дорого, но вычислять его было дешево?

Во-первых, беспокоиться о том, что вещи "дорогие", обычно не то, что Pythonic.Если бы это действительно имело значение, вы бы использовали язык более низкого уровня большую часть времени.Но в целом, если вы хотите, чтобы что-то вычислялось, а не сохранялось, естественным способом является использование метода.Добавьте его в класс автомобилей. не стоит за объект, если, конечно, вы явно не замените метод для каждого объекта.

Вот как это работает: когда вы вызываете a_car.range(),атрибут range сначала ищется в a_car.Если он там не найден, тогда ( здесь пропущено множество деталей! ) класс a_car идентифицируется как Car, и атрибут там ищется.Вы определяете range как Car метод, так что он будет найден там и определен как нечто, что в действительности вызывается, поэтому он вызывается.Как специальное синтаксическое правило, a_car передается в качестве первого параметра в метод (все это частично объясняет, почему для методов в Python требуется явный параметр - называемый self - для методов в Python, в отличие от многих других языков снеявный this).

Я подумал о том, чтобы сделать my_current_fav подклассом Car (), но я не мог придумать, как это сделать, и все еще сохранял свою способность"указать" my_current_favorite "на запись в списке" cars ".

Вы можете определенно хранить подкласс Car в том же списке, что и набор обычных Car s.Нет проблем там.Черт возьми, вы можете хранить Banana в том же списке, что и набор Car s, если хотите;Python типизирован динамически, и ему все равно.Он выяснит, что это за объект в тот момент, когда он становится актуальным.

То, что вы не можете легко сделать, это заставить существующий Car стать MyFavouriteCar.Если вы создали новый MyFavouriteCar, на который ссылается my_favourite_car (не говорите "указывает на", пожалуйста; ссылки на объекты являются абстракцией более высокого уровня, и в отличие от Java в Python нет "нулевого указателя" - None является объектом), тогда вы можете заменить существующий автомобиль в списке на него, но это все же другой объект (даже если он каким-то образом основан на исходном Car, который он заменяет).Вы можете спроектировать таким образом, чтобы это не имело значения;или вы можете прибегнуть к злобной хакерской атаке (которую я не буду здесь объяснять);или вы можете (намного лучше в вашем случае) просто предложить функциональность для всех автомобилей, потому что это действительно бесплатно .

Из дзен Python: Особые случаиНе достаточно особенный .

0 голосов
/ 22 сентября 2011

Похоже, 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

Это займетПреимущество того факта, что вы можете динамически создавать поля в объектах.

Я не понимаю, почему по умолчанию будет 180, если по умолчанию вычисленные значения 300. Если это странное поведение важно, вам понадобитсяустановить другой флаг, чтобы увидеть, были ли другие параметры инициализированы по умолчанию или нет.

0 голосов
/ 22 сентября 2011

Почему бы вам просто не создать метод:

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
0 голосов
/ 22 сентября 2011

Что касается вашего примера, вы можете сделать range доступным только для чтения свойством класса Car, которое будет вычисляться по запросу. Нет необходимости в дополнительных занятиях.

0 голосов
/ 22 сентября 2011

Если у вас есть доступ к классу (и он звучит так, как у вас), просто создайте функцию внутри класса.

def range(self):
    return self.tank_size * self.mpg
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...