0

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

4 回答 4

2

就个人而言,我想看看你如何实现unicode你想要的工作方式,而不依赖于子类的一些实现细节。

如果您尝试(出于显而易见的原因,这是一个伪代码):

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

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

没有理由只为某些未知的不太可能的子类以某种特定方式执行额外的工作。我不认为unicode被设计为子类;如果您确实将其子类化,并且想要与基类不同的行为,请继续并自己覆盖相关方法。

于 2013-07-03T14:08:13.113 回答
1

你没有覆盖.stripand __add__,默认情况下它仍然返回 unicode 对象而不是你的类的实例。这是剥离功能(2.7.3)的来源。至于为什么开发人员决定从 unicode 方法返回 Py_UNICODE 对象,而不是先检查子类并返回?我认为这里的想法是给你足够的时间自己做。

于 2013-07-03T14:07:50.323 回答
0

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

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

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

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

class MyUnicode(object):
    def __init__(self, u):
        self.u = unicode(u)
于 2013-07-03T14:25:03.460 回答
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. Simpleautocast_creator用于使用MyUnicode应该返回MyUnicode而不是返回的方法填充类unicode
3.更复杂一点autocast_reverse的方法也用于提供反向方法(就像__radd__第一个操作数是unicode和第二个操作数时需要的那样MyUnicode
这样,您不必手动覆盖所有方法 - 只需将它们列在autocast_methods元组中。

背景资料:

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

在现实世界中,一群大象总是一群动物。这里没有疑问。
但是一群动物可能是一群大象,但也可以是一群不同的动物,甚至可能没有大象。
这是因为任何一群大象都是一种特殊的动物群。所以
它可以通过定义ElephantGroup为. 怎么能延长?例如通过定义新字段和新方法。 考虑这么简单的操作。 预期结果的类别是什么?AnimalGroup
ElephantGroupAnimalGroupivory_weighttoot()
ElephantGroup() + AnimalGroup()
AnimalGroup- 这里没有魔法,老鼠、海豚等不会变成大象。老鼠和海豚不提供象牙,它们不会鸣叫,所以强迫它们这样做不是预期的行为。

让我们回到MyUnicodeunicode
机器在矩阵或终结者中呈现的含义并不智能。
Python 解释器不明白MyUnicode.
考虑一个扩展unicodestr(在这里并不重要)的类,它被命名EmailAddress并旨在保存电子邮件地址。毫不奇怪 :)
我们现在有一个代码片段:

a = EmailAddress(u'example@example.com')
b = u'/!\n'
c = a + b
d = b + a

仍然期待cd成为EmailAddress? (或者MyUnicode?)
如果您回答,那么请告诉我:
1. 如果EmailAddress.__init__(...)包含精心设计的逻辑检查参数是否可能是有效的电子邮件地址,该怎么办?如果不是,它会引发异常......
2.解释器如何知道它可以安全地MyUnicode任何 unicode实例初始化实例而不执行__init__?还请记住这是 Python,__init__甚至可以在运行时动态更改。
请记住——我们总是可以将实例转换为它的任何祖先。无法隐式执行反向操作。如果解释器将object实例隐式转换为objects子类。
strip()方法遵循相同的规则 -unicode从原始字符中删除字符并unicode返回对新实例的引用(除非存在完全相同的实例unicode- 在这种情况下返回对现有实例的引用)。

引用实例类:

在您的一条评论中,您所说cls的指的是启动执行链的实例类......
cls是一种命名约定,用于metaclasses,classmethods和 in__new__()方法,表示我们没有实例- 我们只有一个类。
事实上,在任何这些情况下我们都无法访问实例 -__new__()但是在大多数情况下应该返回新实例。

我猜你在考虑identifier.__class__属性。它也与执行链无关。它指向由 引用的实例的实际类identifier
为什么您期望使用它的方法unicodestr使用它来创建子类?
将某些内容隐式转换为它的子类不是预期的行为 - 我知道,您的代码中的一个操作数是,MyUnicode但另一个是unicode- 即使在 中strip(),默认参数是unicode包含空白字符。

一些 unicode 实现细节:

Pythonunicodestring类型是不可变的和唯一的(解释来了)。不可变意味着对它们的任何修改操作分别返回或的其他实例。其他实例意味着新实例,但正如我所说,这些类型是唯一的。 这是什么意思?见代码:unicodestring

a = u'aaa'
b = u'aaa'

这里发生了什么?
有一个created 的新实例unicode初始化a。找到要初始化
的对象,因此没有创建新实例。 相反,对象持有的引用计数器增加了。 unicodeb
unicodeu'aaa'

现在,当我们知道它时,请考虑以下代码:

a = u'aaa'
b = MyUnicode(u'aa')
c = b + u'a'

c变量中究竟存储了什么?对unicodeobject 的引用 - 由 引用的同一对象a
为什么更改c不会影响a?因为unicode不可变的,底层对象保持不变。
如果下一行是c = c + u'b',那么c将获得对新/其他实例的引用,并且引用的对象a将减少它的引用计数器。

结论:

Pythonunicodestr类是一致的和可预测的。
由于优化、特殊目的或实现细节,有些类型很难派生。
unicode并且str不打算被子类化,尽管它可以通过例如metaclass我的代码片段来实现。

与往常一样,我正在寻找任何建设性的批评和评论。

祝你好运!

于 2013-07-03T14:27:10.930 回答