代码之家  ›  专栏  ›  技术社区  ›  bukzor

Python:强制新样式类

  •  8
  • bukzor  · 技术社区  · 15 年前

    我希望这段代码“正常工作”:

    def main():
        c = Castable()
        print c/3
        print 2-c
        print c%7
        print c**2
        print "%s" % c
        print "%i" % c
        print "%f" % c
    

    当然,最简单的方法就是写作 int(c)/3 ,但我想为配置迷你语言启用更简单的perl-ish语法。

    __coerce__ 方法,但旧式类已被弃用,将在python3中删除。

    当我对一个新样式的类执行相同的操作时,我会得到以下错误:

    TypeError: unsupported operand type(s) for /: 'Castable' and 'int'
    

    __胁迫__ 新班级的行为?你可以在下面找到我目前的解决方案,但它相当丑陋和冗长。

    这是相关文件:(我想)

    加分:

        print pow(c, 2, 100)
    
    5 回复  |  直到 15 年前
        1
  •  8
  •   Ned Batchelder    15 年前

    __div__ 如果你愿意的话 c/3 去工作。Python不会先将对象转换为数字。

        2
  •  5
  •   bukzor    15 年前

    这是可行的,并且在经过几次改进(对@jchl的支持)之后就不那么粗俗了,但是它似乎仍然是不必要的,特别是考虑到您可以免费获得“旧式”类。

    我还在寻找更好的答案。如果没有更好的方法,在我看来,这就像Python语言中的回归。

    def ops_list():
        "calculate the list of overloadable operators"
        #<type 'object'> has functions but no operations
        not_ops = dir(object)
    
        #calculate the list of operation names
        ops = set()
        for mytype in (int, float, str):
            for op in dir(mytype):
                if op.endswith("__") and op not in not_ops:
                    ops.add(op)
        return sorted(ops)
    
    class MetaCastable(type):
        __ops = ops_list()
    
        def __new__(mcs, name, bases, dict):
            #pass any undefined ops to self.__op__
            def add_op(op):
                if op in dict:
                    return
                fn = lambda self, *args: self.__op__(op, args)
                fn.__name__ = op
                dict[op] = fn
    
            for op in mcs.__ops:
                add_op( op )
            return type.__new__(mcs, name, bases, dict)
    
    
    class Castable(object):
        __metaclass__ = MetaCastable
        def __str__(self):
            print "str!"
            return "<Castable>"
        def __int__(self):
            print "int!"
            return 42
        def __float__(self):
            print "float!"
            return 2.718281828459045
    
        def __op__(self, op, args):
            try:
                other = args[0]
            except IndexError:
                other = None
            print "%s %s %s" % (self, op, other)
            self, other = coerce(self, other)
            return getattr(self, op)(*args)
    
        def __coerce__(self, other):
            print "coercing like %r!" % other
            if other is None: other = 0.0
            return (type(other)(self), other)
    
        3
  •  3
  •   jchl    15 年前
    class MetaCastable(type):
        __binary_ops = ( 
                'add', 'sub', 'mul', 'floordiv', 'mod', 'divmod', 'pow', 'lshift', 
                'rshift', 'and', 'xor', 'or', 'div', 'truediv',
        )
    
        __unary_ops = ( 'neg', 'pos', 'abs', 'invert', )
    
        def __new__(mcs, name, bases, dict):
            def make_binary_op(op):
                fn = lambda self, other: self.__op__(op, other)
                fn.__name__ = op
                return fn
    
            for opname in mcs.__binary_ops:
                for op in ( '__%s__', '__r%s__' ):
                    op %= opname
                    if op in dict:
                        continue
                    dict[op] = make_binary_op(op)
    
            def make_unary_op(op):
                fn = lambda self: self.__op__(op, None)
                fn.__name__ = op
                return fn
    
            for opname in mcs.__unary_ops:
                op = '__%s__' % opname
                if op in dict:
                    continue
                dict[op] = make_unary_op(op)
    
            return type.__new__(mcs, name, bases, dict)
    
    class Castable(object):
        __metaclass__ = MetaCastable
        def __str__(self):
            print "str!"
            return "<Castable>"
        def __int__(self):
            print "int!"
            return 42
        def __float__(self):
            print "float!"
            return 2.718281828459045
    
        def __op__(self, op, other):
            if other is None:
                print "%s(%s)" % (op, self)
                self, other = coerce(self, 0.0)
                return getattr(self, op)()
            else:
                print "%s %s %s" % (self, op, other)
                self, other = coerce(self, other)
                return getattr(self, op)(other)
    
        def __coerce__(self, other):
            print "coercing like %r!" % other
            return (type(other)(self), other)
    
        4
  •  0
  •   Matt Williamson    15 年前
    class Castable(object):
        def __div__(self, other):
            return 42 / other
    
        5
  •  0
  •   kxr    8 年前

    新样式类比旧样式类运行得更快、更精确。这样就不贵了 __getattr__ , __getattribute__ , __coerce__ 以任何廉价的理由和可疑的顺序打电话。

    旧式的 __胁迫__ coerce 在PY3中丢失。问题示例针对相当广泛的虚拟化。

    然而,我不会像其他答案中所示,仅仅为了这样一个循环或提供一种简单的基类行为而使用一个元类。那就是用大锤敲螺母。


    下面是一个“变体”虚拟化的示例助手:

    def Virtual(*methods):
        """Build a (new style) base or mixin class, which routes method or
        operator calls to one __virtualmeth__ and attribute lookups to
        __virtualget__ and __virtualset__ optionally.
    
        *methods (strings, classes): Providing method names to be routed
        """
        class VirtualBase(object):  
            def __virtualmeth__(self, methname, *args, **kw):
                raise NotImplementedError
        def _mkmeth(methname, thing):
            if not callable(thing):
                prop = property(lambda self:self.__virtualget__(methname),
                                lambda self, v:self.__virtualset__(methname, v))
                return prop
            def _meth(self, *args, **kw):
                return self.__virtualmeth__(methname, *args, **kw)
            _meth.__name__ = methname
            return _meth
        for m in methods:
            for name, thing in (isinstance(m, str) and
                                {m:lambda:None} or m.__dict__).items():
                if name not in ('__new__', '__init__', '__setattr__', ##'__cmp__',
                                '__getattribute__', '__doc__', ):   ##'__getattr__', 
                    setattr(VirtualBase, name, _mkmeth(name, thing))
        return VirtualBase
    

    import operator
    class Anaphor(Virtual(int, float, str)):   
        """remember a sub-expression comfortably:
    
        A = Anaphor()      # at least per thread / TLS
        if re.search(...) >> A:
            print(A.groups(), +A)
        if A(x % 7) != 0:
            print(A, 1 + A, A < 3.0, A.real, '%.2f' % A, +A)
        """
        value = 0
        def __virtualmeth__(self, methname, *args, **kw):
            try: r = getattr(self.value, methname)(*args, **kw)
            except AttributeError:
                return getattr(operator, methname)(self.value, *args, **kw)
            if r is NotImplemented: # simple type -> coerce
                try: tcommon = type(self.value + args[0])    # PY2 coerce
                except: return NotImplemented
                return getattr(tcommon(self.value), methname)(*args, **kw)
            return r
        def __call__(self, value):   
            self.value = value
            return value
        __lshift__ = __rrshift__ = __call__     # A << x;  x >> A
        def __pos__(self):                      # real = +A
            return self.value
        def __getattr__(self, name):
            return getattr(self.value, name)
        def __repr__(self):
            return '<Anaphor:%r>' % self.value
    

    pow() :-) :

    >>> A = Anaphor()
    >>> x = 1
    >>> if x + 11 >> A:
    ...     print repr(A), A, +A, 'y' * A, 3.0 < A, pow(A, 2, 100)
    ...     
    <Anaphor:12> 12 12 yyyyyyyyyyyy True 44
    
    推荐文章