You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In designing the API for pyboard objects (like accelerometer, servo, LED), there is a choice for some parts of the interface whether it is implemented as a function or an attribute. For example, consider the accelerometer:
accel = pyb.Accel() # create accelerometer object
print(accel.x_fun()) # read x value using a function call
print(accel.x_attr) # read x value using an attribute
Which is better (if there is such a thing)? x_attr is more efficient because it's only 1 call to the underlying C object. x_fun() is 2 calls: 1 to lookup the x_fun method, the other to call it. You only ever need to read the value of the x axis, so there will never be any arguments to x_fun(), thus x_attr will suffice.
It's sensible to consider the axis' as variables. Behind the scenes, the accelerometer is just an I2C device with 10 or so registers. You read and write these registers, and that's it. The first 3 registers are the x, y and z axis values.
Objections against having x_attr is that it changes asynchronously (ie one read then another read will in general give different values, which is not what you expect for an attribute), and that it's read only (no sense in writing the x axis value).
A counter argument is that the mode register (and other settings) are read/write, and only change when you write to them, so they really are proper attributes (as opposed to methods).
Having x_fun() allows you to pass accel.x_fun as a function to be called later on. To do this with x_attr you will need a lambda: lambda:accel.x_attr.
Other objects like Servo could have angle as an attribute, which is sensible to read and write. LED has intensity which acts exactly like an attribute (LED.intensity=128).
my 2 cents - I like the attribute approach you outline for one reason - speed.
If I'm reading an ADC value - I want to sample as close to when I ask as possible. If I'm setting a servo value - same thing.
I don't think the function call adds extra meaning in these cases and can be explained by documentation.
Clearly when a specific action has to occur, or more than one parameter has to be sent - then a function call is probably better.
Passing around the attribute in a lambda might be needed but I think its more likely the accel object will be passed - so no disadvantage there.
Speed/efficiency is important. But I think it's equally important to be consistent with the API. For example, for an LED object, you can imagine the following.
All functions:
LED.on()
LED.off()
LED.toggle()
i = LED.intensity() # read intensity
LED.intensity(128) # set intensity
All attributes:
LED.on = True
LED.off = True # this is a bit crazy
state = LED.on # returns True if on
LED.toggle = True # toggles
i = LED.intensity
LED.intensity = 128
A mixture:
LED.on()
LED.off()
LED.toggle()
i = LED.intensity
LED.intensity = 128
x_attr is more efficient because it's only 1 call to the underlying C object. x_fun() is 2 calls: 1 to lookup the x_fun method, the other to call it.
That's interesting point of view. I always treated it the opposite: Because method call lookup is standardized, and it can be optimized under some circumstances (replacing dynamic dispatch with static - we discussed that once already; it's not what uPy compiler expected to ever do, but rather optimizer on big box, but the point is that it's possible). On the other hand, current API for querying attrs (which is pretty much based on Python __getattribute__ semantics, so no proposals to change that) is fully dynamic and mandates dynamic dispatch.
So, I don't see that big a difference, it's pretty symmetric: method - lookup and call, attr - call and lookup. Method processing is optimizable (think static dispatch, think Viper, also think that lookup+call looks like a good candidate for single bytecode instr.), attr processing is as good as it is, and that includes unavoidable (so, granted, relatively small) lookup cost.
Maybe you could look at it like:
Led.on() is a shortcut for Led.intensity = 255
so the action is a verb and therefore a function. Setting a value is an attribute.
so:
Led.on() # its a verb
Led.off()
Led.toggle()
Led.intensity = 128
val = Led.intensity
lamp = pyb.Led()
lamp.intensity = 128
This is your third choice.
for accel ?
a = pyb.Accel(setup_args) # e.g. Accel(16, pyb.SAR)
val = a.sample # i.e. read the latest sample
a.sample_rate = 1000 # rather than a.set_rate(1000)
a.trigger(timer, arg) # a verb again
for servo:
s = pyb.Servo(...) # forgptten how we're doing this now - pin mapping right ?
Objections against having x_attr is that it changes asynchronously
IMHO, this external interface trait alone is enough to choosing methods over attributes. Like, methods everywhere. Because for each access, there's actual action performed. Perhaps name methods well to allow recasting them as properties.
if they are the same from a speed point of view... then its down to style... you choose
Currently, attribute access perhaps slightly faster per @dpgeorge comment, but main point is that method calls are optimizable, while attributes - not (w/o providing for them similar standard infra like for methods, which sounds like bloat). If you want speed, you should aim for JITs like viper. And in machine code, it's all function call. So, you hope to optimize any lookups and be able to call needed function directly. Using methods enable this, using attrs - disable.
In your initial example you are describing a sample capture. The sample happens to have a complex set of ten values instead of the usual single value we normally associate with the concept of a "sample" like a temperature measurement. But it is still a sample capture.
Therefore, you should use a function that returns the sample structure.
data=accel.getReading() # this returns a new sample data structure upon each callx1=data.x# this is invariant and read-onlyx2=data.x# this is invariant and read-onlyy1=accel.getReading().y# this will return a fresh data value each timey2=accel.getReading().y# this will return a fresh data value each time
This would give you the desired behavior in all cases, wouldn't it? You can pass around the function itself or you can pass around the resolved data sample. Both are legitimate usage scenarios.
LED.on = True
LED.off = True # this is a bit crazy
state = LED.on # returns True if on
LED.toggle = True # toggles
i = LED.intensity
LED.intensity = 128
I think that to use attributes then you need to use nouns. If you can't represent it as a noun, then I think it should stay as a function.
Personally, I feel that the function interface is richer. You can have multiple arguments, and optional arguments.
LED.state = True
LED.state = False
I'm not sure that it makes sense for comms based APIs (the UART, or I2C) to use an attribute based API, although I suppose you could.
char = UART.data
UART.data = "string to be written"
For the UART, ideally I'd like to see an API which follows the pyserial API.
Dealing with timeouts would be interesting, although they could just be additional attributes.
Having coded both styles, right now coding the attribute stuff in C as considerably more work, and requires the introduction of a meta class. For example, in order to implement pyb.Exti.MODE_IRQ_RISING required adding a pyb_Exti meta class.
I think that using attributes needs to be simplfied (on the C side) before we can consider using them eveywhere.
I also think that if you're looking for a consistent API across everything, then you should really come up with the API first.
Then write the code that implements the API. Right now, the APIs have been made up in a rather adhoc manner as each component has been implemented.
Thanks for the comments. As usual, design is a tricky thing requiring tradeoffs.
The "all attributes" approach is definitely not sensible. UART and similar should surely have .read and .write methods.
I think it's probably sensible to declare that attributes should be used for constants (eg Led.MAXIMUM_INTENSITY) and members of objects only (like a struct object), not for functionality.
Activity
Neon22 commentedon Mar 25, 2014
my 2 cents - I like the attribute approach you outline for one reason - speed.
If I'm reading an ADC value - I want to sample as close to when I ask as possible. If I'm setting a servo value - same thing.
I don't think the function call adds extra meaning in these cases and can be explained by documentation.
Clearly when a specific action has to occur, or more than one parameter has to be sent - then a function call is probably better.
Passing around the attribute in a lambda might be needed but I think its more likely the accel object will be passed - so no disadvantage there.
dpgeorge commentedon Mar 25, 2014
Speed/efficiency is important. But I think it's equally important to be consistent with the API. For example, for an LED object, you can imagine the following.
All functions:
All attributes:
A mixture:
pfalcon commentedon Mar 25, 2014
That's interesting point of view. I always treated it the opposite: Because method call lookup is standardized, and it can be optimized under some circumstances (replacing dynamic dispatch with static - we discussed that once already; it's not what uPy compiler expected to ever do, but rather optimizer on big box, but the point is that it's possible). On the other hand, current API for querying attrs (which is pretty much based on Python
__getattribute__semantics, so no proposals to change that) is fully dynamic and mandates dynamic dispatch.So, I don't see that big a difference, it's pretty symmetric: method - lookup and call, attr - call and lookup. Method processing is optimizable (think static dispatch, think Viper, also think that lookup+call looks like a good candidate for single bytecode instr.), attr processing is as good as it is, and that includes unavoidable (so, granted, relatively small) lookup cost.
Neon22 commentedon Mar 25, 2014
Maybe you could look at it like:
Led.on() is a shortcut for Led.intensity = 255
so the action is a verb and therefore a function. Setting a value is an attribute.
so:
This is your third choice.
for accel ?
for servo:
Neon22 commentedon Mar 25, 2014
if they are the same from a speed point of view... then its down to style... you choose
pfalcon commentedon Mar 25, 2014
IMHO, this external interface trait alone is enough to choosing methods over attributes. Like, methods everywhere. Because for each access, there's actual action performed. Perhaps name methods well to allow recasting them as properties.
pfalcon commentedon Mar 25, 2014
Currently, attribute access perhaps slightly faster per @dpgeorge comment, but main point is that method calls are optimizable, while attributes - not (w/o providing for them similar standard infra like for methods, which sounds like bloat). If you want speed, you should aim for JITs like viper. And in machine code, it's all function call. So, you hope to optimize any lookups and be able to call needed function directly. Using methods enable this, using attrs - disable.
KeithJRome commentedon Mar 25, 2014
In your initial example you are describing a sample capture. The sample happens to have a complex set of ten values instead of the usual single value we normally associate with the concept of a "sample" like a temperature measurement. But it is still a sample capture.
Therefore, you should use a function that returns the sample structure.
This would give you the desired behavior in all cases, wouldn't it? You can pass around the function itself or you can pass around the resolved data sample. Both are legitimate usage scenarios.
dhylands commentedon Mar 25, 2014
I think that to use attributes then you need to use nouns. If you can't represent it as a noun, then I think it should stay as a function.
Personally, I feel that the function interface is richer. You can have multiple arguments, and optional arguments.
LED.state = True
LED.state = False
I'm not sure that it makes sense for comms based APIs (the UART, or I2C) to use an attribute based API, although I suppose you could.
char = UART.data
UART.data = "string to be written"
For the UART, ideally I'd like to see an API which follows the pyserial API.
Dealing with timeouts would be interesting, although they could just be additional attributes.
Having coded both styles, right now coding the attribute stuff in C as considerably more work, and requires the introduction of a meta class. For example, in order to implement pyb.Exti.MODE_IRQ_RISING required adding a pyb_Exti meta class.
I think that using attributes needs to be simplfied (on the C side) before we can consider using them eveywhere.
I also think that if you're looking for a consistent API across everything, then you should really come up with the API first.
Then write the code that implements the API. Right now, the APIs have been made up in a rather adhoc manner as each component has been implemented.
dpgeorge commentedon Mar 25, 2014
Thanks for the comments. As usual, design is a tricky thing requiring tradeoffs.
The "all attributes" approach is definitely not sensible. UART and similar should surely have .read and .write methods.
I think it's probably sensible to declare that attributes should be used for constants (eg Led.MAXIMUM_INTENSITY) and members of objects only (like a struct object), not for functionality.
dpgeorge commentedon Apr 17, 2014
Closing because discussion has been had and things have moved on. Mainly using functions, not attributes.