How to use unquoted keyword-only function args in Python - python-3.x

It is easy to create optional arguments in a function by specifying a default in the def.
for example sorted([1,3,2],reverse=True)
if I were defining this function I might say
def sorted(l:list, reverse:bool=False)
is there any way to define a function's parameters so that I can specify keywords without values? Simply mentioning the keyword makes it true. If there were I could define a function that accepts this
sorted([1,3,2],reverse)
I tried using **kwargs but apparently, it only accepts keywords that are included with values.

The closest to what I was looking for would be to use an Enum
import enum
class SortDirection(enum.Enum):
REGULAR = None
REVERSE = 2
def sorted(l:list, reverse:SortDirection=None):
print(reverse)
sorted([], SortDirection.REVERSE)
So the above program outputs
SortDirection.REVERSE

Related

Python decorator which adds a new parameter to a function

I have an addition function with two parameters a and b which simply adds a and b. To round this number, I have made a decorator factory which takes a decimals parameter and round the results of the addition function to the specified number of decimals. However, this requires the decimals number to be set at function definition. I am looking for a more dynamic approach.
Is it instead possible to make the decorator alter the addition function, such that the addition function gets a new parameter "decimals"? Here is my code:
import functools
def rounding(decimals):
def decorator(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
return round(func(*args, **kwargs), decimals)
return wrapper
return decorator
#rounding(decimals=2)
def addition(a: float, b: float):
return a + b
addition(a=1.0003, b=2.01)
So this makes the addition function always round to 2 decimals. What I instead want my decorator to do, is add a new arguments, such that I can call
addition(a=1.0003, b=2.01, decimals=2)
Is there a way to do this and if yes, is there a way to do this such that function docs still shows a, b and decimals instead of *args, **kwargs (for example when pressing ctrl+P in pycharm on the function)
I have just started working my way around python decorators. I have used https://realpython.com/primer-on-python-decorators/ as inspiration. I have not been able to find an existing answer on this site. The nearest I got was related to partial functions like here, but perhaps I am just searching for the wrong thing.
I am using Python 3.10.
You've made it too complicated.
You don't want a decorator which takes arguments, so you need only 2 levels of nested functions.
Simply add the decimals parameter to the function returned by the decorator.
def rounding():
#functools.wraps(func)
def wrapper(a, b, decimals):
return round(func(a, b), decimals)
return wrapper

Simplifying Init Method Python

Is there a better way of doing this?
def __init__(self,**kwargs):
self.ServiceNo = kwargs["ServiceNo"]
self.Operator = kwargs["Operator"]
self.NextBus = kwargs["NextBus"]
self.NextBus2 = kwargs["NextBus2"]
self.NextBus3 = kwargs["NextBus3"]
The attributes (ServiceNo,Operator,...) always exist
That depends on what you mean by "simpler".
For example, is what you wrote simpler than what I would write, namely
def __init__(self,ServiceNo, Operator, NextBus, NextBus2, NextBus3):
self.ServiceNo = ServiceNo
self.Operator = Operator
self.NextBus = NextBus
self.NextBus2 = NextBus2
self.NextBus3 = NextBus3
True, I've repeated each attribute name an additional time, but I've made it much clearer which arguments are legal for __init__. The caller is not free to add any additional keyword argument they like, only to see it silently ignored.
Of course, there's a lot of boilerplate here; that's something a dataclass can address:
from dataclasses import dataclass
#dataclass
class Foo:
ServiceNo: int
Operator: str
NextBus: Bus
NextBus2: Bus
NextBus3: Bus
(Adjust the types as necessary.)
Now each attribute is mentioned once, and you get the __init__ method shown above for free.
Better how? You don’t really describe what problem you’re trying to solve.
If it’s error handling, you can use the dictionary .get() method in the event that key doesn’t exist.
If you just want a more succinct way of initializing variables, you could remove the ** and have the dictionary as a variable itself, then use it elsewhere in your code, but that depends on what your other methods are doing.
A hacky solution available since the attributes and the argument names match exactly is to directly copy from the kwargs dict to the instance's dict, then check that you got all the keys you expected, e.g.:
def __init__(self,**kwargs):
vars(self).update(kwargs)
if vars(self).keys() != {"ServiceNo", "Operator", "NextBus", "NextBus2", "NextBus3"}:
raise TypeError(f"{type(self).__name__} missing required arguments")
I don't recommend this; chepner's options are all superior to this sort of hackery, and they're more reliable (for example, this solution fails if you use __slots__ to prevent autovivication of attributes, as the instance won't having a backing dict you can pull with vars).

adding cutom types to variables in python function

In typescript i have the option to do something like this:
async tempFunction(vartA:'abc'|'dfe'){
let x = vartA
}
How can i do something similar in Python? (to add specific types to variable within the function definition)
Of course tried the following, but it seems that it doesn't accept it:
def tempFunction(vartA:'abc' or 'dfe'):
pass
is there any override / alternative method to implement it?

Passing a function (with arguments) as an argument in Python

I am measuring performance of different sorting methods using Python built-in library timeit. I would like to pass a function and an integer as arguments to the statement being tested in timeit(). I tried the following:
def sort_1(l):
...
return l_sorted
def test(f: Callable, l_len: int):
l = np.random.rand(low=-1000, high=1000, size=l_len)
f(l)
timeit.timeit(stmt=test(sort_1, l_len=10), number=1000)
... with a ValueError saying that stmt is neither a string nor callable. The error doesn't occur when I call it like this:
timeit.timeit(stmt=test, number=1000)
... but then I cannot pass any argument to test(). What is a general solution if someone wants to pass arguments to a function given as an argument? (let's say, when a method is already implemented and there is not way to pass arguments in a separate argument)
Cheers
Edit:
#jonrsharpe, thanks! The solution looks like this:
timeit.timeit(stmt='test(f=sort_1, l_len=10)', number=100, globals=globals())

Mypy: annotating a variable with a class type

I am having some trouble assigning the variables in a Python 3.6 class to a particular type--a Pathlib path. Following an example from link, I tried to create a TypeVar, but mypy is still throwing errors. I want to make sure that the class variables initialized in the __init__.py only receive a particular type at compile time. So this is just a check to make sure I don't inadvertently set a string or something else to these class variables.
Can anyone suggest the correct way to do this?
Here is some simple code.
import pathlib
from typing import Union, Dict, TypeVar, Type
Pathtype = TypeVar('Pathtype', bound=pathlib.Path)
class Request:
def __init__(self, argsdict):
self._dir_file1: Type[Pathtype] = argsdict['dir_file1']
self._dir_file2: Type[Pathtype] = argsdict['dir_file2']
The error that I am getting is:
Request.py:13: error: Invalid type "Request.Pathtype"
Request.py:14: error: Invalid type "Request.Pathtype"
Neither Type, TypeVar nor NewType are correct to use here. What you simply want to do is use Path itself:
from pathlib import Path
class Request:
def __init__(self, argsdict):
self._dir_file1: Path = argsdict['dir_file1']
self._dir_file2: Path = argsdict['dir_file2']
If you annotate your argsdict as being of type Dict[str, Path], you can skip having to annotate your fields entirely: mypy will infer the correct type:
from typing import Dict
from pathlib import Path
class Request:
def __init__(self, argsdict: Dict[str, Path]):
self._dir_file1 = argsdict['dir_file1']
self._dir_file2 = argsdict['dir_file2']
Here's a brief explanation of what the various type constructs you were attempting to use/was suggested to you actually do:
TypeVar is used when you are trying to create a generic data structure or function. For example, take List[int], which represents a list containing ints. List[...] is an example of a generic data structure: it can be parameterized by any arbitrary type.
You use TypeVar as a way of adding "parameterizable holes" if you decide you want to create your own generic data structure.
It's also possible to use TypeVars when writing generic functions. For example, suppose you want to declare that you have some function that can accept a value of any type -- but that function is guaranteed to return a value of the exact same type. You can express ideas like these using TypeVars.
The Type[...] annotation is used to indicate that some expression must be the type of a type. For example, to declare that some variable must hold an int, we would write my_var: int = 4. But what if we want to write something like my_var = int? What sort of type hint could we give that variable? In this case, we could do my_var: Type[int] = int.
NewType basically lets you "pretend" that you're taking some type and making a subclass of it -- but without requiring you to actually subclass anything at runtime. If you're careful, you can take advantage of this feature to help catch bugs where you mix different "kinds" of strings or ints or whatever -- e.g. passing in a string representing HTML into a function expecting a string representing SQL.
Replace TypeVar with NewType and remove the Type[] modifier.

Resources