I have the following custom classes (stripped down some), implementing an expression tree:
from abc import ABC, abstractmethod
class Operator:
def __init__(self, str_reps):
self.str_reps = str_reps
def __str__(self):
return self.str_reps[0]
def __eq__(self, other):
return self is other
def __ne__(self, other):
return self is not other
def __hash__(self):
return hash(str(self))
NOT = Operator(["¬", "~", "not", "!"])
AND = Operator(["∧", "&", "and"])
OR = Operator(["∨", "|", "or"])
class Node(ABC):
#abstractmethod
def __eq__(self, other):
pass
#abstractmethod
def __hash__(self):
pass
def __ne__(self, other):
return not self == other
#abstractmethod
def __str__(self):
pass
#abstractmethod
def __invert__(self):
pass
def bracket_if_necessary(self):
return str(self)
class Leaf(Node):
def __init__(self, v):
self.val = v
def __eq__(self, other):
if not isinstance(other, Leaf):
return False
return self.val == other.val
def __hash__(self):
return hash(self.val)
def __str__(self):
return str(self.val)
def __invert__(self):
return UnaryNode(self)
class UnaryNode(Node):
def __init__(self, child):
self.child = child
self.hash = hash(NOT) + hash(self.child)
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.child == other.child
def __hash__(self):
return self.hash
def __str__(self):
return str(NOT) + self.child.bracket_if_necessary()
def __invert__(self):
return self.child
class VariadicNode(Node):
def __init__(self, op, children):
self.op = op
self.children = children
self.hash = hash(self.op) + sum(hash(child) for child in self.children)
def __eq__(self, other):
if not isinstance(other, VariadicNode):
return False
return self.op is other.op and set(self.children) == set(other.children)
def __hash__(self):
return self.hash
def __str__(self):
return (" " + str(self.op) + " ").join(child.bracket_if_necessary() for child in self)
def __invert__(self):
return VariadicNode(AND if self.op is OR else OR, tuple(~c for c in self))
def bracket_if_necessary(self):
return "(" + str(self) + ")"
def __iter__(self):
return iter(self.children)
def __contains__(self, item):
return item in self.children
If I run this and try things like
Leaf("36") == Leaf("36)
~Leaf("36") == ~Leaf("36")
~~Leaf("36") == Leaf("36")
they all return True, as expected.
However, I'm running into bugs in the code that utilizes these nodes:
# Simplify procedure in DPLL Algorithm
def _simplify(cnf, l):
# print for debugging
for c in cnf:
print("b", l, ~l, type(l), "B", c, type(c), l in c)
return VariadicNode(AND, tuple(_filter(c, ~l) for c in cnf if l not in c))
# Removes the chosen unit literal (negated above) from clause c
def _filter(c, l):
# print for debugging
for x in c:
print("a", l, type(l), "A", x, type(x), x==l)
return VariadicNode(c.op, tuple(x for x in c if x != l))
Here cnf is given as a VariadicNode(AND) with all children being VariadicNode(OR). Children of VariadicNode are always given as a tuple.
These two prints result in lines like:
a ¬25 <class 'operators.UnaryNode'> A ¬25 <class 'operators.UnaryNode'> False
b ¬25 25 <class 'operators.UnaryNode'> B ¬25 ∨ ¬36 <class 'operators.VariadicNode'> False
which should not happen (¬25 == ¬25 in the first line and ¬25 in (¬25 ∨ ¬36) in the second should both return True). However there is also a line in the output:
b ¬25 25 <class 'operators.UnaryNode'> B ¬25 <class 'operators.VariadicNode'> True
so the check ¬25 in (¬25) actually does return True as it should.
Can anyone tell me what's going on?
If more info is needed, the rest of the code is available at [deleted] (hopefully it's publicly available, I'm pretty new to github so I don't know their policies). Note that it is still a WIP though.
The classes are located in operators.py and the rest of the (relevant) code is in SAT_solver.py, while test.py allows for easy running of the entire project, provided the networkx library is installed.
EDIT
I've now pushed a sample dimacs .txt file to the github repository that results in the described problem. Simply download hamilton.txt, SAT_solver.py and operators.py from the repository to the same folder, run SAT_solver.py (which has a main() method) and input hamilton or hamilton.txt to the command line when prompted for the problem file name (just leave the solution file name empty when prompted to prevent the program from writing any files). This should result in a lot of output, including problematic lines as described above.
The code you posted is not the code in your github repo. Your code has UnaryNode.__eq__ of
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.child == other.child
the repo code has
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.op is other.op and self.child == other.child
which also requires that the operators are identical. Instrumenting your code and breaking at a failure shows that you're generating two different NOT operators somewhere:
>>> str(x)
'¬24'
>>> str(l)
'¬24'
>>> x == l
False
>>> x.child == l.child
True
>>> str(x.op)
'¬'
>>> str(l.op)
'¬'
>>> x.op == l.op
False
>>> id(x.op)
2975964300
>>> id(l.op)
2976527276
Track down where that's happening and fix it however you like, whether by avoiding having more than one or by not caring whether there are more than one (my preference). I know in a deleted comment you wrote "The operators are single objects and I want them to be compared for equality based on their references", but (1) they're not single objects, and (2) if you didn't want such an unnecessary thing you wouldn't have woudn up in trouble..
If I had to guess, the other operators are being introduced when you call copy.deepcopy, but you're definitely not working with singletons.
Related
I'm trying to find a general way of generating objects which can be converted to strings and back again using the parse module. For example, for a class StringyObject whose instances have just two attributes a and b:
import parse
class StringyObject(object):
fmt = "{a} {b}"
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return self.fmt.format(a=self.a, b=self.b)
#classmethod
def parse(cls, string):
result = parse.parse(cls.fmt, string)
kwargs = result.named
return cls(**kwargs)
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return NotImplemented
if __name__ == "__main__":
obj = StringyObject("foo", "bar")
reconstructed_obj = StringyObject.parse(str(obj))
assert reconstructed_obj == obj, "The reconstructed object should be equivalent to the original one."
The script consecutively calls the __str__ instance method and the parse class method, and verifies that the resulting objects obj and reconstructed_obj are equivalent (defined here as being instances of the same class and having the same dictionaries; cf. Elegant ways to support equivalence ("equality") in Python classes).
So far, so good, but I'd like to extend this method to attributes which are lists of variable length. For example, if b is a list, then I could do the following:
import parse
class StringyObject(object):
fmt = "{a} {b}"
separator = ", "
def __init__(self, a, b):
self.a = a
assert isinstance(b, list), "b should be a list."
self.b = b
def __str__(self):
b_string = self.separator.join(self.b)
return self.fmt.format(a=self.a, b=b_string)
#classmethod
def parse(cls, string):
result = parse.parse(cls.fmt, string)
kwargs = result.named
kwargs['b'] = kwargs['b'].split(cls.separator)
return cls(**kwargs)
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return NotImplemented
if __name__ == "__main__":
obj = StringyObject("foo", ["bar1", "bar2"])
reconstructed_obj = StringyObject.parse(str(obj))
assert reconstructed_obj == obj, "The reconstructed object should be equivalent to the original object."
This still works for this example, but is less elegant because I start to have to use join() and split(), which is what I wanted to avoid by using parse.parse. Furthermore, if I add another attribute c which comes after b in the string representation, the parsing goes haywire:
class StringyObject(object):
fmt = "{a} {b} {c}"
separator = ", "
def __init__(self, a, b, c):
self.a = a
assert isinstance(b, list), "b should be a list."
self.b = b
self.c = c
def __str__(self):
b_string = self.separator.join(self.b)
return self.fmt.format(a=self.a, b=b_string, c=self.c)
Then running the script
obj = StringyObject("foo", ["bar1", "bar2"], "hello")
result = parse.parse(StringyObject.fmt, str(obj))
produces the wrong Result object:
<Result () {'a': 'foo', 'c': 'bar2 hello', 'b': 'bar1,'}>
What I would actually like to is implement a kind of 'sub-parser' for b which keeps on running as long as it can find a separator, and only then continues with parsing c. Is there an elegant way to do this?
My suggestion is to look into using ast.literal_eval. This function is a safe eval of Python literal structures (ints, float, strings, lists, dicts...)
I wasn't able to get your examples to work using the parse library, but if you modify your format string slightly, it will work pretty easily with ast.literal_eval:
import ast
class StringyObject(object):
fmt = "{a!r}, {b!r}"
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return self.fmt.format(a=self.a, b=self.b)
#classmethod
def parse(cls, string):
return cls(*ast.literal_eval(string))
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return NotImplemented
if __name__ == "__main__":
objects = [("foo", "bar"),
("foo", ["bar1", "bar2"]),
(["foo1", ("foo2", ["foo3", {"foo4"}])], {"bar1" : "bar2", "bar3": ["bar4", "bar5"]})]
for a, b in objects:
obj = StringyObject(a, b)
reconstructed_obj = StringyObject.parse(str(obj))
assert reconstructed_obj == obj, "The reconstructed object should be equivalent to the original one."
The downside to this implementation is that it will only work for basic python literals; i.e., StringyObject(frozenset(['foo']), 'bar') won't work.
I found that the desired parsing result could be achieved by adding some 'fixed' characters (not just spaces) in the format string. For example, below I've put a pipe (|) between the {b} and {c}:
import parse
class StringyObject(object):
fmt = "{a} {b} | {c}"
separator = ", "
def __init__(self, a, b, c):
self.a = a
assert isinstance(b, list), "b should be a list."
self.b = b
self.c = c
def __str__(self):
b_string = self.separator.join(self.b)
return self.fmt.format(a=self.a, b=b_string, c=self.c)
#classmethod
def parse(cls, string):
result = parse.parse(cls.fmt, string)
kwargs = result.named
kwargs['b'] = kwargs['b'].split(cls.separator)
return cls(**kwargs)
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return NotImplemented
if __name__ == "__main__":
obj = StringyObject("foo", ["bar1", "bar2"], "hello")
result = parse.parse(StringyObject.fmt, str(obj))
print result
reconstructed_obj = StringyObject.parse(str(obj))
assert reconstructed_obj == obj, "The reconstructed object should be equivalent to the original object."
The printed Result is
<Result () {'a': 'foo', 'c': 'hello', 'b': 'bar1, bar2'}>
as desired. The reconstructed_obj is also equivalent to the original obj.
The code below works(EDIT: actually, turns out it doesn't!), but I don't like the hanging return True statement that appears after the try: except: block.
class MySlottedClass(object):
def __new__(klass, **slots):
klass.__slots__ = []
for k in slots:
klass.__slots__.append(k)
return super(MySlottedClass,klass).__new__(klass)
def __init__(self, **slots):
for k,v in slots.items():
setattr(self,k,v)
super(MySlottedClass,self).__new__()
def __eq__(self, other):
for slot in self.__slots__:
try:
assert getattr(self, slot) == getattr(other,slot), "Not Equal"
except (AssertionError, AttributeError):
return False
return True
##Testing
##Note that the above class definition is just a skeleton
##The below objects are created using 4 different but identically defined classes
##In the actual problem, I am using a metaclass to make these classes dynamically
msc1 = MySlottedClassABC(a=1,b=1,c=3)
msc2 = MySlottedClassAB(a=1,b=1)
msc3 = MySlottedClassBA(b=2,a=1)
msc4 = MySlottedClassXY(x=1,y=2)
assert msc1!=msc2
assert msc2==msc3
assert msc3==msc2
assert msc2!=msc4
Is there a more pythonic way of writing the __eq__ method for this class?
The return True is fine. I think the bigger problem is using an assert for flow control. Asserts do not run at all if the user passes -O to python on the command line. You should write something more like this:
for slot in self.__slots__:
if not hasattr(other, slot) or getattr(self, slot) != getattr(other,slot):
return False
return True
Also, __slots__ needs to be defined at the class level to work, not inside __init__:
class Foo(object):
__slots__ = ['a', 'b', 'c']
If you have a variable number of items, you probably should not be using __slots__ at all.
Ugh, nevermind I figured it out. It was pretty obvious:
def __eq__(self, other):
try:
for slot in self.__slots__:
assert getattr(self, slot) == getattr(other,slot), "Not Equal"
except (AssertionError, AttributeError):
return False
else:
return True
I should probably close this question so I don't look too dumb.
Edit: Nope, no good!
Thanks to everyone's help I now understand there are lots of problems with this way of doing it. First of all, I should not be using assert for this since it is mainly for testing, and can be turned off. Second of all, the code doesn't give the expected result for MySlottedClass(a=1,b=2)==MySlottedClass(a=1,b=2,c=3).
I came up with this way instead. Note that the class definition is repeated 4 times so I can test comparison of objects of different classes below; all of the classes are identical, however, until their instances are created. Also note that in the actual use case, I am using a metaclass to generate these classes automatically (and __eq__ is defined as a part of that metaclass).
class MySlottedClassAB(object):
def __new__(klass, **slots):
klass.__slots__ = []
for k in slots:
klass.__slots__.append(k)
return super(MySlottedClassAB,klass).__new__(klass)
def __init__(self, **slots):
for k,v in slots.items():
setattr(self,k,v)
super(MySlottedClassAB,self).__init__()
def __eq__(self, other):
if set(self.__slots__) != set(other.__slots__): return False
for slot in self.__slots__:
if getattr(self, slot) != getattr(other,slot):
return False
return True
def __ne__(self, other):
return not self == other
class MySlottedClassBA(object):
def __new__(klass, **slots):
klass.__slots__ = []
for k in slots:
klass.__slots__.append(k)
return super(MySlottedClassBA,klass).__new__(klass)
def __init__(self, **slots):
for k,v in slots.items():
setattr(self,k,v)
super(MySlottedClassBA,self).__init__()
def __eq__(self, other):
if set(self.__slots__) != set(other.__slots__): return False
for slot in self.__slots__:
if getattr(self, slot) != getattr(other,slot):
return False
return True
def __ne__(self, other):
return not self == other
class MySlottedClassXY(object):
def __new__(klass, **slots):
klass.__slots__ = []
for k in slots:
klass.__slots__.append(k)
return super(MySlottedClassXY,klass).__new__(klass)
def __init__(self, **slots):
for k,v in slots.items():
setattr(self,k,v)
super(MySlottedClassXY,self).__init__()
def __eq__(self, other):
if set(self.__slots__) != set(other.__slots__): return False
for slot in self.__slots__:
if getattr(self, slot) != getattr(other,slot):
return False
return True
def __ne__(self, other):
return not self == other
class MySlottedClassABC(object):
def __new__(klass, **slots):
klass.__slots__ = []
for k in slots:
klass.__slots__.append(k)
return super(MySlottedClassABC,klass).__new__(klass)
def __init__(self, **slots):
for k,v in slots.items():
setattr(self,k,v)
super(MySlottedClassABC,self).__init__()
def __eq__(self, other):
if set(self.__slots__) != set(other.__slots__): return False
for slot in self.__slots__:
if getattr(self, slot) != getattr(other,slot):
return False
return True
def __ne__(self, other):
return not self == other
And here are the testing procedures:
##Testing
msc1 = MySlottedClassABC(a=1, b=2, c=3)
msc2 = MySlottedClassAB(a=1, b=2)
msc3 = MySlottedClassBA(b=2, a=1)
msc4 = MySlottedClassXY(x=1, y=2)
assert msc1 != msc2
assert msc2 != msc1
assert msc2 == msc3
assert msc3 == msc2
assert msc3 != msc4
assert msc4 != msc3
However, after testing Joran Beasley's answer, I discovered to my surprised it produces IDENTICAL results to that above, with much shorter and more sensible code. So it seems the best way to accomplish this is to simply compare the two __dict__ attributes.
Seems like you are trying to recreate a namedtuple. Using namedtuple will allow to create classes on the dynamically, test for equality and other interesting things. The downside is that since tuples are immutable, so to are namedtuples and you will have to create a new object instead of updating an attribute. namedtuples will not check the order of your slots, so you must order your slots lexicographically or add your own __eq__ method that accounts for slot order.
Example usage:
from collections import namedtuple
MySlottedClassAB = namedtuple("MySlottedClassAB", ['a', 'b'])
MySlottedClassABC = namedtuple("MySlottedClassABC", ['a', 'b', 'c'])
class MySlottedClassBA(namedtuple("MySlottedClassBA", ['b', 'a'])):
def addAB(self):
return self.a + self.b
msc1 = MySlottedClassAB(a=1, b=2)
msc2 = MySlottedClassBA(b=2, a=1)
msc3 = MySlottedClassABC(1, 2, 3)
print(msc1)
print(msc2)
print(msc3)
print("{} == {} is {}".format(msc1, msc1, msc1==msc1))
print("{} == {} is {}".format(msc1, msc2, msc1==msc2))
print("{} == {} is {}".format(msc1, msc3, msc1==msc3))
print("msc2.addAB() is {}".format(msc2.addAB()))
If the order of your slots and mutability are important the following will work (for python 2).
class MySlottedClassMeta(type):
def __init__(cls, name, bases, attrs):
super(MySlottedClassMeta, cls).__init__(name, bases, attrs)
def __new__(metacls, name, bases, attrs):
assert "__slots__" in attrs
attrs["_ordered_slots"] = tuple(sorted(attrs["__slots__"]))
attrs["__init__"] = create_init(attrs["__slots__"])
attrs["__eq__"] = create_eq()
attrs["__str__"] = create_str()
cls = super(MySlottedClassMeta, metacls).__new__(metacls, name, bases, attrs)
return cls
def create_init(slots):
args = ", ".join(slots)
assignments = "\n ".join("self.{0} = {0}".format(attr) for attr in slots)
init_source = """
def __init__(self, {}):
{}
""".format(args, assignments)
exec(init_source, globals(), None)
return __init__
def create_eq():
def __eq__(self, other):
try:
same_slots = self._ordered_slots == other._ordered_slots
except AttributeError:
return False
if not same_slots:
return False
return all(getattr(self, attr) == getattr(other, attr)
for attr in self._ordered_slots)
return __eq__
def create_str():
def __str__(self):
attr_values = ", ".join("{}={}".format(s, getattr(self, s)) for s in self.__slots__)
return "{}({})".format(self.__class__.__name__, attr_values)
return __str__
class MySlottedClassXY(object):
__slots__ = ['x', 'y']
__metaclass__ = MySlottedClassMeta
class MySlottedClassYX(object):
__slots__ = ['y', 'x']
__metaclass__ = MySlottedClassMeta
xy1 = MySlottedClassXY(x=1,y=2)
xy2 = MySlottedClassXY(1, 2)
yx = MySlottedClassYX(x=1, y=2)
print(xy1.__slots__)
print(yx.__slots__)
assert xy1 == xy1
assert xy1 == xy2
assert xy1 == yx
It's work noting that __slots__ are overkill in almost all circumstances. Guido Van Rossum stated that they were a premature optimisation based on unfounded fears about the performance of attribute lookups in new style classes. Guido does also state that __slots__ can reduce the memory footprint of program when you need to create lots of small objects.
I feared that all of the changes in the [new] class system were going to have a negative impact on performance. ... Thus the use of __slots__ was a way to optimize the lookup of data attributes—a fallback, if you will, in case people were disappointed with the performance impact of the new class system. This turned out unnecessary, but by that time it was of course too late to remove __slots__.
http://python-history.blogspot.co.uk/2010/06/inside-story-on-new-style-classes.html
def __eq__(self,other):
return self.__dict__== other.__dict__
should work
I've run into some confusing behaviour of the magic comparison methods.
Suppose we have the following class:
class MutNum(object):
def __init__ (self, val):
self.val = val
def setVal(self, newval):
self.val = newval
def __str__(self):
return str(self.val)
def __repr__(self):
return str(self.val)
# methods for comparison with a regular int or float:
def __eq__(self, other):
return self.val == other
def __gt__(self, other):
return self.val > other
def __lt__(self, other):
return self.val < other
def __ge__(self, other):
return self.__gt__(other) or self.__eq__(other)
def __le__(self, other):
return self.__lt__(other) or self.__eq__(other)
The class does what it is supposed to do, comparing a MutNum object to a regular int or float is no problem. However, and this is what I don't understand, it even compares fine when the magic methods are given two MutNum objects.
a = MutNum(42)
b = MutNum(3)
print(a > b) # True
print(a >= b) # True
print(a < b) # False
print(a <= b) # False
print(a == b) # False
Why does this work? Thanks.
It evaluates as follows (using a repr-like notation instead of referring to variables):
MutNum(42) > MutNum(3)
=> MutNum(42).__gt__(MutNum(3))
=> MutNum(42).val > MutNum(3)
=> 42 > MutNum(3)
And from there, it's just the int-MutNum comparision you already know works.
If you throw in some print's and/or sys.stderr.write's, I think you'll see what's happening. EG:
def __gt__(self, other):
sys.stderr.write('__gt__\n')
sys.stderr.write('{}\n'.format(type(other)))
sys.stderr.write('{} {}\n'.format(self.val, other))
result = self.val > other
sys.stderr.write('result {}\n'.format(result))
return result
def __lt__(self, other):
sys.stderr.write('__lt__\n')
sys.stderr.write('{}\n'.format(type(other)))
sys.stderr.write('{} {}\n'.format(self.val, other))
result = self.val < other
sys.stderr.write('result {}\n'.format(result))
return result
When you try to compare self.val (an int) to other (a MutNum), python realizes it has nothing for comparing an int to a MutNum, and reverses the order of the comparison, and compares a MutNum to an int - which is something you've defined. That is, a single > comparison is doing the > as you'd expect, but it's also doing a <.
Let's say we have a class:
NOTE: this is a dummy class only.
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)
And we want to use this modify method to change values in our object:
myclass = C()
myclass = myclass.modify(a=10)
But when I want to change the value based on the original one, I have to write this:
myclass = C()
myclass = myclass.modify(a=myclass.a/10)
Or:
myclass = myclass.modify(a=abs(myclass.a))
My question is, is there a way, to create a global variable in a module, that I can import and use it as a placeholder for current value, so I can use this formula:
from globvars import current
myclass = C()
myclass = myclass.modify(
a=abs(current) % current ** 2,
b=current//2,
c=bool(current)
)
First I tried to a create a class, which will store the operation it is taking and a value, and modify() will look first for its variable as a keyword and then execute the function. Actually it is only working for simple situations like: current+10 or current**2.
But when I realised, I want to use this current for example with an hsba(current) (color converter) function, where current is pointing to an object stored in an other object, I just give up, I can't write this to every class I'm going to use..
Is there a solution for this? Maybe it's quite easy, I just can't see it :)
Thanks in advance for replies!
Here is a working solution. It is not complete and full of pretty bad design choices, but I hope it helps.
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)
And you can do something like:
obj = YourObj(a=4, b=5)
obj(a=current - 4 + current * current)
This is basically an expression interpreter embedded in python's math operations.
Whenever you use an operation on current (like +), it will return an Expr (because it overrides __add__ and __radd__) that will register which operation this is, and what are each of its operands. These expressions can be nested, so if you say current + 4 - current, it will return Expr(op='-', left=Expr(op='+', left='current', right=4), right='current').
An expression can then be evaluated by calling it like a function and passing it the value that should replace 'current'. When you evaluate an expression, it will:
replace all the occurences of 'current' by the value passed
recursively evaluate the nested functions
return the end result of the whole expression
When you do obj(a=current + 4), the __call__ method of YourObj is called. It will evaluate the expression resulting of current + 4 and store it in a.
I hope this is clearer. Maybe I should rename some of the 'current' to make it less confusing.
Your modify method could take the name of the attribute to modify, and a function that takes the current value of the attribute and returns its new computed value. Then you can do something like:
def compute_new_value(current):
new_value = abs(current) % current ** 2
return new_value
myclass = C()
myclass.modify('a', compute_new_value)
For simple cases, lambda makes it less verbose:
myclass.modify('a', lambda cur: cur + 4)
And your class:
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)
Edit: I may have missed something. Since you're writing myclass = myclass.modify..., should the modify method return a copy of the object ?
You have a poor design, in my opinion, but you could do this using eval(). Of course, that just makes your design smell even more. Still...
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")
The downside is that you have to pass the expressions as strings. Also, with this simple implementation, you can only use values defined on the instance in the expression (e.g., you cant access class attributes, or any attributes of parent classes, or globals). You could add the ability to use class attributes or globals and even parent classes by building the v dictionary in the appropriate order, of course:
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
If you want a current variable that holds the current value, you could use this (inside the loop, since it needs to change for each attribute processed):
v["current"] = getattr(self, name, None)
One of the biggest drawbacks here is that you can't easily access variables from the caller's scope, although you could dig them out of the stack frame I guess... ugh. Or make the caller interpolate those into the string... double ugh.
Morphyn's answer is the proper way to do it, in my opinion. A lambda is hardly complicated
This was my old solution.. (sort of, this a dummy version of it)
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)
And the usage:
MyObj = C()
MyObj(a = -2)
MyObj(a = abs(current))
MyObj(a = 2 ** current)
You have a Python class which needs an equals test. Python should use duck-typing but is it (better/more accurate) to include or exclude an isinstance test in the eq function? For example:
class Trout(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
return isinstance(other, Trout) and self.value == other.value
Using isinstance in __eq__ methods is pretty common. The reason for this is that if the __eq__ method fails, it can fallback on an __eq__ method from another object. Most normal methods are called explicitly, but __eq__ is called implicitly, so it requires look-before-you-leap more frequently.
EDIT (thanks for the reminder, Sven Marnach):
To make it fallback, you can return the NotImplemented singleton, as in this example:
class Trout(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
if isinstance(other, Trout):
return self.value == other.value
else:
return NotImplemented
Suppose a RainbowTrout knows how to compare itself to a Trout or to another RainbowTrout, but a Trout only knows how to compare itself to a Trout. In this example, if you test mytrout == myrainbowtrout, Python will first call mytrout.__eq__(myrainbowtrout), notice that it fails, and then call myrainbowtrout.__eq__(mytrout), which succeeds.
Using isintsance() is usually fine in __eq__() methods. You shouldn't return False immediately if the isinstance() check fails, though -- it is better to return NotImplemented to give other.__eq__() a chance of being executed:
def __eq__(self, other):
if isinstance(other, Trout):
return self.x == other.x
return NotImplemented
This will become particularly important in class hierarchies where more than one class defines __eq__():
class A(object):
def __init__(self, x):
self.x = x
def __eq__(self, other):
if isinstance(other, A):
return self.x == other.x
return NotImplemented
class B(A):
def __init__(self, x, y):
A.__init__(self, x)
self.y = y
def __eq__(self, other):
if isinstance(other, B):
return self.x, self.y == other.x, other.y
return NotImplemented
If you would return False immediately, as you did in your original code, you would lose symmetry between A(3) == B(3, 4) and B(3, 4) == A(3).
The "duck-typing" principle is that you don't care what other is, as long as it has a value attribute. So unless your attributes share names with conflicting semantics, I'd suggest doing it like this:
def __eq__(self, other):
try:
return self.value == other.value
except AttributeError:
return False # or whatever
(Alternately you could test whether other has a value attribute, but "it's easier to ask forgiveness than to get permission")