Python:在类Foo:x = MyClass()中,MyClass可以知道它所分配的变量的名称吗?

时间:2013-02-02 22:50:54

标签: python serialization metaclass

我希望能够做到以下几点:

class PrintName:
    def __init__( self, obj ):
        print obj._name

class SetName:
    def __init__( self, name = None ): # Default name is None
        self._name = name

class Test:
    f = SetName( ) # No explicit name given
    g = PrintName( f )

此时我想让python打印'f',所以当执行PrintName(f)时,f应该知道它的名字。

我找到的每个自动命名解决方案在创建后都将属性命名为。 我一直试图用元类来解决这个问题,但即使这样也不行。

我想这样做是为了能够“保存”并“重新加载”python代码供以后使用(有点像原始脚本语言,可以在具有使用评估的程序中进行更改)< / p>

例如:

x = 0
y = 0
p = ( x, y )

打印p得到(0,0),然后用x和y做一些结果

x = 124
y = -32
p = ( x, y )

print p =(124,-32)。

最简单的方法是使用

p = ( 'x', 'y' )

但在这种情况下,我们怎么知道'x'是变量的名称而不是字符串'x'

我已经尝试过为此目的编写一个简单的脚本语言,但这是很多工作,如果以上方法可行,则可以在脚本中使用整个python语言。

我正在努力为我的问题找到一个简单而灵活的解决方案。

提前感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

我设法使用 __ 准备 __ 在Python 3.x中找到了解决方案。 这是一个解释我想要做的工作代码。

from collections import MutableMapping

class MDict(MutableMapping):
    def __init__(self, *args, **kwargs):
        self._d = dict(*args, **kwargs)
    def __getitem__(self, key):
        return self._d[key]
    def __setitem__(self, key, value):
        self._d[key] = value
        try:
            self._d[key]._key = key # Let's set the name of the object
        except AttributeError: # Not a good way to handle immutable objects
            pass
    def __delitem__(self, key):
        del self._d[key]
    def __iter__(self):
        return iter(self._d)
    def __len__(self):
        return len(self._d)

class MBase(type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return MDict()
    def __new__(metacls, name, bases, mdct):
        return super().__new__(metacls, name, bases, dict(mdct))
    def __str__( self ):
        return "class {0}(CSub, metaclass=MBase):".format( self.__name__ )

class CSub: # An empty class so we know when to go deeper int print_code
    pass

class Integer:
    def __init__( self, value ):
        self._value = value
    def __str__( self ):
        try: # See if it's a reference
            return "{0} = Integer( {1} )".format( self._key, self._value._key )
        except: # Not it's a number
            return "{0} = Integer( {1} )".format( self._key, self._value )

class Point:
    def __init__( self, x, y ):
        if type( self ) == type( x ): # Check to see if initializing with a reference
            self._x, self._y = x._key, y._key
        else: # It's not reference
            self._x, self._y = x, y
    def __str__( self ):
        try:
            return "{0} = Point( {1}, {2} )".format( self._key, self._x._key, self._y._key )
        except:
            return "{0} = Point( {1}, {2} )".format( self._key, self._x, self._y )


def print_code( cls, indent = 2 ):
    # Print the header
    if indent == 2:
        print( "class Main(metaclass=MBase):" )
    for attr, value in cls.__dict__.items():
        if not attr.startswith( "_" ): # Ignore hidden attributes
            print( " " * indent + str( value ) ) # Use indentation
            if isinstance( value, CSub.__class__ ): # If it's a subclass then process that too
                print_code( value, indent + 2 )

class Main(metaclass=MBase):
    x = Integer( 0 )
    y = Integer( 100 )
    z = Integer( x )
    p1 = Point(x,x)
    p2 = Point(y,y)
    class Sub(CSub, metaclass=MBase):
        p = Point(1,1)
print_code( Main )

这样,如果我更改对象x,那么引用x的所有其他对象也将更改。 更重要的是,我可以轻松地将代码保存到文本文件中供以后使用。

这仍然需要工作,但这是一个良好的开端。 我希望这会帮助其他人寻找类似的东西。

答案 1 :(得分:0)

不可能从一个对象向后工作到保存它的变量的名称,除非迭代当前上下文中的每个变量并检查哪些变量具有等于给定对象的值。这将是相对缓慢和混乱的。

也许更好的替代方案(虽然我不确定它是否适用于您的目的)将使用名称本身,例如而不是将f传递给PrintName(或其他),而是传递'f'字符串。如果需要访问PrintName构造函数中变量的值,则可以访问当前堆栈框架上方的堆栈框架,并选择具有给定名称的变量。您可以使用inspect module执行此操作,如下所示:

class PrintName:
    def __init__(self, name):
        # of course you can just print the name
        last_frame = inspect.stack()[1]
        if name in last_frame.f_locals:
            value = last_frame.f_locals[name]
        elif name in last_frame.f_globals:
            value = last_frame.f_globals[name]
        # do stuff with value

毋庸置疑,这是非常hackish,而不是你应该在任何正常程序中做的事情。它意味着用于类似调试器的东西。