更多Pythonic方法使用try,assert定义自定义__eq__方法,除了

时间:2014-11-06 21:36:02

标签: python

下面的代码(编辑:实际上,事实证明它没有!),但我不喜欢在{{1}之后出现的挂起return True语句阻止。

try: except:

是否有更多pythonic方法为此类编写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 方法?

4 个答案:

答案 0 :(得分:7)

return True没问题。我认为更大的问题是使用assert进行流量控制。如果用户在命令行上将-O传递给python,则断言根本不会运行。你应该写一些更像这样的东西:

for slot in self.__slots__:
    if not hasattr(other, slot) or getattr(self, slot) != getattr(other,slot):
        return False
return True

此外,__slots__需要在课程级别定义才能工作,而不是在__init__内:

class Foo(object):
    __slots__ = ['a', 'b', 'c']

如果你的项目数量可变,你可能根本就不应该使用__slots__

答案 1 :(得分:3)

<罢工>呃,没关系,我明白了。这很明显:

    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

我应该关闭这个问题所以我看起来不太愚蠢。


编辑:不,不好!

感谢大家的帮助,我现在明白这种做法存在很多问题。首先,我不应该使用assert,因为它主要用于测试,并且可以关闭。其次,代码没有给出MySlottedClass(a=1,b=2)==MySlottedClass(a=1,b=2,c=3)的预期结果。

我想出了这样的方式。请注意,类定义重复4次,因此我可以测试下面不同类的对象的比较;但是,在创建实例之前,所有类都是相同的。另请注意,在实际用例中,我使用元类自动生成这些类(并且__eq__被定义为该元类的一部分)。

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

以下是测试程序:

##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

然而,在测试Joran Beasley's answer之后,我发现令我惊讶的是它产生了上面的IDENTICAL结果,代码更短更敏感。因此,实现此目的的最佳方法似乎是简单地比较两个__dict__属性。

答案 2 :(得分:2)

好像你正在尝试重新创建namedtuple。使用namedtuple将允许动态创建类,测试相等性和其他有趣的东西。缺点是,由于元组是不可变的,因此要命名为元组,您必须创建一个新对象而不是更新属性。 namedtuples不会检查您的广告位的顺序,因此您必须按字典顺序订购广告位,或添加自己的__eq__方法来说明广告位顺序。

使用示例:

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()))

如果你的插槽和可变性的顺序很重要,那么以下内容将起作用(对于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

有人指出__slots__在几乎所有情况下都是过度杀伤力。 Guido Van Rossum表示,基于对新风格类中属性查找性能的毫无根据的担忧,它们是过早的优化。 Guido还指出,当您需要创建批次小对象时,__slots__可以减少程序的内存占用量。

  

我担心[新]级系统的所有变化都会对性能产生负面影响。 ...因此,__slots__的使用是优化数据属性查找的一种方法 - 如果你愿意,可以使用后备,以防人们对新类系统的性能影响感到失望。事实证明这是不必要的,但到那时删除__slots__当然为时已晚。

http://python-history.blogspot.co.uk/2010/06/inside-story-on-new-style-classes.html

答案 3 :(得分:1)

def __eq__(self,other):
    return self.__dict__== other.__dict__

应该有效