Python 2.7 - 左值修改的简洁语法

Python 2.7 - clean syntax for lvalue modification

类似结构的类型不希望被远程复制者修改是很常见的。

字符串是一个基本的例子,但这是一个简单的例子,因为它是可以原谅的不可变的——Python 是不寻常的,甚至允许对文字字符串进行方法调用。

问题是(在大多数语言中)我们经常遇到诸如 (x,y) Point class 之类的事情。我们偶尔想独立更改 xy。即,从使用的角度来看,Point LVALUE 应该是可变的(即使副本不会看到变化)。

但是Python 2.7 似乎没有提供任何选项来启用自动复制作业。所以这意味着我们实际上必须使我们的 Point class 不可变,因为无意中会在各处创建引用(通常是因为有人在将对象传递给其他人之前忘记克隆对象)。

不,我对仅允许对象发生变异的无数 hack 不感兴趣 "while it's being created",因为这是一个无法扩展的弱概念。

这些情况的逻辑结论是我们需要我们的变异方法来实际修改 LVALUE。例如 %= 支持。问题是如果有一个更合理的语法会更好,比如使用 __setattr__ and/or 定义 set_xset_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 修改创建了一个基本语法,它不仅限于看起来很傻的运算符。我就是找不到它。请帮忙。

在Python中,典型的模式是修改前复制,而不是赋值时复制。您可以实现某种具有所需语义的数据存储,但这似乎需要大量工作。

我觉得我们已经对预先存在的解决方案进行了尽职调查。鉴于“<=”在某些语言(例如Verilog)中是赋值,我们可以很直观地介绍:

value_struct_instance<<='field', value

作为

的 Pythonic 形式
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
# 
# 

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 = ..."