-
Notifications
You must be signed in to change notification settings - Fork 320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Return created parameter from add_parameter #4412
Return created parameter from add_parameter #4412
Conversation
Codecov Report
@@ Coverage Diff @@
## master #4412 +/- ##
=======================================
Coverage 68.24% 68.24%
=======================================
Files 339 339
Lines 31781 31781
=======================================
Hits 21688 21688
Misses 10093 10093 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks good to me, brings more life to add_parameter
syntax :)
the only question i have is what happens with docstring
argument of add_parameter
and the docstring that comes right after the attribute declaration - the latter takes over, right? in any case, this probably deserves a separate PR anyway to clarify what method of providing a docstring for a parameter to use when and how.
As I understand it the upshot of this is that
|
so we are bound to have to duplicate the parameter docstrings? :( one for shpinx and another one for e.g. |
I am afraid so unless we come up with a hack of some sort |
@astafan8 Does not look like there is a way to get the attribute docstring at runtime https://stackoverflow.com/questions/55672640/how-can-i-get-doc-string-of-the-class-attribute-in-python |
Hi, I came across this PR and would like to comment. I see the benefit of I also think that having the dosctring as |
I agree this is suboptimal and ideally, we really should get away from having to give name which has always been strange and ugly looking. This may be possible using inspection or by implementing parameters using the descriptor protocol. Regardless I do feel like the gain of making parameters static outweighs the issue by a significant margin
The thing is that having a string following an attribute is a well established thing with support in python and in Sphinx which experienced python users will know about but the docstring argument is something that qcodes invented with no support elsewhere. |
I agree, having to type the name twice is not too bad. But the docstring is too much IMO.
Interesting, I haven't seen this before and was not aware of this, maybe I'm not an experienced Python user then :P But in any case, I think the runtime availability of docstrings is very important. As mentioned in the Stackoverflow link above (and see also discussion on Python mailing list, PEP 257), attribute docstrings are ignored by the the Python interpreter and will never be available at runtime. Maybe one option to consider would then be to initialize parameters using a decorator, something like class MyInstrument(...):
@parameter(
unit="V",
label="Resolution",
# not sure if set_cmd/get_cmd should go here, or should we have @frequency.setter?
)
def resolution(self):
"""
Parameter to control voltage resolution
""" Which I guess is pretty similar to the descriptor idea. This would be quite a major change, but it would solve all of the problems; autocomplete and documentation work while not having to write anything twice. |
Hmm, maybe this is not perfect either, you can't access |
@mgunyho Thanks for your input. Good to know that you use the runtime docstring. I had not seem many users make use of it. I will investigate if there are ways to write a sphinx extension that parses that our when generating the docs. I will also see if there is a way for the parameter to get its way with introspection |
f97c39d
to
1881499
Compare
1881499
to
f25a452
Compare
A few random thoughts that came up on this
|
Spend a bit of time today exploring what is possible and found out that Sphinx will document attributes if they have a type annotation. This will remove the need to include a docstring to have a parameter documented which was one of the concerns raised above. |
f25a452
to
fa26504
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4412 +/- ##
==========================================
+ Coverage 67.65% 67.67% +0.02%
==========================================
Files 351 352 +1
Lines 30591 30704 +113
==========================================
+ Hits 20697 20780 +83
- Misses 9894 9924 +30 ☔ View full report in Codecov by Sentry. |
I also experimented with looking up the name from the previous stack frame using some very hacky code. While this is definitely possible I am not sure if I like this better than having to write the name twice |
An alternative could be to overwrite |
This may also be interesting to improve the way parameters are documented https://www.sphinx-doc.org/en/master/development/tutorials/autodoc_ext.html#autodoc-ext-tutorial |
34f1a76
to
e84598e
Compare
For reference this is a libcst transform that can perform this rewrite at least in a simple example #%%
import libcst as cst
from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand
class AddParameterTransformer(VisitorBasedCodemodCommand):
def leave_SimpleStatementLine(self, original_node: cst.SimpleStatementLine, updated_node: cst.SimpleStatementLine) -> cst.SimpleStatementLine:
if isinstance(updated_node.body[0], cst.Expr):
call_node = updated_node.body[0].value
else:
return updated_node
if isinstance(call_node, cst.Call):
func_node = call_node.func
else:
return updated_node
if isinstance(func_node, cst.Attribute) and func_node.attr.value == "add_parameter":
arg = call_node.args[0].value
if isinstance(arg, cst.SimpleString):
var_name = arg.value.strip('"')
stm = cst.parse_statement(f"self.{var_name}: Parameter = x")
new_node = stm.body[0]
new_node = new_node.with_changes(value=call_node)
return updated_node.with_changes(body=[new_node])
return updated_node
def transform_code(code: str) -> str:
transformer = AddParameterTransformer(CodemodContext())
module = cst.parse_module(code)
new_module = module.visit(transformer)
return new_module.code
# Test the transformation
code = """
self.add_parameter(
"cool_time",
label="Cooling Time",
unit="s",
get_cmd="PS:CTIME?",
get_parser=int,
set_cmd="CONF:PS:CTIME {}",
vals=Ints(5, 3600),
)
"""
#%%
a = transform_code(code)
print(a)
# %%
|
9d66f36
to
e51c0b7
Compare
e51c0b7
to
8fa757a
Compare
8fa757a
to
204d701
Compare
25723ff
to
715b109
Compare
835a6fb
to
3611268
Compare
3611268
to
ef0a7e2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And kudos for the refactor tool!!!
docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb
Outdated
Show resolved
Hide resolved
Based on libcst, with cli tool and tests
ef0a7e2
to
36cc036
Compare
This means that you will be able to do
which means that you get the benefit of the
self.resolution = Parameter(..., instrument=self)
form without having toremember to pass the instrument to Parameter
In documentation this currently (with the qcodes extension) looks like this:
Currently it looks like this
If we go this way we need to
This has tradeoffs between duplication of the parameter name as docstring vs statically defined the parameter.
We have decided that the benefit of a statically defined parameter outweighs the issues. Furthermore, we provide a tool to generate the additional names and if there is a wish we can extend the tool to also lint these things in existing code similar to how the standard library requires that a typevar and its assigned variable is named the same T= TypeVar("T")
An example of applying this can be found in #6089