I'm wondering how to access a descriptor in different functions? I can initialize speed as a descriptor when I initialize the Vehicle class, however I can't access its value within a Vehicle function (calc_speed()-> self.speed.value) returns AttributeError: 'int' object has no attribute 'value'
. Also, shouldn't changing the value audi.speed = 120
trigger the set function (as well as printing should trigger the get function)?'
class SpeedDesc(object):
def __init__(self, name, val):
self.var_name = name
self.value = val
def __get__(self, obj, objtype):
print('Getting', self.var_name)
return self.value
def __set__(self, obj, value):
msg = 'Setting {name} to {value}'
print(msg.format(name=self.var_name, value=value))
self.value = value
class Vehicle(object):
def __init__(self, vType):
self.vehicle_type = vType
self.speed = SpeedDesc('speed desc', 100)
def calc_speed(self, accel):
return self.speed.value * accel
if __name__ == '__main__':
audi = Vehicle('sedan')
print('vehicle speed:', audi.speed.value)
audi.speed = 120
print(audi.calc_speed(1.5))
vehicle speed: 100
Traceback (most recent call last):
File "descriptor_example.py", line 31, in <module>
audi.calc_speed(1.5)
File "descriptor_example.py", line 24, in calc_speed
return self.speed.value * accel
AttributeError: 'int' object has no attribute 'value'
What I expect
Getting speed desc
vehicle speed: 100
Setting speed desc to 120
180
speed
should be a class attribute; __get__
is passed the instance whose speed you want when you access it via an instance rather than a class. I've adjusted the definition of SpeedDesc
slightly to emphasize that you can still initialized it from Vehicle.__init__
.
You never need to access the value
attribute of the descriptor explicitly: that's an implementation detail for __get__
and __set__
. In fact, because there is exactly one instance of SpeedDesc
shared by all instance of Vehicle
, you don't want to store the speed in self.value
. You should store it in a dict that associates a speed with a particular instance, which is most easily done by attaching it to the object received as the obj
argument.
class SpeedDesc(object):
def __init__(self, name, val=None):
self.var_name = name
self.default = val
self.attr_name = "_" + name # e.g.
def __get__(self, obj, objtype):
if obj is None:
return self
print('Getting', self.var_name)
return getattr(obj, self.attr_name, self.default)
def __set__(self, obj, value):
msg = 'Setting {name} to {value}'
print(msg.format(name=self.var_name, value=value))
setattr(obj, self.attr_name, value)
class Vehicle(object):
speed = SpeedDesc('speed desc')
def __init__(self, vType):
self.vehicle_type = vType
self.speed = 100
def calc_speed(self, accel):
return self.speed * accel
if __name__ == '__main__':
audi = Vehicle('sedan')
# Produces a call to Vehicle.speed.__get__(audi, Vehicle)
print('vehicle speed:', audi.speed)
# Produces a call to Vehicle.speed.__set__(audi, 120)
audi.speed = 120
print(audi.calc_speed(1.5))
Note that you don't necessarily need to pass a name explicitly; the __set_name__
method can be used to get the name of the attribute the descriptor is assigned to.
class SpeedDesc(object):
def __init__(self, val=None):
self.default = val
def __get__(self, obj, objtype):
if obj is None:
return self
print('Getting', self.var_name)
return getattr(obj, self.attr_name, self.default)
def __set__(self, obj, value):
msg = 'Setting {name} to {value}'
print(msg.format(name=self.var_name, value=value))
setattr(obj, self.attr_name, value)
def __set_name__(self, owner, name):
self.var_name = name
self.attr_name = "_" + name # e.g.
print(f"Created {self.var_name} for {owner}, backed by {self.attr_name}")
class Vehicle(object):
speed = SpeedDesc()
def __init__(self, vType):
self.vehicle_type = vType
self.speed = 100
def calc_speed(self, accel):
return self.speed * accel
After the type is defined, Vehicle.speed.__set_name__(Vehicle, "speed")
is called on your behalf.