python:在类_init__()中获取实例名

本问题已经有最佳答案,请猛点这里访问。

在python中构建一个新的类对象时,我希望能够基于类的实例名创建一个默认值,而不需要传递额外的参数。我该怎么做呢?下面是我想要的基本伪代码:

1
2
3
4
5
6
7
8
9
10
class SomeObject():
    defined_name = u""

    def __init__(self, def_name=None):
        if def_name == None:
            def_name = u"%s" % (<INSTANCE NAME>)
        self.defined_name = def_name

ThisObject = SomeObject()
print ThisObject.defined_name   # Should print"ThisObject"


嗯,几乎有一种方法:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python
import traceback
class SomeObject():
    def __init__(self, def_name=None):
        if def_name == None:
            (filename,line_number,function_name,text)=traceback.extract_stack()[-2]
            def_name = text[:text.find('=')].strip()
        self.defined_name = def_name

ThisObject = SomeObject()
print ThisObject.defined_name
# ThisObject

回溯模块允许您查看用于调用SomeObject()的代码。用一个小的字符串争吵,text[:text.find('=')].strip()你可以猜猜def_name应该是什么。

然而,这个黑客是脆弱的。例如,这并不是很好:

1
2
3
4
5
ThisObject,ThatObject = SomeObject(),SomeObject()
print ThisObject.defined_name
# ThisObject,ThatObject
print ThatObject.defined_name
# ThisObject,ThatObject

因此,如果要使用这个hack,必须记住必须调用SomeObject()使用简单的python语句:

1
ThisObject = SomeObject()

顺便说一下,作为使用回溯的进一步例子,如果您定义了

1
2
3
4
5
6
7
def pv(var):
    # stack is a list of 4-tuples: (filename, line number, function name, text)
    # see http://docs.python.org/library/traceback.html#module-traceback
    #
    (filename,line_number,function_name,text)=traceback.extract_stack()[-2]
    # ('x_traceback.py', 18, 'f', 'print_var(y)')
    print('%s: %s'%(text[text.find('(')+1:-1],var))

然后你可以打电话

1
2
3
x=3.14
pv(x)
# x: 3.14

同时打印变量名及其值。


实例没有名称。当全局名ThisObject被绑定到通过计算SomeObject构造函数创建的实例时,构造函数已经运行完毕。

如果希望对象具有名称,只需在构造函数中传递该名称。

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


您可以在类中创建一个方法来检查当前帧中的所有变量,并使用hash()来查找self变量。

这里提出的解决方案将返回指向实例对象的所有变量。

在下面的类中,isinstance()用于在应用hash()时避免问题,因为一些对象,例如numpy.arraylist是不可用的。

1
2
3
4
5
6
7
8
9
10
11
import inspect
class A(object):
    def get_my_name(self):
        ans = []
        frame = inspect.currentframe().f_back
        tmp = dict(frame.f_globals.items() + frame.f_locals.items())
        for k, var in tmp.items():
            if isinstance(var, self.__class__):
                if hash(self) == hash(var):
                    ans.append(k)
        return ans

进行了以下测试:

1
2
3
4
5
def test():
    a = A()
    b = a
    c = b
    print c.get_my_name()

其结果是:

1
2
test()
#['a', 'c', 'b']

这不能工作,想象一下:a = b = TheMagicObjet()。名称对值没有影响,它们只是指向值。


实现这一目标的一个非常非常糟糕的方法就是颠倒责任:

1
2
3
4
5
6
7
class SomeObject():
    def __init__(self, def_name):
        self.defined_name = def_name
        globals()[def_name] = self

SomeObject("ThisObject")
print ThisObject.defined_name

如果你想支持全球范围之外的东西,你就必须做一些更糟糕的事情。


在Python中,所有数据都存储在对象中。此外,可以将名称与对象绑定,然后可以使用该名称查找该对象。

对象的名称与它可能绑定到的名称(如果有的话)没有关系。它可能有几十个不同的名称,或者一个也没有。此外,Python没有任何指向对象到名称的"反向链接"。

考虑一下这个例子:

1
2
3
foo = 1
bar = foo
baz = foo

现在,假设您有一个值为1的整数对象,您想向后计算并找到它的名称。你会打印什么?有三个不同的名称将该对象绑定到它们,它们都是同样有效的。

1
2
print(bar is foo) # prints True
print(baz is foo) # prints True

在Python中,名称是访问对象的一种方式,因此无法直接使用名称。您可以搜索各种名称空间,直到找到与感兴趣的对象绑定的名称,但我不建议这样做。

如何在python中得到变量的字符串表示?

有一个著名的演讲叫做"像Pythonista一样的代码",它总结了这种情况,"其他语言有‘变量’和‘Python有‘名称’"

http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables


受到unutbu和Saullo Castro的答案的启发,我创建了一个更复杂的类,甚至可以子类化。它解决了问题中的问题。

"create a default value based on the instance name of the class
without passing in an extra argument."

当创建该类或子类的实例时,它的作用如下:

在帧堆栈中向上移动,直到不属于当前实例的方法的第一个帧为止。检查此框架以获得属性self.creation_(name/file/module/function/line/text)。执行一个额外的检查,检查名称为self.creation_name的对象是否实际定义在框架的局部变量()名称空间中,以100%确保找到的creation_name是正确的,否则会引发错误。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import traceback, threading, time

class InstanceCreationError(Exception):
    pass

class RememberInstanceCreationInfo:
    def __init__(self):
        for frame, line in traceback.walk_stack(None):
            varnames = frame.f_code.co_varnames
            if varnames is ():
                break
            if frame.f_locals[varnames[0]] not in (self, self.__class__):
                break
                # if the frame is inside a method of this instance,
                # the first argument usually contains either the instance or
                #  its class
                # we want to find the first frame, where this is not the case
        else:
            raise InstanceCreationError("No suitable outer frame found.")
        self._outer_frame = frame
        self.creation_module = frame.f_globals["__name__"]
        self.creation_file, self.creation_line, self.creation_function, \
            self.creation_text = \
            traceback.extract_stack(frame, 1)[0]
        self.creation_name = self.creation_text.split("=")[0].strip()
        super().__init__()
        threading.Thread(target=self._check_existence_after_creation).start()

    def _check_existence_after_creation(self):
        while self._outer_frame.f_lineno == self.creation_line:
            time.sleep(0.01)
        # this is executed as soon as the line number changes
        # now we can be sure the instance was actually created
        error = InstanceCreationError(
               "
Creation name not found in creation frame.
creation_file:"

               "%s
creation_line: %s
creation_text: %s
creation_name ("

               "might be wrong): %s" % (
                    self.creation_file, self.creation_line, self.creation_text,
                    self.creation_name))
        nameparts = self.creation_name.split(".")
        try:
            var = self._outer_frame.f_locals[nameparts[0]]
        except KeyError:
            raise error
        finally:
            del self._outer_frame
        # make sure we have no permament inter frame reference
        # which could hinder garbage collection
        try:
            for name in nameparts[1:]: var = getattr(var, name)
        except AttributeError:
            raise error
        if var is not self: raise error

    def __repr__(self):
        return super().__repr__()[
               :-1] +" with creation_name '%s'>" % self.creation_name

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
class MySubclass(RememberInstanceCreationInfo):
    def __init__(self):
        super().__init__()

    def print_creation_info(self):
        print(self.creation_name, self.creation_module, self.creation_function,
                self.creation_line, self.creation_text, sep=",")

instance = MySubclass()
#out: instance, __main__, <module>, 68, instance = MySubclass()

如果无法正确确定创建名称,则会引发错误:

1
2
3
4
5
6
7
8
variable, another_instance = 2, MySubclass()

# InstanceCreationError:
# Creation name not found in creation frame.
# creation_file: /.../myfile.py
# creation_line: 71
# creation_text: variable, another_instance = 2, MySubclass()
# creation_name (might be wrong): variable, another_instance

如果您想要一个类的唯一实例名,请尝试__repr__()id(self)

1
2
3
4
class Some:
    def __init__(self):
        print(self.__repr__())  # = hex(id(self))
        print(id(self))

它将打印实例的内存地址,这是惟一的。


我认为名字很重要,如果它们是指向任何对象的指针…没有问题,如果:

1
2
foo = 1
bar = foo

我知道foo指向1 bar指向相同的值1到相同的内存空间。但假设我想创建一个类,其中包含一个添加对象的函数。

1
2
3
4
5
Class Bag(object):
   def __init__(self):
       some code here...
   def addItem(self,item):
       self.__dict__[somewaytogetItemName] = item

所以,当我像下面这样实例化类包时:

1
2
3
4
newObj1 = Bag()
newObj2 = Bag()
newObj1.addItem(newObj2)I can do this to get an attribute of newObj1:
newObj1.newObj2

实际上,最好的方法是按照所选的答案将名称传递给构造函数。但是,如果你真的不想让用户把名字传递给构造函数,你可以这样做:

如果你在命令行中创建一个实例'ThisObject = SomeObject()',你可以从命令历史中的命令字符串中获得对象名:

1
2
3
4
5
6
7
import readline
import re

class SomeObject():
    def __init__(self):
        cmd = readline.get_history_item(readline.get_current_history_length())                                                          
        self.name = re.split('=| ',cmd)[0]

如果你是用'exec'命令创建实例,你可以用:

1
2
if cmd[0:4] == 'exec': self.name = re.split('\'|=| ',cmd)[1]     # if command performed using 'exec'
else: self.name = re.split('=| ',cmd)[0]