Reputation: 1264
I wrote some code based on what I read here, here, and 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")
Executing the above gives:
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
The slots and read-only behaviour work fine, but apparently there is some problem with the property getters, since while __repr__
which uses _lat
and _lon
directly is giving the correct values, the attribute accesses using place.lat
and place.lon
are instead giving the value of place.alt
.
Please advise me on what is wrong with my code and how to fix it.
Upvotes: 0
Views: 125
Reputation: 129001
The lambda
here creates an anonymous function:
namespace[propname] = property(lambda self: getattr(self, "_" + propname))
That function references propname
, which is a local variable of the function in which it was defined. Unfortunately, it's not copying the propname
value at that moment—it's keeping a reference to that propname
variable, and once you get around to actually using that function, the for
loop has completed and propname
is left with the last value in roprops
; namely, alt
.
To fix this, you can use a somewhat-hacky-but-widely-recognized way of capturing it by value rather than by reference: create a parameter that shadows the other variable, but with a default value with the value you want:
namespace[propname] = property(lambda self, propname=propname: getattr(self, "_" + propname))
As Karl Knechtel mentions in the comments, you can also use operator.attrgetter
, which eliminates the hacky bits altogether:
namespace[propname] = property(operator.attrgetter('_' + propname))
Lastly, as your question was originally posted on Code Review, I'd note that you should probably run your code through pep8
.
Upvotes: 3