尝试使用元类创建只读插槽

时间:2014-05-17 04:50:54

标签: python

我根据我阅读hereherehere的内容编写了一些代码。

#! /usr/bin/env python3

class ROSlotsType(type):
    def __new__(cls, name, bases, namespace, **kwds):
        roprops = namespace.pop("__roslots__")
        namespace["__slots__"] = tuple(("_" + propname) for propname in roprops)
        for propname in roprops:
            namespace[propname] = property(lambda self: getattr(self, "_" + propname)) # can't use self.__dict__ since it doesn't exist
        return type.__new__(cls, name, bases, namespace)

class Location(metaclass = ROSlotsType):
    __roslots__ = ["lat", "lon", "alt"]
    def __init__(self, lat, lon, alt = 0):
        self._lat = lat ; self._lon = lon ; self._alt = alt
    def __repr__(self):
        return "Location({}, {}, {})".format(self._lat, self._lon, self._alt)

place = Location(25.282, 82.956, 77.0)
print("Created object {}".format(place))

print("Accessing its attributes:", place.lat, place.lon, place.alt)

print("Trying to access its __dict__...")
try: place.__dict__
except:
    print("Caught exception; object has only __slots__: {}".format(place.__slots__))

print("Trying to set new property...")
try: place.name = "Varanasi"
except:
    print("Caught exception; cannot add new property")

print("Trying to modify read-only property...")
try: place.alt += 1
except:
    print("Caught exception; cannot modify read-only property")

执行以上操作给出:

Created object Location(25.282, 82.956, 77.0)
Accessing its attributes: 77.0 77.0 77.0
Trying to access its __dict__...
Caught exception; object has only __slots__: ('_lat', '_lon', '_alt')
Trying to set new property...
Caught exception; cannot add new property
Trying to modify read-only property...
Caught exception; cannot modify read-only property

插槽和只读行为正常,但显然属性getter存在一些问题,因为直接使用__repr___lat的{​​{1}}正在给出正确的值,使用_lonplace.lat进行的属性访问会改为赋予place.lon的值。

请告诉我我的代码有什么问题以及如何修复它。

1 个答案:

答案 0 :(得分:3)

此处lambda创建一个匿名函数:

namespace[propname] = property(lambda self: getattr(self, "_" + propname))

该函数引用propname,它是定义它的函数的局部变量。不幸的是,它并没有在那一刻复制propname值 - 它将引用保留到propname变量,并且一旦你到了实际使用该函数时,for循环已完成,propname保留roprops中的最后一个值;即alt

要解决这个问题,您可以使用一种有点hacky但广泛认可的方法,通过值而不是通过引用来捕获它:创建一个隐藏其他变量的参数,但是使用您想要的值的默认值:

namespace[propname] = property(lambda self, propname=propname: getattr(self, "_" + propname))

作为Karl Knechtel mentions in the comments,您还可以使用operator.attrgetter,这样可以完全消除hacky位:

namespace[propname] = property(operator.attrgetter('_' + propname))

最后,由于您的问题最初发布在代码审核中,我注意您应该通过pep8运行代码。