I am very new to OPC UA, so please excuse if this is a very basic question.
I want to call a method with two input arguments. The input arguments are described as follows:
What I have tried is this:
evse = client.get_node("ns=6;s=::EVSE")
set_default = client.get_node("ns=6;s=::EVSE:SetDefault")
res = evse.call_method(set_default, 1, 20)
Which yields this error:
BadInvalidArgument: "One or more arguments are invalid."(BadInvalidArgument)
If I replace the last line by this
res = evse.call_method(set_default, [1, 20])
I get this error:
BadArgumentsMissing: "The client did not specify all of the input arguments for the method."(BadArgumentsMissing)
When I call the method manually from UaExpert, it works fine.
Can somebody give me a hint how to proceed?
Edit:
These are the argument structures expanded:
Does the Python library automatically cast the method arguments for you?
Maybe you're just not providing values of the correct datatype in your first attempt; try this instead:
res = evse.call_method(set_default, 1, 20.0)
Or look into the documentation or source code to see if this is required of you. I also don't know if there's a special unsigned "wrapper" class you would need to use for the first argument if the library doesn't do this casting for you.
Related
I am trying to introduce some automation to a script I'm writing, and I'm having some trouble with calling a function that has parameters from another module. Here's the scenario:
I have two modules: test.py and Strategies.py. I have code that generates a list of all the functions in Strategies.py. From that list, I am using getattr to execute each function.
What I'm having problems with is that some of my functions have parameters. I am getting the following error with a function that has an 'x' argument:
TypeError: buy_test_function() missing 1 required positional argument: 'x'
To make this as clear as possible, here's the relevant code:
call_method = strategy_names[0][y]
call_method = getattr(Strategies, call_method)()
I know the first line above is working fine. I also know that it's the empty parentheses at the end of the second line that's causing the problem. The magic I need is finding a way to dynamically read each function's required arguments and execute the function with the necessary arguments in the parentheses.
I've tried to use inspect.signature(), but it keeps telling me the object is not callable.
I have to believe Python has an elegant solution to this, but I've had little luck on Google. Any assistance is greatly appreciated.
Thank you!
Assuming the functions in Strategies are not class methods, and you've annotated the function types in the signatures, you can construct default instances of the class types you specified for the arguments and pass them in as params:
from inspect import signature
call_method = strategy_names[0][y]
call_method = getattr(Strategies, call_method)
sig = signature(call_method)
call_method = getattr(Strategies, call_method)(*[param.annotation() for param in sig.parameters.values()])
See for reference:
>>> def test(x:int):
... return x*2
>>> sig = signature(test)
>>> sig.parameters['x'].annotation
<class 'int'>
>>> sig.parameters['x'].annotation()
0
>>> test(*[param.annotation() for param in sig.parameters.values()])
0
I will also note that if you can define the values you want to use in your call methods ahead of time for a given method and your function names in Strategies are unique, you can prebuild a dictionary that maps the function name to the args you want to use:
args = {'test':[1]}
test(*args[test.__name__])
I have a script which accepts, on the command line, arguments to create a matplotlib.patches.Rectangle object.
Typical arguments that could be passed to the Rectangle constructor might be something like (in Python, without argument parsing):
patches.Rectangle(
xy=(15000,0),
width=9.4,
height=4,
color='peachpuff'
)
The user can encode this on the command line like so:
--patches '[{ "x" : 15000,"y" : 0,"width" : 9.4,"height" : 4,"color" : "peachpuff"}]'
This json array is loaded with json.loads. Note that most of the arguments can just be passed directly through to the Rectangle constructor, but the xy tuple causes a problem: json.loads will never generate a tuple type, so you can't create a tuple this way.
To work around it, I have the user pass separate x and y mappings, and then combine them like this:
p.add_argument('--patches', help='A JSON array of patches.Rectangle object arguments to patch onto the chart',
type=json.loads)
# stuff
# put some patches on
if args.patches:
def from_json(json):
ret = dict(json)
del ret['x']
del ret['y']
ret['xy'] = (json['x'], json['y'])
return ret
for pjson in args.patches:
p = from_json(pjson)
ax.add_patch(patches.Rectangle(**p))
Half of that code (essentially all of from_json) is just dealing with the transformation of x and y args into the xy = (x, y) tuple.
Any more elegant/Pythonic way to deal with this?
It might involve cleaner handling of the json input, or perhaps passing something other than json on the command line (but it must have rough feature-parity with the current approach).
Your from_json function can effectively be used as a custom type for your --patches option.
def RectangleArgs(s):
d = json.loads(s)
d['xy'] = (d.pop('x'), d.pop('y'))
return d
...
parser.add_argument('--patches', action='append', type=RectangleArgs)
...
for patch in args.patches:
ax.add_patch(patches.Rectangle(**patch))
Aside from simply massaging the JSON read from the command line, you can do additional validation, raising argparse.ArgumentTypeError if you find anything that would cause a problem when you try to use the return value in a call to patches.Rectangle.
Your motivating assumption that you need a tuple for the xy argument to Rectangle is not correct: a 2-element list works just as well, so "xy" : "[1, 2]" will work fine.
Pointed out by Jeff in the comments.
so this is my function, and it doesn't work.. why?
def Oppnadjur(djurfil):
djurfil = open("djur.txt", "r")
Djur = djurfil.readlines()
Djur.sort()
djurfil.close()
Djurlista=[]
You wrote that your function should receive one parameter, djurfil. However, you clearly did not mean to do that, as you proceed to not use that parameter, overwriting it with a different value. See the Python Tutorial about how to define functions.
The error message you see means that you had called your function as you had intended, with no parameters (Opnnadjur()), but that's not how you had defined it. So Python is looking for the parameter it thinks you should be passing in.
The error would be in your calling code, not the def of the function. You need to call Oppnadjur with one parameter. The error message suggests that you are calling it with zero parameters.
You define your function with one argument (djurfil), but the argument is unused in your function so you can get rid of it.
def Oppnadjur():
djurfil = open("djur.txt", "r")
Djur = djurfil.readlines()
Djur.sort()
djurfil.close()
Djurlista=[]
I face a problem while implementing with ctypes. I have 2 C functions:
antichain** decompose_antichain(antichain*, int, char (*)(void*, void*), void** (*)(void*));
counting_function** decompose_counting_function(counting_function*);
where antichain and counting_function are two structures. An antichain can be seen like a set, containing elements of unknown type (in this exemple, counting_function). The decompose_antichain function takes as argument (amongst other things) the function to use to decompose the elements the antichain contains (-> a function of which the prototype is void** (*) (void*)).
Now i would like to use decompose_antichain from Python. I used ctypes:
lib = cdll.LoadLibrary("./mylib.dylib")
#CountingFunction, Antichain and other definitions skipped
DECOMPOSE_COUNTING_FUNCTION_FUNC = CFUNCTYPE(POINTER(c_void_p), POINTER(CountingFunction))
decompose_counting_function_c = lib.decompose_counting_function
decompose_counting_function_c.argtypes = [POINTER(CountingFunction)]
decompose_counting_function_c.restype = POINTER(c_void_p)
decompose_antichain_c = lib.decompose_antichain
decompose_antichain_c.argtypes = [POINTER(Antichain), c_int, DECOMPOSE_COUNTING_FUNCTION_FUNC, COMPARE_COUNTING_FUNCTIONS_FUNC]
decompose_antichain_c.restype = POINTER(POINTER(Antichain))
(...)
antichains_list = decompose_antichain_c(antichain, nb_components, COMPARE_COUNTING_FUNCTIONS_FUNC(compare_counting_functions_c), DECOMPOSE_COUNTING_FUNCTION_FUNC(decompose_counting_function_c))
The last line produces the error: invalid result type for a callback function.
I can't see where the problem come from. Can anyone help me? Thanks
You need to make sure the argtypes and result types match. It looks like you swapped the argument types of decompose_antichain_c. You have DECOMPOSE_COUNTING_FUNCTION_FUNC, COMPARE_COUNTING_FUNCTIONS_FUNC in the argtypes, which doesn't match the declaration of the C function you gave above. You then try to call it with COMPARE_COUNTING_FUNCTIONS_FUNC first, and DECOMPOSE_COUNTING_FUNCTION_FUNC second.
DECOMPOSE_COUNTING_FUNCTION_FUNC also looks wrong. It should probably be CFUNCTYPE(POINTER(c_void_p), c_void_p) just guessing from the rest of the code.
I can give a more detailed answer if you provide the code that creates COMPARE_COUNTING_FUNCTIONS_FUNC and CountingFunction
I have a function that is supposed to take a string, append things to it where necessary, and return the result.
My natural inclination is to just return the result, which involved string concatenation, and if it failed, let the exception float up to the caller. However, this function has a default value, which I just return unmodified.
My question is: What if someone passed something unexpected to the method, and it returns something the user doesn't expect? The method should fail, but how to enforce that?
It's not necessary to do so, but if you want you can have your method raise a TypeError if you know that the object is of a type that you cannot handle. One reason to do this is to help people to understand why the method call is failing and to give them some help fixing it, rather than giving them obscure error from the internals of your function.
Some methods in the standard library do this:
>>> [] + 1
Traceback (most recent call last):
File "", line 1, in
TypeError: can only concatenate list (not "int") to list
You can use decorators for this kind of thing, you can see an example here.
But forcing parameters to be of a specific type isn't very pythonic.
Python works under the assumption that we are all intelligent adults that read the documentation. If you still want to do it, you should not assert the actual type, but rather just catch the exception when the argument does not support the operations you need, like that:
def foo(arg):
try:
return arg + "asdf"
except TypeError:
return arg
What does the default value have to do with it? Are you saying you want to return the default value in the case where the caller doesn't pass a str? In that case:
def yourFunc( foo ):
try:
return foo + " some stuff"
except TypeError:
return "default stuff"
Space_C0wb0y has the right answer if you want to return the arg unmodified if it's not a string, and there's also the option of making an attempt to convert something to a string:
def yourFunc2( bar ):
return str(bar) + " some stuff"
Which will work with a lot of different types.