Как выполнить поверхностное обновление копии с неизменным типом в Python? (структурное разделение) - PullRequest
1 голос
/ 01 ноября 2019

Я прихожу в Python из Scala, где для заданного (case) класса я могу выполнить копию, и на этом же шаге указать произвольное подмножество полей. В Scala это выглядит так:

case class Foo(year: Int, album: String)

// here's a Foo
val foo: Foo = Foo(1973, "Larks Tongues In Aspic")

// updated album with (shallow) copy of year in one step
val nooFoo: Foo = foo.copy(album = "Black Beauty: Live at the Fillmore West")

(пример прост для репликации)

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

Я рассмотрел использование namedtuple или повышение ошибок в __setattr__ для создания неизменяемого объекта. Тогда, я думаю, у меня могут быть методы, которые вызывают object.__setattr__, что, похоже, перегружает локальный метод __setattr__. Но значит ли это, что мне нужно реализовать метод для каждой комбинации обновлений, которые я могу выполнить? Это значит, что мне нужно определить до n! обновить функции для n полей (в теории). В случае класса с 2 полями это может выглядеть следующим образом:

import copy

class Foo:
  self.year = None
  self.album = None

  def __init__(self, year, album):
    object.__setattr__(self, "year", year)
    object.__setattr__(self, "album", album)

  def __setattr__(self):
    raise TypeError("i pity the Foo")

  def update_a(self, new_year):
    noo_foo = copy.copy(self)
    object.__setattr__(noo_foo, "year", new_year)
    return noo_foo

  def update_b(self, new_album):
    noo_foo = copy.copy(self)
    object.__setattr__(noo_foo, "album", new_album)
    return noo_foo


Как Pythonic может выполнять неизменяемые обновления объектов (копии с модификацией полей), не оказываясь в ловушке при записи тонныклассный образец? Или я упускаю что-то простое?

1 Ответ

1 голос
/ 01 ноября 2019

namedtuple поддерживает это поведение с помощью метода ._replace (несмотря на начальное подчеркивание, это публичный метод, просто с префиксом подчеркивания, чтобы избежать столкновения с пользователем-определенные поля), и он на самом деле неизменный (перезапись __setattr__ не является действительно неизменной).

from collections import namedtuple

Foo = namedtuple('Foo', 'year album')

foo1 = Foo(1996, 'Album1')
foo2 = foo1._replace(year=1997)

Если вам не нужна совместимость со старыми версиями Python, и вы хотите простой способ добавитьдополнительные поведения для класса, более новый typing.NamedTuple - более гибкий способ создания именованных кортежей.

...