Python基本类型不计算继承

时间:2013-07-03 14:06:24

标签: python inheritance types

Python基本类型不像我预期的那样计算继承。例如:

class MyUnicode(unicode):
    pass

mu = MyUnicode('xxx')

>>> type(mu)
<class '__main__.MyUnicode'> # ok

>>> type(mu + 'x')
<type 'unicode'> # why not <class '__main__.MyUnicode'> ?

>>> type(mu.strip())
<type 'unicode'> # why not <class '__main__.MyUnicode'> ?

字符串是不可变的,那么这两个方法必须返回新对象。但是为什么开发人员在这些方法中硬编码unicode返回类型而不是使用子类? 它是否可以防止一些潜在的缺点,我不知道?

4 个答案:

答案 0 :(得分:2)

我个人希望看到如何实现unicode以您想要的方式工作,而不依赖于子类的某些实现细节。

如果您尝试(由于显而易见的原因,它是伪代码):

def __add__(self, other):
    return type(self)(concatenate(self.string, other.string))

您正在强制该类具有单arg构造函数。为什么?此外,您mu + u'x'u'x' + mu的类型不同,而且保证效率也会降低。

没有理由仅为某些未知的不太可能的子类做某些特定方式的其他工作。我不认为unicode被设计为子类;如果你做了它的子类化,并且想要一个与你的基类不同的行为,那就继续并自己覆盖相关的方法。

答案 1 :(得分:1)

您没有覆盖.strip__add__,默认情况下仍会返回unicode对象,而不是您的类实例。以下是条带功能的source(2.7.3)。至于为什么开发人员决定从unicode方法返回Py_UNICODE对象而不是先检查子类并返回它?我认为这里的想法是你得到的就足够了。

答案 2 :(得分:0)

因为Python倾向于简单化,所以在处理内置类型时通常不会知道子类。

在主要实现(用C编写)中,更容易将某些内容转换为Py_UCS4PyListObject或C中定义的其他类型,而不是动态获取类型并调用构造函数(可能与超类构造函数不兼容并崩溃)。

经验法则是,如果要完全替换内置类,则替换所需的所有方法,但我建议不要这样做。

很多时候,只需将它作为类的属性并根据需要添加方法就更简单了:

class MyUnicode(object):
    def __init__(self, u):
        self.u = unicode(u)

答案 3 :(得分:0)

经过OP的多次评论后,我决定从头开始重写我的答案。

可能的解决方法:

class MyUnicodeMetaClass(type):

    autocast_methods = ('__add__', '__radd__', 'format')

    def __init__(cls, name, bases, attrs):   
        super(MyUnicodeMetaClass, cls).__init__(name, bases, attrs)
        for method_name in MyUnicodeMetaClass.autocast_methods:
            try:
                setattr(cls, method_name, cls.autocast_creator(method_name))
            except AttributeError:
                if method_name.startswith('__r'):
                    setattr(cls, method_name, cls.autocast_reverse(method_name))
                else:
                    raise

    def autocast_creator(cls, method_name):
        method = unicode().__getattribute__(method_name)
        def autocast_method(self, *args, **kwargs):
            method = unicode(self).__getattribute__(method_name)
            return cls(method(*args, **kwargs))
        return autocast_method

    def autocast_reverse(cls, method_name):
        method_name = method_name.replace('__r', '__', 1)
        def autocast_method(self, *args, **kwargs):
            method = unicode(args[0]).__getattribute__(method_name)
            return cls(method(self, *args[1:], **kwargs))
        return autocast_method


class MyUnicode(unicode):
    __metaclass__ = MyUnicodeMetaClass

a = MyUnicode(u'aaa {0}')
print a, type(a)
# aaa {0} <class '__main__.MyUnicode'>
b = a + u'bbb'
print b, type(b)
# aaa {0}bbb <class '__main__.MyUnicode'>
c = u'ddd' + a
print c, type(c)
# cccaaa {0} <class '__main__.MyUnicode'>
d = a.format(115)
print d, type(d)
# aaa 115 <class '__main__.MyUnicode'>

可能需要扩展但基础骨架已准备就绪。
这里发生了什么事? 1. Metaclass用于改变MyUnicode类的创建 2.简单autocast_creator用于使用应返回MyUnicode而不是MyUnicode的方法填充unicode类。 3. 更复杂autocast_reverse也用于提供反向方法(如__radd__,当第一个操作数为unicode且第二个操作数为MyUnicode时需要{1}})
这样,您就不必手动覆盖所有方法 - 只需在autocast_methods元组中列出它们。

背景资料:

继承:
面向对象编程旨在尽可能地反映真实的单词 继承在这里也不例外。

在现实世界中,一群大象总是一群动物。这里毫无疑问。
但是一群动物可能是一群大象,但它也可能是一群不同的动物,甚至在这个群体中也没有大象。
这是因为任何一群大象都是一种特殊的动物群 因此,通过将ElephantGroup定义为AnimalGroup子类,可以将其反映在计算机程序中。
ElephantGroup如何延长AnimalGroup?例如,通过定义新字段ivory_weight和新方法toot() 考虑这样一个简单的操作ElephantGroup() + AnimalGroup() 什么是预期结果? AnimalGroup - 这里没有魔法,老鼠,海豚等不会成为大象。老鼠和海豚不能提供象牙,它们不会嘟嘟嘟嘟,所以强迫它们这样做不是预期的行为。

让我们回到MyUnicodeunicode 机器在Matrix或终结者的意义上并不聪明 Python解释器并不了解MyUnicode的目的是什么 考虑一个名为unicode的{​​{1}}或str(此处并不重要)的类,用于保存电子邮件地址。毫不奇怪:)
我们现在有一个代码片段:

EmailAddress

仍然希望a = EmailAddress(u'example@example.com') b = u'/!\n' c = a + b d = b + a c成为d的实例? (或EmailAddress?)
如果您已回答,请告诉我:
1.如果MyUnicode包含精心设计的逻辑检查参数是否可能是有效的电子邮件地址,该怎么办?如果它不是......那就引起异常 2.如果解释器能够在不执行EmailAddress.__init__(...)的情况下使用任何 MyUnicode实例安全地初始化unicode实例,该怎么办?还请记住这是Python,__init__甚至可以在运行时动态更改 记住 - 我们总是可以将实例投射到它的任何祖先身上。反向操作不能隐式完成。如果解释器会隐式地将__init__实例强制转换为object子类,那将是一团糟。
objects方法遵循相同的规则 - 从原始字符中删除strip()个字符,并返回对新unicode实例的引用(除非存在完全相同的unicode - 在这种情况下返回对现有的引用。)

参考实例类:

在你的一条评论中,你说unicode指的是启动执行链的实例类......
clsclsmetaclassesclassmethods方法中使用的命名惯例,表示我们没有实例 - 我们只有一个班级 事实上,我们无法在任何这些情况下访问实例 - __new__()但是在大多数情况下应该返回新实例。

我猜你在考虑__new__()属性。它与执行链无关。它指向identifier.__class__引用的实际实例类 为什么期望identifierunicode的方法用它来创建子类? 隐含地向它的子类投射某些东西不是预期的行为 - 我知道,代码中的一个操作数是str,而另一个操作数是MyUnicode - 即使在unicode中也是如此,默认参数是包含空格字符的strip()

一些unicode实施细节:

Python unicodeunicode类型是不可变的,唯一(解释即将到来)。 不可变意味着对它们的任何modyfying操作分别返回其他 stringunicode的实例。
其他实例意味着新实例,但正如我所说,这些类型唯一
这是什么意思?见代码:

string

这里发生了什么? 创建a = u'aaa' b = u'aaa' 新实例以初始化unicode 找到了初始化a的{​​{1}}对象,因此未创建新实例 而是增加了unicode对象持有b的引用计数器。

现在我们知道了,请考虑以下代码:

unicode

u'aaa'变量中究竟存储了什么?对a = u'aaa' b = MyUnicode(u'aa') c = b + u'a' 对象的引用 - c引用的同一对象 为什么更改unicode不会影响a?因为c 不可变且基础对象保持不变 如果下一行是a,则unicode将引用新的/其他实例,c = c + u'b'引用的对象会使其参考计数器减少。

<强>结论:

Python ca类是一致且可预测的 由于优化,特殊目的或实施细节,有些类型难以衍生出来 unicodestr并不打算进行子类化,尽管可以通过我的代码段中的unicode来实现。

一如既往,我一直在寻找任何建设性的批评和评论。

祝你好运!