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

Posted

技术标签:

【中文标题】Python 2.7 - 用于左值修改的简洁语法【英文标题】:Python 2.7 - clean syntax for lvalue modification 【发布时间】:2018-01-29 00:09:50 【问题描述】:

拥有预计不会被远方的拷贝持有者修改的类结构类型是很常见的。

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

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

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

不,我对允许对象仅在“创建时”发生变异的无数hack不感兴趣,因为这是一个无法扩展的弱概念。

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

【问题讨论】:

这太可怕了。请改用numpy 来实现您的观点。 即使您可以这样做,您仍然会遇到相同的错误,除了意外共享包含点的容器而不是意外共享点本身。您正在寻找的语义不适合 Python 这样的语言。 @NeilG Point 只是一个例子。我看不出 numpy 如何解决左值修改问题。请更具体。 @user2357112 是的,这些错误可能发生在容器、像素缓冲区中,随你的便。但是对于简单的结构,应该有一个干净的解决方案,比如 C 中的“struct”,或者 Pascal、Modula-3 等中的“record”,否则你会遇到很多错误。 您正在寻找值类型,而 Python 没有这些。您能做的最好的事情就是摆脱您在具有值类型的语言中学到的习惯,例如清除对象而不是创建新对象。 【参考方案1】:

在 Python 中,典型的模式是在修改之前复制,而不是在赋值时复制。您可以使用您想要的语义实现某种数据存储,但这似乎需要做很多工作。

【讨论】:

所以...问题是如何使修改前的复制自动发生,就像使用“+=”一样——但是当这种运算符令人困惑或不合适时。 @personal_cloud += 不一定是写时复制。在某些情况下,它可以改变左侧的对象;在其他情况下,它可以返回一个新对象。使用您想要的语义编写数据存储,或者更小心不要忘记在改变对象之前复制它们。 @personal_cloud 我不知道有谁写过这样的包,因为我认为它不像你那样有用。你必须自己写。我绝对没有时间这样做。 修改前复制的基本问题是“修改前”很难预测。当你将一个对象传递给其他人的模块时,你不能完全确定他们不会在调用期间修改它——或者更糟:在你不期望的调用之后。从技术上讲,每个模块的作者都应该记录它可以修改的内容。但是,如果他们做得不好怎么办?然后我必须做更多的调试。因此,没有按值传递是该语言的一个严重问题。我宁愿每次将大多数对象传递给其他人时自动复制它们。【参考方案2】:

我觉得我们已经尽职尽责地寻找预先存在的解决方案。鉴于“

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
# 
# https://***.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 = ..."

【讨论】:

@Neil G 如果 x 是整数,我们可以“x+=1”吗?请记住,整数是对象!绝对不要在修改时使用操作符来复制??? 尝试使用整数 x += 1。是的,绝对不要为此重写运算符。 那么字符串呢?我可以做h += ".txt"吗?然后,是否可以推断出语言设计者在处理不可变对象时完全意识到左值修改的普遍需求?或者我们根本无法将修改时自动复制等原则应用于任何其他类型。只是因为 int 和 string 在某些方面是不同的。 int 和 string 究竟在哪些方面与其他类型不同? (提示:人们认为它们是 VALUES 而不是对象。哦,但我们不想承认这种情况永远发生在不是 int 或 string 的东西上)

以上是关于Python 2.7 - 用于左值修改的简洁语法的主要内容,如果未能解决你的问题,请参考以下文章

为啥我得到的表达式必须是可修改的左值?

Python 03程序设计与Python语言概述

将文字转换为左值

[转]Python安装3 —— Python3.8和2.7共存

在 python 2.7 中导入 zipfile 模块时出现语法错误

在Javascript中是否有python 2.7x中的Object spread语法?