Groovy will collect all the named parameters into a map and pass it into a method as the first parameter. This seems neat, but after trying to make it work it seems really unusable.
So the issue is a method like this:
def method(paramMap, specificVar1 = 7, specificVar2 = 14)
when you call this method with something like this:
method(12, extraValue: "hello")
you get pretty much what you expect:
assert 12 == specificVar1
assert 14 == specificVar2
assert [extraValue:"hello"] == paramMap
not bad, makes sense. The problem is, if you assume the Map params are optional then you can end up with values like this:
method(12)
assert paramMap == 12
assert specificVar1 == 7 // default values
assert specificVar2 == 14
That scalar should have gone into specificVar--not the map. If I specifically type the map in the method:
def method(Map paramMap, specificVar1 = 7, specificVar2 = 14)
then method(12, extraValue: "hello") works just as it did before, but method(12) throws a ClassCastException. This just doesn't seem usable. Is there any way to make that Map "sticky" so that it will simply be empty if there are no Map parameters?
Setting default values on parameters create overloaded methods with combinations made from left to right, thus, it is hard to make method(12) and also be able to pass map entries.
Your method def method(paramMap, specificVar1=7, specificVar2=14) will generate the following methods:
Object Maps.method(java.lang.Object)
Object Maps.method(java.lang.Object,java.lang.Object)
Object Maps.method(java.lang.Object,java.lang.Object,java.lang.Object)
And a fully typed method with the map param:
def method3(Map paramMap=[:], Integer specificVar1=7, Integer specificVar2=14) {
}
Will generate the following methods:
Object Maps.method3()
Object Maps.method3(java.util.Map)
Object Maps.method3(java.util.Map,java.lang.Integer)
Object Maps.method3(java.util.Map,java.lang.Integer,java.lang.Integer)
(No suitable method for method(12)).
Also, entries passed to the method will be collected and inserted in the first map parameter. The following method:
def method4(Integer specificVar1=7, Integer specificVar2=14, Map map=[:]) {
Generates:
Object Maps.method4()
Object Maps.method4(java.lang.Integer)
Object Maps.method4(java.lang.Integer,java.lang.Integer)
Object Maps.method4(java.lang.Integer,java.lang.Integer,java.util.Map)
Thus, method4 12, a:'b' fails with:
No signature of method: Maps.method4() is applicable for argument types:
(java.util.LinkedHashMap, java.lang.Integer) values: [[a:b], 12]
So, no, i don't think you can do what you want using maps :-).
Solution 1:
If you are in for a pure dynamic solution, you can use a single map argument:
def method5(Map map) {
def specificVar1 = map.specificVar1 ?: 7
def specificVar2 = map.specificVar2 ?: 14
}
Solution 2 (updated):
You can create a class to represent the parameters. Using a map to be coerced into the object is statically compilable and a syntatic sugar for it.
#groovy.transform.CompileStatic
class Maps {
def method6(Foo foo) { "$foo.params, $foo.specificVar1, $foo.specificVar2" }
def method6(Map map) { method6 map as Foo }
static main(args) {
def maps = new Maps()
assert maps.method6(params: [a: 'b', c: 'd'], specificVar1: 40) ==
"[a:b, c:d], 40, 14"
assert maps.method6(new Foo(params: [a: 'b', c: 'd'], specificVar2: 21)) ==
"[a:b, c:d], 7, 21"
}
}
class Foo {
def specificVar1 = 7, specificVar2 = 14, params = [:]
}
Solution 3:
An overloaded method.
def method6(Map paramMap, Integer specificVar1=7, Integer specificVar2=14) {
"$paramMap, $specificVar1, $specificVar2"
}
def method6(Integer specificVar1=7, Integer specificVar2=14) {
method6 [:], specificVar1, specificVar2
}
assert method6( 12 ) == "[:], 12, 14"
assert method6( ) == "[:], 7, 14"
assert method6( a:'b', 18 ) == "[a:b], 18, 14"
assert method6( 18, a:'b', 27 ) == "[a:b], 18, 27"
assert method6( 90, 100 ) == "[:], 90, 100"
assert method6( a:'b', 140, c:'d' ) == "[a:b, c:d], 140, 14"
The map version method can't have a default parameter, otherwise both methods will generate a parameterless method6 and those will conflict.
Related
I have a class named CAN_MSG for which I want to define the __eq__ method so that I can check two classes for equality.
Input_CAN_Sorter.py:
class CAN_MSG:
def __init__(self, first_sensor_id, timestamp, sensor_data):
self.first_sensor_id = first_sensor_id
self.sensor_data = sensor_data
self.timestamp = timestamp
def __eq__(self, other):
result = self.first_sensor_id == other.first_sensor_id
n = len(self.sensor_data) # no Error
i = len(other.senor_data) # AttributeError: 'CAN_MSG' object has no attribute 'senor_data'.
result = result and len(self.sensor_data) == len(other.senor_data)
for i in range(len(self.sensor_data)):
result = result and self.sensor_data[i] == other.senor_data[i]
result = result and self.timestamp == other.timestamp
return result
The class has a list of ints called sensor_data. When I compare using __eq__(self, other):
There is no problem with len(self.sensor_data), but with len(other.sensor_data) I get the following error:
AttributeError: 'CAN_MSG' object has no attribute 'senor_data'.
I don't understand why I can access self.sensor_data but not other.sensor_data.
test.py:
from Input_CAN_Sorter import CAN_MSG
list_temp = [1, 2, 3, 4, 5, 6, 7]
list_temp2 = [1, 2, 3, 4, 5, 6, 7]
CAN_MSG_1 = CAN_MSG(1, "TIME", list_temp)
CAN_MSG_2 = CAN_MSG(1, "TIME", list_temp2)
if CAN_MSG_1 == CAN_MSG_2:
print("=")
In C++ I would have done a check for the class type before and maybe a cast afterwards so that the compiler knows for sure that it is the same class, but in Python this is not possible/necessary if I understand correctly.
Probably this is a completely stupid mistake but I'm not 100% familiar with Python and can't come up with a reasonable explanation.
You misspelled your variable name.
AttributeError: 'CAN_MSG' object has no attribute 'senor_data'.
i = len(other.senor_data)
You meant to write:
i = len(other.sensor_data)
I'm trying to get the name of a enum given one of its multiple values:
class DType(Enum):
float32 = ["f", 8]
double64 = ["d", 9]
when I try to get one value giving the name it works:
print DType["float32"].value[1] # prints 8
print DType["float32"].value[0] # prints f
but when I try to get the name out of a given value only errors will come:
print DataType(8).name
print DataType("f").name
raise ValueError("%s is not a valid %s" % (value, cls.name))
ValueError: 8 is not a valid DataType
ValueError: f is not a valid DataType
Is there a way to make this? Or am I using the wrong data structure?
The easiest way is to use the aenum library1, which would look like this:
from aenum import MultiValueEnum
class DType(MultiValueEnum):
float32 = "f", 8
double64 = "d", 9
and in use:
>>> DType("f")
<DType.float32: 'f'>
>>> DType(9)
<DType.double64: 'd'>
As you can see, the first value listed is the canonical value, and shows up in the repr().
If you want all the possible values to show up, or need to use the stdlib Enum (Python 3.4+), then the answer found here is the basis of what you want (and will also work with aenum):
class DType(Enum):
float32 = "f", 8
double64 = "d", 9
def __new__(cls, *values):
obj = object.__new__(cls)
# first value is canonical value
obj._value_ = values[0]
for other_value in values[1:]:
cls._value2member_map_[other_value] = obj
obj._all_values = values
return obj
def __repr__(self):
return '<%s.%s: %s>' % (
self.__class__.__name__,
self._name_,
', '.join([repr(v) for v in self._all_values]),
)
and in use:
>>> DType("f")
<DType.float32: 'f', 8>
>>> Dtype(9)
<DType.float32: 'd', 9>
1 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.
Problem
I have a string and scores for each char in the string
text = 'text'
scores = [0.99, 0.98, 0.97, 0.96]
I would like to do many string operations to text, like re, +, slice or split, after do these operations, I want to keep correspond scores. For example, if I do a slice operation I want get sliced_text and sliced_scores as result:
sliced_text = text[0:2]
sliced_scores = scores[0:2]
Python3 has UserString class, for some simple operations like slice or add, it's very useful.
from collections import UserString
from typing import List
class ScoreString(UserString):
def __init__(self, text: str, scores: List[float]):
super().__init__(text)
assert len(text) == len(scores)
self.scores = scores
def __getitem__(self, index) -> 'ScoreString':
return self.__class__(self.data[index], self.scores[index])
def __add__(self, other) -> 'ScoreString':
return self.__class__(self.data + other.data, self.scores + other.scores)
score_str = ScoreString('Test123', [1, 2, 3, 4, 5, 6, 7])
a = score_str[0:2] + score_str[4:]
print(a.data) # Te123
print(a.scores) # [1, 2, 5, 6, 7]
But split or re not work.
print(score_str.split('12')) # return string list ['Test', '3'], lose scores
import re
re.sub('123', '', score_str) # TypeError: expected string or bytes-like object
Any suggestion?
Actually what you are trying to do in split function
score_str.split('12')
--> Here score_str is a class object which is inheriting UserString, so when you apply split function it will call the function of base class and you will get a normal output as str.split().
import re
re.sub('123', '', score_str)
--> Here when you are trying to implement regular expression, Then sub functions have arguments sub(pattern, repl, string, count=0, flags=0), but here in third argument you are passing score_str, which is an object of ScoreString. That is why you are getting error.
If you will re-write as below,
re.sub('123', '', score_str.data)
It should work..
I hope this will help.
I am trying to create some more functional code in Python and I want to know if it is possible to transform dictionary (key,values) to pass as a function parameter.
I am currently doing this in a more imperative way, where I filter and then manually extract each key depending on the result of the filter. My current code:
def a(i: int, config: dict):
function_array = [function1, function2, function3]
selected = function_array[i]
if (i == "0"):
result = selected(x = config['x'])
elif (i == "1"):
result = selected(y = config['y'])
elif (i == "2"):
result = selected(z = config['z'])
return result
The current result is correct, but when I have many cases, I need to hardcode each parameter for the specified function. So, that is why I want to know if it is possible to pass the config object as I want (with an x when i is 0, for example) and then just do something like this:
def a(i: int, config: dict):
function_array = [function1, function2, function3]
result = function_array[i](config)
return result
The syntax for passing items from a dictionary as function parameters is simply selected(**config)
So for your example, it would look something like this:
def function1(x=0):
return x + 1
def function2(y=42):
return y * 2
def function3(z=100):
return z
def a(i, config):
function_array = [function1, function2, function3]
selected = function_array[i]
return selected(**config)
config = {x: 10}
a(0, config) # calls function1(x=10)
config = {y: 20}
a(1, config) # calls function2(y=20)
config = {}
a(2, config) # calls function3()
Every python function can be instructed to take a dictionary of keywords. See e.g. https://www.pythoncheatsheet.org/blog/python-easy-args-kwargs . (Official source at https://docs.python.org/3/reference/compound_stmts.html#function-definitions, but it's harder to read.)
You could do:
def a(i: int, keyword: str, **kwargs: dict):
if keyword in kwargs:
result = kwargs[keyword](i)
and you would run it with something like:
a(5, "func3", func1=print, func2=sum, func3=all)
Or, you could just pass a dictionary itself into the function:
def a(i: int, keyword: str, config: dict)
if keyword in config:
result = config[keyword](i)
This would be run with something like:
a(5, "func3", {"func1": print, "func2": sum, "func3": all})
The only difference is that the ** in the function declaration tells python to automatically make a dictionary out of explicit keywords. In the second example, you make the dictionary by yourself.
There's an important thing happening behind the scenes here. Functions are being passed around just like anything else. In python, functions are still objects. You can pass a function just as easily as you can pass an int. So if you wanted to have a list of lists, where each inner list is a function with some arguments, you easily could:
things_to_do = [[sum, 5, 7, 9], [any, 1, 0], [all, 1, 0]]
for thing_list in things_to_do:
function = thing_list[0]
args = thing_list[1:]
print(function(args))
And you'll get the following results:
21
True
False
(Note also that all of those functions take an iterable, such as a list. If you want to pass each argument separately, you would use *args instead of args.)
You can do it with defined functions, too. If you have
def foo1(arg1):
pass
def foo2(arg1, arg2):
pass
you can just as easily have
things_to_do = [[sum, 5, 7, 9], [foo1, 'a'], [foo2, 0, None]]
Im running a mapreduce job, but it keeps failing saying missing input. Unfortunately its not showing where its missing either
from mrjob.job import MRJob
from mrjob.step import MRStep
import re
class flight_combination(MRJob):
def steps(self):
return [MRStep(mapper=self.mapper_1,reducer=self.reducer_1)]
def mapper_1(self,_,value):
group1 = {}
group2 = {}
parts = value.split(",")
destination = parts[0]
origin = parts[1]
count = parts[2]
group1[destination] = {'Origin': origin, 'count': count}
group2[origin] = {'Destination':destination,'count':count}
yield group1
yield group2
def reducer_1(self,key,value):
g1,g2 = data
for key1 in g1:
for key2 in g2:
if g1[key1]['Origin'] == g2[key2]['Destination']:
total = int(g1[key1]['count'])*int(g2[key2]['count'])
yield (key1,key2,total)
if __name__ == '__main__':
flight_combination.run()
Following is the error:
`File "wd.py", line 35, in <module>
flight_combination.run()
…...
File "/usr/lib/python3.6/site-packages/mrjob/job.py", line 536, in run_mapper
for out_key, out_value in mapper(key, value) or ():
ValueError: not enough values to unpack (expected 2, got 1)`
The run method of the object type flight_combination is expecting 2 arguments, but provided with 1 argument. ( Python by default takes self as the first argument for methods invoked on object)
To fix this -
as the method run is defined in the parent class, go through its definition and pass the other argument.
Override the run method by redefining it flight_combination class and provide your logic.