Python 2.7 - чистый синтаксис для изменения lvalue

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

Строка является базовым примером, но это простой случай, потому что это извиняемо неизменное - Python необычен даже для того, чтобы разрешать такие вещи, как вызовы методов для литералов.

Проблема в том, что (на большинстве языков) мы часто имеем такие вещи, как класс (x,y) Point. Иногда мы хотим независимо менять x и y. I.e., с точки зрения использования, Point LVALUE должен быть изменчивым (даже если копии не будут видеть мутацию).

Но Python 2.7, похоже, не предоставляет никаких параметров для включения автоматического копирования при назначении. Таким образом, мы фактически ДОЛЖНЫ сделать наш Point класс IMMUTABLE, потому что непреднамеренные ссылки будут создаваться повсюду (обычно, потому что кто-то забыл клонировать объект, прежде чем передавать его кому-то другому).

И нет, меня не интересуют бесчисленные хаки, которые позволяют объекту мутировать только "пока он создается", поскольку это слабое понятие, которое не масштабируется.

Логическое завершение этих обстоятельств состоит в том, что нам нужны наши методы мутаций для фактического изменения LVALUE. Например, %= поддерживает это. Проблема в том, что было бы намного лучше иметь более разумный синтаксис, например, используя __setattr__ и/или определяющие методы set_x и set_y, как показано ниже.

class Point(object):
# Python doesn't have copy-on-assignment, so we must use an immutable
# object to avoid unintended changes by distant copyholders.

    def __init__(self, x, y, others=None):
        object.__setattr__(self, 'x', x)
        object.__setattr__(self, 'y', y)

    def __setattr__(self, name, value):
        self %= (name, value)
        return self # SHOULD modify lvalue (didn't work)

    def __repr__(self):
        return "(%d %d)" % (self.x, self.y)

    def copy(self, x=None, y=None):
        if x == None: x = self.x
        if y == None: y = self.y
        return Point(x, y)

    def __eq__ (a,b): return a.x == b.x and a.y == b.y
    def __ne__ (a,b): return a.x != b.x or  a.y != b.y
    def __add__(a,b): return Point(a.x+b.x, a.y+b.y)
    def __sub__(a,b): return Point(a.x-b.x, a.y-b.y)

    def set_x(a,b): return a.copy(x=b) # SHOULD modify lvalue (didn't work)
    def set_y(a,b): return a.copy(y=b) # SHOULD modify lvalue (didn't work)

    # This works in Python 2.7. But the syntax is awful.
    def __imod__(a,b):
        if   b[0] == 'x': return a.copy(x=b[1])
        elif b[0] == 'y': return a.copy(y=b[1])
        else:             raise AttributeError,  \
                "Point has no member '%s'" % b[0]



my_very_long_and_complicated_lvalue_expression = [Point(10,10)] * 4


# modify element 0 via "+="   -- OK
my_very_long_and_complicated_lvalue_expression[0] += Point(1,-1)

# modify element 1 via normal "__set_attr__"   -- NOT OK
my_very_long_and_complicated_lvalue_expression[1].x = 9999

# modify element 2 via normal "set_x"  -- NOT OK
my_very_long_and_complicated_lvalue_expression[2].set_x(99)

# modify element 3 via goofy "set_x"   -- OK
my_very_long_and_complicated_lvalue_expression[3]    %='x',   999


print my_very_long_and_complicated_lvalue_expression

Результат:

[(11 9), (10 10), (10 10), (999 10)]

Как вы можете видеть, += и %= работают нормально, но практически ничего не работает. Разумеется, изобретатели языка создали базовый синтаксис для модификации LVALUE, который не ограничивается тупыми операторами. Я просто не могу найти его. Пожалуйста, помогите.

Ответ 1

Я чувствую, что мы дали поиску уже существующих решений его должной осмотрительности. Учитывая, что "< =" является присвоением на некоторых языках (например, Verilog), мы можем интуитивно ввести:

value_struct_instance<<='field', value

как питоническая форма

value_struct_instance.field = value

Вот обновленный пример для поучительных целей:

# Python doesn't support copy-on-assignment, so we must use an
# immutable object to avoid unintended changes by distant copyholders.
# As a consequence, the lvalue must be changed on a field update.
#
# Currently the only known syntax for updating a field on such an
# object is:
#
#      value_struct_instance<<='field', value
# 
# https://stackoverflow.com/questions/45788271/

class Point(object):

    def __init__(self, x, y, others=None):
        object.__setattr__(self, 'x', x)
        object.__setattr__(self, 'y', y)

    def __setattr__(self, name, value):
        raise AttributeError, \
            "Use \"point<<='%s', ...\" instead of \"point.%s = ...\"" \
            % (name, name)

    def __repr__(self):
        return "(%d %d)" % (self.x, self.y)

    def copy(self, x=None, y=None):
        if x == None: x = self.x
        if y == None: y = self.y
        return Point(x, y)

    def __ilshift__(a,b):
        if   b[0] == 'x': return a.copy(x=b[1])
        elif b[0] == 'y': return a.copy(y=b[1])
        else:             raise AttributeError,  \
                "Point has no member '%s'" % b[0]

    def __eq__ (a,b): return a.x == b.x and a.y == b.y
    def __ne__ (a,b): return a.x != b.x or  a.y != b.y
    def __add__(a,b): return Point(a.x+b.x, a.y+b.y)
    def __sub__(a,b): return Point(a.x-b.x, a.y-b.y)



my_very_long_and_complicated_lvalue_expression = [Point(10,10)] * 3

# modify element 0 via "+="
my_very_long_and_complicated_lvalue_expression[0] += Point(1,-1)

# modify element 1 via "<<='field'," (NEW IDIOM)
my_very_long_and_complicated_lvalue_expression[1]<<='x', 15
print my_very_long_and_complicated_lvalue_expression
# result:
# [(11 9), (15 10), (10 10)]

my_very_long_and_complicated_lvalue_expression[1]<<='y', 25
print my_very_long_and_complicated_lvalue_expression
# result:
# [(11 9), (15 25), (10 10)]

# Attempt to modify element 2 via ".field="
my_very_long_and_complicated_lvalue_expression[2].y = 25
# result:
# AttributeError: Use "point<<='y', ..." instead of "point.y = ..."

Ответ 2

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