【问题标题】:inheritance from str or int从 str 或 int 继承
【发布时间】:2011-02-10 01:07:08
【问题描述】:

为什么我在创建继承自 str(或也继承自 int)的类时遇到问题

class C(str):
   def __init__(self, a, b):
     str.__init__(self,a)
     self.b = b

C("a", "B")

TypeError: str() takes at most 1 argument (2 given)

如果我尝试使用int 而不是str,也会发生同样的情况,但它适用于自定义类。我需要使用__new__ 而不是__init__?为什么?

【问题讨论】:

    标签: python string inheritance integer new-operator


    【解决方案1】:
    >>> class C(str):
    ...     def __new__(cls, *args, **kw):
    ...         return str.__new__(cls, *args, **kw)
    ... 
    >>> c = C("hello world")
    >>> type(c)
    <class '__main__.C'>
    
    >>> c.__class__.__mro__
    (<class '__main__.C'>, <type 'str'>, <type 'basestring'>, <type 'object'>)
    

    由于__init__是在对象构造完成后调用的,所以修改不可变类型的值已经来不及了。注意__new__是一个类方法,所以我调用了第一个参数cls

    更多信息请参见here

    >>> class C(str):
    ...     def __new__(cls, value, meta):
    ...         obj = str.__new__(cls, value)
    ...         obj.meta = meta
    ...         return obj
    ... 
    >>> c = C("hello world", "meta")
    >>> c
    'hello world'
    >>> c.meta
    'meta'
    

    【讨论】:

    • 我用你的回答来帮助回答我自己的 SO 问题here。谢谢!
    【解决方案2】:

    继承内置类型很少值得。你必须处理几个问题,你并没有真正得到太多好处。

    使用合成几乎总是更好。您可以保留 str 对象作为属性,而不是继承 str

    class EnhancedString(object):
         def __init__(self, *args, **kwargs):
             self.s = str(*args, **kwargs)
    

    您可以手动或使用 __getattr__ 自动推迟您想要在底层 str self.s 上工作的任何方法。

    话虽如此,需要您自己的字符串类型应该让您停下来。有许多类应该将字符串存储为它们的主要数据,但您通常希望使用strunicode(如果您表示文本,则为后者)作为字符串的一般表示。 (一个常见的例外是如果您需要使用 UI 工具包的字符串类型。)如果您想为字符串添加功能,请尝试使用 对字符串进行操作的函数 而不是 用作字符串的新对象,这使您的代码更简单,并且与其他所有人的程序更兼容。

    【讨论】:

    • @wise,要使用__getattr__ 将其他未定义的属性推迟到组合中的特定属性,可以通过def __getattr__(self, name): return getattr(self.s, name) 之类的代码来完成。请注意,不一定推荐使用此代码,但在某些情况下可以很好地工作;在合成中手动定义你想要的东西通常会更好。
    • > "继承内置类型很少值得。"这可能是真的,但并非总是如此。有时创建“标记字符串”类型很有用,例如从解析器生成不同类型的类似字符串的标记。然而,它的问题是(我认为)你失去了短字符串性能优化。
    • 我不相信。你能指出为什么你认为从 str 继承是一个潜在的错误来源吗?因为它不是惯用语?
    • docs.python.org/3/library/… "类,UserString 充当字符串对象的包装器。对此类的需求已被直接从 str 子类化的能力部分取代;"这不是那么单调。
    • @blais,假设您执行your_subclass[:5]your_subclass.replace(x, y)——它会返回什么? str 的实例还是您的子类的实例?这甚至没有定义,但实际上它会返回str。无法保证任何方式,这种未定义的行为是一件坏事。
    【解决方案3】:

    当你实例化一个类时,你传入的参数被传递给__new__(构造函数)和__init__(初始化器)类的方法。因此,如果您从一个对实例化期间可能提供的参数数量有限制的类继承,您必须保证它的__new____init__ 都不会获得比预期更多的参数。这就是你遇到的问题。你用C("a", "B") 实例化你的类。解释器在C 中寻找__new__ 方法。 C 没有它,所以 python 窥视它的基类 str。并且由于它有一个,因此使用并提供了两个参数。但是str.__new__ 期望只得到一个参数(除了它的类对象作为第一个参数)。所以TypeError 被提出。这就是为什么你必须在你的子类中扩展它,就像你对__init__ 所做的那样。但请记住,它必须返回类实例,并且它是一个 static 方法(无论它是否使用 @staticmethod 装饰器定义),如果您使用 super 函数,则该方法很重要。

    【讨论】:

    • 要验证,请在提示符处尝试:&gt;&gt;&gt; str.__init__('abc', 'def')。这无济于事。但是,&gt;&gt;&gt; str.__new__(str, 'abc', 'def') 抛出异常:TypeError: str() takes at most 1 argument (2 given)。这有点令人困惑,因为我们用 3 个参数而不是 2 个参数调用了 str.__new__(..)。这是否意味着 str.__new__(..) 接受任意数量的参数,然后调用 str(..)?可能不会,因为str(..) 会依次调用str.__new__(..)...
    • @Evgeni: str 类对象根本没有实现 __init__ 方法。 basestringstr 的基类)也没有。 __init__ 直接继承自 object 并且接受任意数量的参数。相反,__new__ 方法实际上是在str 中实现的,它最多接受一个参数,除了第一个参数必须是str 或其子类。为__new__basestringobject 尝试&gt;&gt;&gt; '__init__' in str.__dict__ 等表达式。
    • 这是检查实现了哪些方法的好方法。我仍然有点困惑为什么要写str.__new__(str, 'abc', 'def'),它说我提供了 2 个参数。我在那里数了三个。对于示例class C(object):\n @staticmethod \n def m(a): pass,如果我将其称为C.m(1, 2),它将按预期显示TypeError: m() takes exactly 1 argument (2 given)。我猜__new__ 会产生一个令人困惑的消息,因为__new__ 是特殊的(例如,你不需要在实现它时添加@staticmethod)。
    【解决方案4】:

    在不可变类型的情况下使用__new__

    class C(str):
        def __new__(cls, content, b):
            return str.__new__(cls, content)
    
        def __str__(self):
            return str.__str__(self)
    
    a=C("hello", "world")
    print a
    

    打印返回hello

    Python 字符串是不可变类型。调用函数__new__ 来创建对象C 的新实例。 python __new__ 函数的存在基本上是为了允许从不可变类型继承。

    【讨论】:

    • type(C("a", "B")) -> NoneType
    • 你试试你的答案吗?现在它返回 ''
    • 你检查过帖子的更新了吗?它已根据您的要求进行了更新。您在进行更改后投了反对票。此外,在您的评论中,您期望我的代码中的功能在您的问题中没有被要求(让 __str__() 函数为继承的类工作)!
    • 第一次执行后代码返回NoneType,第二次执行后返回""。哪个没有要求的功能?我没有问__str__ 函数,也没有必要,因为该类继承自str,请参阅 gnibbler 答案。现在我无法修改否决票,我需要您编辑您的答案。
    【解决方案5】:

    在仔细阅读this 之后,这里是另一个子类化 str 的尝试。与其他答案的变化是使用 super(TitleText, cls).__new__ 在正确的类中创建实例。这个似乎在使用时表现得像一个 str ,但它允许我覆盖一个方法:

    class TitleText(str):
        title_text=""
        def __new__(cls,content,title_text):
            o=super(TitleText, cls).__new__(cls,content)
            o.title_text = title_text
            return o
    
        def title(self):
            return self.title_text
    
    >>> a=TitleText('name','A nice name')
    >>> a
    'name'
    >>> a[0]
    'n'
    >>> a[0:2]
    'na'
    >>> a.title()
    'A nice name'
    

    这可以让您正确地进行切片和下标。这是用来干嘛的?用于在管理索引页面中重命名 Django 应用程序。

    【讨论】:

    • 呸!您的repr 具有欺骗性这一事实导致不清楚aTitleTexta[0]a[0:2] 是str(今天,在当前版本中,作为实现细节)。如果你编写了一个没有继承 str 的类,你可以做一个更有用、更少混淆和欺骗性的东西。
    【解决方案6】:

    上面已经回答了这个问题,这只是一个切线观察,可能对某人有用。

    我在试图找出一种在删除临时文件后删除临时文件的方法时遇到了这个问题。

    上下文是一个 Flask 会话:用户可以上传一些文件,但在有效提交将他/她的数据传输到最终目的地所必须经历的整个工作流程之前就放弃了。在那之前,我将文件保存在一个临时目录中。假设用户放弃并关闭浏览器窗口,我不希望这些文件徘徊。

    由于我在 Flask 会话中保留了临时路径——它只是一个最终会被删除的字典(例如,超时),我可以自定义一个 str 类来保存临时目录地址/路径,并拥有它的__del__ 处理临时目录删除的方法。

    这里是:

    class Tempdir(str):
        def __new__(cls, *args, **kwargs):
            from tempfile import mkdtemp
            _dir = mkdtemp()
            return super().__new__(cls, _dir)
        def __del__(self):
            from shutil import rmtree
            rmtree(str(self))
    

    在你的 python 解释器/应用程序中实例化它:

    > d = Tempfile()
    > d
    '/var/folders/b1/frq3gywj3ljfqrf1yc7zk06r0000gn/T/tmptwa_g5fw'
    > 
    > import os
    > os.path.exists(d)
    True
    

    当你退出解释器时:

    $ ls /var/folders/b1/frq3gywj3ljfqrf1yc7zk06r0000gn/T/tmptwa_g5fw
    ls: /var/folders/b1/frq3gywj3ljfqrf1yc7zk06r0000gn/T/tmptwa_g5fw: No such file or directory
    

    你去。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-31
      • 2019-12-01
      • 2013-04-06
      • 1970-01-01
      • 2011-09-14
      • 2011-03-15
      相关资源
      最近更新 更多