【问题标题】:Is it possible to create a variable as a placeholder for 'current' value of a class in python?是否可以在 python 中创建一个变量作为类的“当前”值的占位符?
【发布时间】:2013-04-27 23:24:09
【问题描述】:

假设我们有一个类:

注意:这只是一个虚拟类。

class C(object):
    def __init__(self):
        self.a = -10
        self.b = 5
        self.c = 2
    def modify(self, **kwargs):
        for keyword in kwargs:
            vars(self)[keyword] = kwargs[keyword]
        return(self)

我们想使用这个修改方法来改变我们对象中的值:

myclass = C()
myclass = myclass.modify(a=10)

但是当我想在原来的基础上更改值时,我必须这样写:

myclass = C()
myclass = myclass.modify(a=myclass.a/10)

或者:

myclass = myclass.modify(a=abs(myclass.a))

我的问题是,有没有办法在模块中创建一个全局变量,我可以导入它并将其用作当前值的占位符,所以我可以使用这个公式:

from globvars import current

myclass = C()
myclass = myclass.modify(
    a=abs(current) % current ** 2,
    b=current//2,
    c=bool(current)
)

首先我尝试创建一个类,该类将存储它正在执行的操作和一个值,modify() 将首先查找其变量作为关键字,然后执行该函数。实际上它只适用于简单的情况,例如:current+10current**2。 但是当我意识到时,我想将此currenthsba(current)颜色转换器)函数一起使用,其中current 指向存储在另一个对象中的对象,我只是放弃吧,我不能把这个写到我要使用的每一堂课上……

有解决办法吗?也许这很容易,我只是看不到它:)

提前感谢您的回复!

【问题讨论】:

  • 在我看来,你把这件事过度复杂化了。为什么不像正常一样分配给属性?为什么myclass = myclass.modify(a=10)myclass.a = 10 好?
  • 不要在这里使用vars()for keyword, value in kwargs.iteritems(): setattr(self, keyword, value) 的工作效率会更高。
  • @Lattyware 是的,我可以使用它,但在我的情况下,我想检查输入以验证它们。这就是我选择这种方法的原因。
  • @MartijnPieters 好点,谢谢!我将在我的代码中更改它!
  • @PeterVaro:为什么不使用@property 然后创建一个执行验证的setter?

标签: python oop class variables placeholder


【解决方案1】:

您的modify 方法可以采用要修改的属性名称,以及采用该属性的当前值并返回其新计算值的函数。然后您可以执行以下操作:

def compute_new_value(current):
    new_value = abs(current) % current ** 2
    return new_value

myclass = C()
myclass.modify('a', compute_new_value)

对于简单的情况,lambda 使其不那么冗长:

myclass.modify('a', lambda cur: cur + 4)

还有你的班级:

class C(object):
    [...]
    def modify(self, attr_name, func):
        cur_value = getattr(self, attr_name)
        new_value = func(cur_value)
        setattr(self, attr_name, new_value)

编辑:我可能遗漏了什么。既然您正在编写myclass = myclass.modify...,那么modify 方法是否应该返回对象的副本

【讨论】:

  • 是的,修改函数将返回原始对象本身。但这不是您的解决方案的问题。问题是,我必须定义一个新方法,甚至是一个 lambda 方法,才能做到这一点。它比写这个要复杂得多:myclass.modify(a = abs(myclass.a) % myclass.a ** 2。我想要的只是让代码看起来更流畅,通过添加这个current 占位符,所以我可以使用这个:myclass.modify(a = abs(current) % current ** 2)。更容易阅读和了解正在发生的事情。
  • 不,使用全局变量从来没有更容易阅读和了解正在发生的事情。使用函数准确地描述了您在做什么:您正在对属性的当前值应用转换。转换就是函数,我不认为 lambda 对它来说太冗长了。
  • 在某些时候我完全同意你的看法!但是你能在@kindall 的回答上看看我的 cmets 吗?我在那里写下我的目标,我希望这是一个解释而不是借口:)
【解决方案2】:

在我看来,你的设计很糟糕,但你可以使用eval() 做到这一点。当然,这只会让你的设计更加有味道。还是……

class C(object):
    # ...
    def modify(self, **kwargs):
        for name, expr in kwargs.iteritems():
            setattr(self, name, eval(expr, vars(self)))

obj = C()
obj.modify(a="a+2", b="b*42")

缺点是您必须将表达式作为字符串传递。此外,通过这个简单的实现,您只能在表达式中使用在实例上定义的值(例如,您不能访问类属性、父类的任何属性或全局变量)。当然,您可以通过以适当的顺序构建v 字典来添加使用类属性或全局变量甚至父类的能力:

def modify(self, **kwargs):
    vardict = {}   # allow globals and self attributes to be used in expressions
    vardict.update(globals())
    vardict.update(vars(self))
    for name, expr in kwargs.iteritems():
        value = eval(expr, v)
        setattr(self, name, eval(expr, vardict))
        vardict[name] = value

如果您想要一个保存当前值的current 变量,您可以使用它(在循环内,因为它需要针对每个处理的属性进行更改):

v["current"] = getattr(self, name, None)

这里最大的缺点之一是您无法轻松地从 调用者的 范围内访问变量,尽管我猜您可以将它们从堆栈框架中挖掘出来......呃。或者让调用者将它们插入到字符串中...... double ugh。

在我看来,Morphyn 的回答是正确的做法。 lambda 并不复杂

【讨论】:

  • 是的,使用字符串将表达式从一个对象推送到另一个对象总是很脏,而且eval() 在我们的例子中也不是最好的处理方式。实际上,您需要了解的是,我正在为 GUI 标记和脚本编写一个类似“超集”的库。这就是为什么我正在使用 python 的强大功能进行快速简单的“标记样式”编码。这意味着,将使用我的类的设计师永远不会看到类内部是什么,或者它是如何工作的,等等。
  • 他将使用它作为redbutton = rectangle(width=100, height=50),当他想修改它时,稍后在脚本中,他会调用这个项目(__call__):redbutton(width=current+100)。这就是我得到的,也是我想要的。
  • 好的,我明白了。好吧,我很抱歉地说我不知道​​有什么方法可以做这种魔法。
  • 如果您接受以下内容,有一种方法可以做到:redbutton(width=curwidth+100, height=curheight+220) 但我不确定它是否比编写正确的 python 更简单。
  • @morphyn 嗯,非常感谢您的提醒。不知何故,这让我松了一口气,不只是我不知道这种魔法真正的 python 大师也不知道:)
【解决方案3】:

这是一个可行的解决方案。它并不完整并且充满了非常糟糕的设计选择,但我希望它有所帮助。

class Expr(object):

    def __init__(self, op, left, right):
        self.op = op
        self.left = left
        self.right = right

    def __call__(self, current):
        l = self._replace_current(self.left, current)
        r = self._replace_current(self.right, current)
        return self._do_operation(l, r)

    def _replace_current(self, val, current):
        if val == 'current':
            return current
        elif isinstance(val, Expr):  # recurse
            return val(current)
        else:
            return val

    def _do_operation(self, l, r):
        if self.op == '+':
            return l + r
        elif self.op == '*':
            return l * r
        elif self.op == '-':
            return l - r

    def __add__(self, other):
        return self._left_op('+', other)

    def __radd__(self, other):
        return self._right_op('+', other)

    def __mul__(self, other):
        return self._left_op('*', other)

    def __rmul__(self, other):
        return self._right_op('*', other)

    def __sub__(self, other):
        return self._left_op('-', other)

    def __rsub__(self, other):
        return self._right_op('-', other)

    def _left_op(self, op, other):
        if isinstance(other, Current):
            return Expr(op=op, left=self, right='current')
        else:
            return Expr(op=op, left=self, right=other)

    def _right_op(self, op, other):
        if isinstance(other, Current):
            return Expr(op=op, left='current', right=self)
        else:
            return Expr(op=op, left=other, right=self)


class Current(Expr):

    def __init__(self):
        super(Current, self).__init__(None, None, None)

    def __call__(self, current):
        return current

    def _left_op(self, op, other):
        return Expr(op=op, left='current', right=other)

    def _right_op(self, op, other):
        return Expr(op=op, left=other, right='current')


current = Current()


class YourObj(object):

    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __call__(self, **kw):
        for key, val in kw.iteritems():
            # You should probably make sure it is actually an attribute of YourObj
            if isinstance(val, Expr):
                current = self.a
                new_val = val(current)
                setattr(self, key, new_val)
            else:
                setattr(self, key, val)

你可以这样做:

obj = YourObj(a=4, b=5)
obj(a=current - 4 + current * current)

这基本上是嵌入在 python 数学运算中的表达式解释器。

每当您对current(如+)使用操作时,它都会返回一个Expr(因为它会覆盖__add____radd__),它将注册这是哪个操作,以及每个操作是什么操作数。这些表达式可以嵌套,所以如果你说current + 4 - current,它将返回Expr(op='-', left=Expr(op='+', left='current', right=4), right='current')

然后可以通过像函数一样调用它并将应该替换“当前”的值传递给它来评估表达式。当您评估一个表达式时,它将:

  • 用传递的值替换所有出现的“当前”
  • 递归计算嵌套函数
  • 返回整个表达式的最终结果

当您执行obj(a=current + 4) 时,会调用YourObj__call__ 方法。它将评估current + 4的表达式结果并将其存储在a中。

我希望这更清楚。也许我应该重命名一些“当前”以减少混乱。

【讨论】:

  • 好吧,我现在有点糊涂了.. 不过多解释一下就好了:) Current 类中有什么? Expr 类何时会创建实例?
  • 我添加了一些解释。当您在 current 上调用数学运算时会创建 Expr 的实例,例如 current + 4(参见 Expr__add__ 方法)。 CurrentExpr 的子类。
  • 我还要添加一个答案,我的旧解决方案.. 我实际上无法决定哪个“更好”:/
【解决方案4】:

这是我的旧解决方案..(有点,这是它的虚拟版本

class __current__(object):
    def do(self, e, v = None):
        c = __current__()
        c.exp = e
        if v is not None:
            c.val = v
        return(c)
    def __abs__(self):
        return(self.do(abs))
    def __rpow__(self, v):
        return(self.do(pow, v))

current = __current__()

class C(object):
    def __call__(self, **kwargs):
        for keyword, value in kwargs.iteritems():
            try:
                expression = value.exp
                try:
                    value = expression(vars(self)[keyword], value.val)
                except AttributeError:
                    value = expression(vars(self)[keyword])
            except AttributeError:
                value = value
            setattr(self, keyword, value)

及用法:

MyObj = C()
MyObj(a = -2)
MyObj(a = abs(current))
MyObj(a = 2 ** current)

【讨论】:

  • 嗯...基本思想是一样的。在您的工作中,知道如何评估表达式的责任在 MyObj 中,我认为这不是最好的主意。而且,我认为你不能在同一个表达式中使用两次current(没有测试过)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-23
  • 2021-01-12
  • 1970-01-01
  • 2015-09-16
  • 2019-06-13
  • 1970-01-01
相关资源
最近更新 更多