【问题标题】:Accessing the name of an instance in Python for printing在 Python 中访问实例的名称以进行打印
【发布时间】:2010-08-22 22:09:58
【问题描述】:

因此,作为“像计算机科学家一样思考”中问题 17.6 的一部分,我编写了一个名为 Kangaroo 的类:

class Kangaroo(object):

    def __init__(self, pouch_contents = []):
        self.pouch_contents = pouch_contents

    def __str__(self):
        '''
        >>> kanga = Kangaroo()
        >>> kanga.put_in_pouch('olfactory')
        >>> kanga.put_in_pouch(7)
        >>> kanga.put_in_pouch(8)
        >>> kanga.put_in_pouch(9)
        >>> print kanga
        "In kanga's pouch there is: ['olfactory', 7, 8, 9]"
        '''

        return "In %s's pouch there is: %s" % (object.__str__(self), self.pouch_contents)

    def put_in_pouch(self, other):
        '''
        >>> kanga = Kangaroo()
        >>> kanga.put_in_pouch('olfactory')
        >>> kanga.put_in_pouch(7)
        >>> kanga.put_in_pouch(8)
        >>> kanga.put_in_pouch(9)
        >>> kanga.pouch_contents
        ['olfactory', 7, 8, 9]
        '''

        self.pouch_contents.append(other)

让我发疯的是,我希望能够编写一个字符串方法,该方法可以通过__str__ 下的单元测试。我现在得到的是:

In <__main__.Kangaroo object at 0x4dd870>'s pouch there is: ['olfactory', 7, 8, 9]

基本上,我想知道我是否可以在 kanga = Kangaroo 上执行某些功能,使得该功能的输出是这 5 个字符,即 function(kanga) -> "kanga"。

有什么想法吗?

编辑: 阅读第一个答案让我意识到有一种更简洁的方式来问我原来的问题。有没有办法重写__init__,使以下代码在编写时有效?

>>> somename = Kangaroo()
>>> somename.name
'somename'

【问题讨论】:

  • 看起来第一个答案消失了?!
  • @John Machin 也许我暗示黑客的恶臭对海报来说是可恶的;)
  • @aaronasterling 我删除了你的答案吗?我什至不知道我能做到这一点。如果是这样,完全是新手事故。对不起,伙计。
  • @tel,不,不是你:你不能那样做。我对答案发表了评论,提出了一种非常老套的方法来做你想做的事,但它并没有给出答案。我怀疑这就是他们删除它的原因。
  • @tel:offtopic,但您上面示例中的pouch_contents = [] 可能不会按照您的意愿行事。见the second "classic blunder" in this post

标签: python string printing class-method


【解决方案1】:

为了正确看待您的请求,请说明您希望将此代码创建的对象附加到什么名称:

marsupials = []
marsupials.append(Kangaroo()) 

This classic essay by the effbot 给出了很好的解释。

在您的编辑中回答修改后的问题:否。

现在您已经在评论中说得很清楚了,并说此命名练习的全部目的是区分与您的可变默认参数相关联的用于调试目的的对象:

至少在 Python 的 CPython 实现中,在任何给定时间,所有现有对象都有一个唯一的 ID,可以通过id(obj) 获得。这可能足以满足您的调试目的。请注意,如果删除了一个对象,则该 ID(即内存地址)可以被随后创建的对象重新使用。

【讨论】:

  • 在您的有袋动物示例中,我希望它只使用一些默认值,例如“roo”。我不是想为 .name 分配一个唯一标识符,只是一个要放入 __str__ 方法的字符串。我认为这是明确的。如果那是不可能的,好吧,那让我有点难过,但我想解决方法很明显。
  • @tel:阅读我链接到的文章。让您的用户决定他们想要什么名称(如果有)。
【解决方案2】:

我不打算发布此内容,但如果您只想将其用于调试,那么您就去吧:

import sys

class Kangaroo(object):
    def __str__(self):
        flocals = sys._getframe(1).f_locals
        for ident in flocals:
            if flocals[ident] is self:
                name = ident
                break
        else:   
            name = 'roo'
        return "in {0}'s pouch, there is {1}".format(name, self.pouch_contents)

kang = Kangaroo()
print kang

这依赖于 CPython (AFAIK),不适合生产代码。如果实例位于任何类型的容器中并且可能随时因任何原因失败,它将无法工作。不过,它应该为您解决问题。

它的工作原理是从堆栈帧中取出 f_locals 字典,该字典表示调用 print kang 的命名空间。 f_locals 的键是框架中变量的名称,因此我们只需循环遍历它并测试每个条目是否为self。如果是这样,我们break。如果break 没有被执行,那么我们没有找到一个条目并且循环else 子句按要求分配值'roo'。

如果您想从某种容器中取出它,您需要扩展它以查看f_locals 中的任何容器。如果它是一个 dictlike 容器,你可以返回 key,如果它是一个 tuple 或 list,你可以返回 index。

【讨论】:

  • 哇,这真是太丑陋和骇人听闻了。我会让它缓存名称。而且我永远不会让这个在生产代码中。但是哇,这令人印象深刻,令人不安。
  • 我过去曾使用过这样的代码(甚至更骇人听闻)进行调试。不过,您需要在 __init__ 期间存储名称,否则例如,如果将袋鼠传递给函数,您将只获取函数内部参数的名称。
  • @gnibbler,我不知道如何在 __init__ 函数中获取名称,因为当 __init__ 运行时,kang 没有输入到 f_locals 中。我错过了什么吗?
  • 我想知道如何实现框架。感谢您发布此内容。
【解决方案3】:
class Kangaroo(object):

    def __init__(self, pouch_contents=None, name='roo'):
        if pouch_contents is None:
            self.pouch_contents = []  # this isn't shared with all other instances
        else:
            self.pouch_contents = pouch_contents
        self.name = name
    ...

kanga = Kangaroo(name='kanga')

请注意,在参数中在= 周围放置空格是一种很好的风格

【讨论】:

  • 这是正确的做法。我希望我可以将您的答案和约翰·马钦的答案结合起来。 :-)
  • 在阅读 bstpierre 的评论后,我改变了 pouch_contents 的初始化方式。这是您发现的少一个错误
  • @bstpierre 没有阅读 OP 的原始预编辑问题文本,其中 OP 表示他对可变默认 arg 问题了如指掌。 OP 的回复:“@bstpierre 练习的全部目的是检查那个错误,所以我知道。但你是对的,这可能很讨厌”。我在编辑我的答案时讨论了这个问题。你的编辑离题了。
【解决方案4】:

你想要的在 Python 中基本上是不可能的,即使有建议的“hacks”。例如, 下面的代码会打印什么?

>>> kanga1 = kanga2 = kanga3 = Kangaroo()
>>> kanga2.name 
???
>>> kanga3.name
???

>>> l = [Kangaroo()]
>>> l[0].name
???

如果您想要“命名”对象,只需为您的对象提供一个名称

def __init__(self, name):
    self.name = name

更明确(我们喜欢 Python)并且在所有情况下都保持一致。当然你可以做类似的事情

>>> foo = Kangaroo("bar")
>>> foo.name
'bar'

但 foo 只是实例可能拥有的众多标签之一。该名称是明确且永久的。如果您愿意,您甚至可以强制执行唯一命名(同时您可以为不同的对象尽可能多地重用变量)

【讨论】:

  • 这是出于调试目的。出于调试目的而更改类接口有点奇怪,你不觉得吗?您给出的第一个案例是未定义的,它将打印“kanga1”或“kanga2”或“kanga3”。第二种情况只需要稍微多一点的工作,但你可以让它打印 'l-1' 例如。
【解决方案5】:

当我开始研究这个问题时,我还没有看到 aaronasterling 的骇人听闻的答案,但无论如何,这是我自己的骇人听闻的答案:

class Kangaroo(object):

    def __init__(self, pouch_contents = ()):
        self.pouch_contents = list(pouch_contents)

    def __str__(self):
        if not hasattr(self, 'name'):
            for k, v in globals().iteritems():
                if id(v) == id(self):
                    self.name = k
                    break
                else:
                    self.name = 'roo'                    
        return "In %s's pouch there is: %s" % (self.name, self.pouch_contents)

kanga = Kangaroo()
print kanga

你可以通过看起来很有趣来打破它(它按书面形式工作,但作为 doctest 的一部分会失败),但它确实有效。在我的学习经历中,我更关心什么是可能的,而不是什么是实用的,所以我很高兴看到至少有两种不同的方法可以做我认为应该可以做的事情。即使它们是坏方法。

【讨论】:

  • 你真的应该在 that 循环中添加一个 else 子句以提供默认值,以防它找不到任何东西;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-07
  • 1970-01-01
  • 2020-06-11
相关资源
最近更新 更多