我根据我阅读here,here和here的内容编写了一些代码。
#! /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}}正在给出正确的值,使用_lon
和place.lat
进行的属性访问会改为赋予place.lon
的值。
请告诉我我的代码有什么问题以及如何修复它。
答案 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
运行代码。