3

I'm using a library called unit-convert. The interface looks like this:

# Bytes to terabytes
>>> UnitConvert(b=19849347813875).tb

Suppose I have strings taken from user input (omitting the input code) like so:

input_value_unit = 'b'
output_value_unit = 'tb'

How can I substitute these into the call?

I tried using UnitConvert(input_value_unit=user_input_value).output_value_unit, but this doesn't use the string values.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • Welcome to Stack Overflow. I edited the question to show good style for Stack Overflow questions - cutting to the root of the question, showing the part of the example that is actually relevant to the problem (a [mre]), and asking directly. Please keep in mind that this is **not a discussion forum**. – Karl Knechtel Jan 23 '23 at 00:35
  • That said, there are two completely different problems to solve (aside from ones created by your existing code, such as using `==` where `in` is needed as the first attempt at an answer pointed out). There is a straightforward technique for each of those substitutions, but they work differently from each other. They *should* also have common reference duplicate questions, but I can't easily find them right now. – Karl Knechtel Jan 23 '23 at 00:38
  • Rather than a complex approach, the OP may well better make use of `if input_value_unit == 'b' and output_value unit == 'tb': x = UnitConvert(b=19849347813875).tb` and so on. – user19077881 Jan 23 '23 at 00:49
  • @user19077881 That is going to mean O(n^2) lines of code with nested `if`s. The dynamic approach is a lot simpler, it just needs some background information. – Karl Knechtel Jan 23 '23 at 00:53
  • I have been thinking about writing some canonicals from scratch on the relevant theme (working with *namespaces* in Python, and looking up values dynamically in namespaces). One of the canonicals in that series might be perfect to answer this in the future, so I saved this question for future cleanup as well. The way this question was phrased, also reshaped my thinking a bit - in particular, I realized that the way the two halves of this problem are handled, aren't really that different from each other. – Karl Knechtel Jan 23 '23 at 01:15
  • 1
    This really bugged me for some reason, so I raised an issue: https://github.com/huntfx/unit-convert/issues/1 – Mad Physicist Jan 23 '23 at 01:17
  • 1
    Hey, author here (thanks for raising the issue), it was a very quick simple script I made back in 2019 and it hasn't received any love since - I'm away from my PC right now but but I'll definitely be making some improvements in the near future. – Peter Jan 23 '23 at 09:09

2 Answers2

3

Code like function(x=1) doesn't care if there's a variable named x naming a string; the x literally means x, not the x variable. Similarly for attributes: x.y doesn't care if there is a y variable naming a string; it will just get the y attribute of x.

However, we can use strings to specify both of these things "dynamically".

To replace the b in the example, we need to use a string as a keyword argument name. We can do this by making a dictionary for the keyword arguments, and then using ** to pass them. With a literal string, that looks like: UnitConvert(**{'b': ...}).

To replace the tb, we need to use a string as an attribute name. We can do this by using the built-in getattr to look up an attribute name dynamically. With a literal string, that looks like: getattr(UnitConvert(...), 'tb').

These transformations let us use a literal string instead of an identifier name.

Putting it together:

# suppose we have read these from user input:
input_value_unit = 'b'
output_value_unit = 'tb'
input_amount = 19849347813875
# then we use them with the library:
getattr(UnitConvert(**{input_value_unit: input_amount}), output_value_unit)
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • Looks like this is the right answer for now. I raised an issue with the author of the library because it's just not a good interface – Mad Physicist Jan 23 '23 at 01:18
  • Your design looks much better, yes. I thought of using a named method rather than `__getitem__`, but otherwise essentially the same. Personally, when I want unit conversion in my code, I typically just choose a unit (say `SECONDS = 1`) and establish other constants in relation to that (`MINUTES = 60` etc.) and do the math (`time.sleep((10 * MINUTES) / SECONDS)`, which I read as "10 minutes in seconds"). A more sophisticated approach would use a custom type to ensure correct "dimensional analysis". – Karl Knechtel Jan 23 '23 at 01:26
  • The best design I've seen so far is using overloaded operators on a custom units object. So you could do something like `5 * min` and use it in expressions with other times. – Mad Physicist Jan 23 '23 at 04:03
  • 1
    A couple of libraries with better interfaces: https://pypi.org/project/units/ and my favorite https://pypi.org/project/Pint/ – Mad Physicist Jan 23 '23 at 04:24
-1

Edit again - perhaps I still misunderstand. You're using an existing module that you downloaded?

Now that your code has been pared back to look nothing like the original, my first answer no longer applies. I'll leave it below the underline because you should still be aware.

Usually in your situation the second unit would be passed as a second parameter to the function. Then the function can do the appropriate conversion.

UnitConvert(user_input_value, output_value_unit)

There's an alternative that looks a little closer to what you had in mind. If your function returns a dictionary with all the possible conversions, you can select the one you need.

UnitConvert(user_input_value)[output_value_unit]

The old irrelevant answer. Your statement:

if user_input_convert == ["kilometres", "miles", "nanometres", "metres"]:

is comparing a single string to a list of strings. They will never be equal. What you probably want is:

if user_input_convert in ["kilometres", "miles", "nanometres", "metres"]:

That checks to see if your string is equal to one of the strings in the list.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • While this is certainly one of the issues with the code (and the issue with the conditional mentioned in the title) it doesn't address the issue with `UnitConvert(user_input_value_unit=user_input_value).user_input_convert` which is their attempt to use the _value_ of user input as one of the keywords and attribute access. _i.e._ `user_input_value_unit = 'b'`, `user_input_value=19849347813875` and `user_input_convert='tb'` and they want to be able to do something like: `UnitConvert(b=19849347813875).tb` but using the variables. – Henry Ecker Jan 23 '23 at 00:35
  • This could be considered a typo, and is a problem irrelevant to what OP was actually asking about - I had to remove the corresponding code in editing in order to make the question properly readable. – Karl Knechtel Jan 23 '23 at 00:36
  • 1
    We appear to be talking here about an existing third-party library with a specific interface, not about designing an interface to make it possible to use things the way we want. – Karl Knechtel Jan 23 '23 at 00:54
  • 1
    "perhaps I still misunderstand" - when I read the initial version of the question, the two key notes I made were "Im trying to replace b, and .tb with the input variables.", and that OP named a specific library (implying the interface is fixed). At this point, I judged that the code attempt would be more or less worthless regardless, and only distract from the question - unfortunately I guess you were already writing an answer. It seems like OP planned to use conditional logic to enumerate every permitted combination of inputs, and write the corresponding call for each. – Karl Knechtel Jan 23 '23 at 01:06
  • @KarlKnechtel I've been around long enough to know a poorly written question, but this one blindsided me. I saw an obvious problem and jumped on it without stopping to understand the question in depth. Congratulations to you for digging into it, you even found the exact module they're trying to use! I hope I remember to delete this answer later, I'm sure it won't help anybody. – Mark Ransom Jan 23 '23 at 03:28
  • Actually, [someone else](https://stackoverflow.com/users/235698/mark-tolonen) edited in the link for the package. – Karl Knechtel Jan 23 '23 at 03:29
  • @KarlKnechtel I just assumed it was you since you were credited with the latest edit, my mistake. And looking at the full edit history it looks like the package name was there from the beginning, I feel like a total idiot now. – Mark Ransom Jan 23 '23 at 03:36