Casting Constructor in Python - python

I'm using stricly typed Python, and would like to achieve something similar to the copy/move constructor overloading of C++. That is, I'd like to make my object convertible to another type using an explicit definition.
Here's an example:
class CallerSurface(StringEnum):
IOS_APPLICATION = "ios_application"
ANDROID_APPLICATION = "android_application"
WEB_APPLICATION = "web_application"
which I can use with some function such as:
def getResponse(data: Data, caller_name: CallerSurface) -> str:
I'd like to add some part of the definition of class CallerSurface to make it possible for a function that takes a param of type CallerSurface, to also take a param of type str, and just "know" how to convert the str to CallerSurface without the programmer needing to explicitly figure out a conversion.
So I want to use in the following way:
caller_name: str = HTTPUtils.extractCallerFromUserAgent(request)
response = getResponse(other_data, caller_name)
caller_name is a str, but getResponse takes a CallerSurface. I'd like to make the conversion be implicitly defined in then CallerSurface class.
In C++ you could achieve this by defining a copy and a move constructor that takes in string. Is there something in Python for this?

There is no way to automate the conversion (there's no such thing as implicit type conversion of the sort you're looking for), so your options are:
Expand the allowed argument types (caller_name: CallerSurface becomes caller_name: Union[CallerSurface, str]), manually convert the related types to the intended type, or
Use #functools.singledispatch to make multiple versions of the function, one that accepts each type, where all but one implementation just converts to the intended type and calls itself
In short, this isn't C++, and implicit type conversions aren't a thing in the general case.

Python doesn't do type conversion implicitly. It does provide things like the __str__ magic method, but that conversion still requires an explicit call to the str() function.
What you want to do, I think, is leave the typing as CallerSurface, and use a static type checker to force the caller to do (e.g.):
caller_name = HTTPUtils.extractCallerFromUserAgent(request)
response = getResponse(other_data, CallerSurface(caller_name))
Using a type checker (e.g. mypy) is key, since that makes it impossible to forget the CallerSurface call (or whatever other kind of conversion needs to happen).

Related

declare variable type inside function

I am defining a function that gets pdf in bytes, so I wrote:
def documents_extractos(pdf_bytes: bytes):
pass
When I call the function and unfortunately pass a wrong type, instead of bytes let's say an int, why I don't get an error? I have read the documentation regarding typing but I don't get it. Why is the purpose of telling the function that the variable shoudl be bytes but when you pass and int there is no error? This could be handle by a isinstance(var, <class_type>) right? I don't understand it =(
Type hints are ignored at runtime.
At the top of the page, the documentation that you've linked contains a note that states (emphasis mine):
The Python runtime does not enforce function and variable type annotations. They can be used by third party tools such as type checkers, IDEs, linters, etc.
The purpose of type hints is for static typechecking tools (e.g. mypy), which use static analysis to verify that your code respects the written type hints. These tools must be run as a separate process. Their primary use is to ensure that new changes in large codebases do not introduce potential typing issues (which can eventually become latent bugs that are difficult to resolve).
If you want explicit runtime type checks (e.g. to raise an Exception if a value of a wrong type is passed into a function), use isinstance().
By default python ignores type hints at runtime, however python preserves the type information when the code is executed. Thanks to this library authors can implement runtime type checking packages such as typeguard, pydantic or beartype.
If you don't want to use isinstance checks yourself, you can use one of those libraries.
Typeguard example:
main.py:
from typeguard import importhook
importhook.install_import_hook('mypack')
import mypack
mypack.documents_extractos("test")
mypack.py
def documents_extractos(pdf_bytes: bytes):
pass
When you run python3 main.py you will get error TypeError: type of argument "pdf_bytes" must be bytes-like; got str instead

Python default type in class type arguments

I'm trying to use generic type hints in python, however I'm not sure if a behaviour I want is possible or not. I've done a lot of reading on the type hints documentation, but cannot find what I'm looking for.
I want to be able to specify class type arguments with default types if the type isn't defined. For instance, I would imagine the code would look something like this.
T = TypeVar('T')
S = TypeVar('S')
class Attribute(Generic[T, S=int]):
...
class FooBar:
a: Attribute[str, str]
b: Attribute[str]
So in this case type S would default to int if it is not specified in a type hint. I am also happy with a solution which uses metaprogramming to make this possible.
I think you're gonna have to wait till Python 3.12 if I'm understanding this correctly.
PEP 696 – Type defaults for TypeVarLikes | peps.python.org
The whole idea behind generic types stems from statically-typed languages (eg. Scala or Java) and it is to allow flexibility for types when defining code, and prevents unnecessary excessive overloading for functions (which is not needed in Python being a dynamically-typed language). Hence defining default or fallback types kind of deviates from the concepts of generic altogether. With that being said, I see that the Union[] is a better fit to answer your question.
If you would like to combine that with generic types (which does not make much sense given the logic behind generics), you can do something like:
from typing import Union, TypeVar
T = TypeVar('T')
def foo(var: Union[T, str]):
if var:
return "boo"
res = foo("bar")
print(res)
However, I suggest that when defining your code, try to be specific and strict when possible regarding types, and to define better generics. That can be accomplished by defining more strict generic/abstract types:
T = TypeVar('T') # Can be anything
A = TypeVar('A', str, bytes) # Must be str or bytes
The latter will allow you to remain generic,with slightly more control over your types.

What type hint can I use in Python if my type is a function?

I want to use type hints in Python, but there are certain cases where the type of my parameters are not clear to me... do functions have a type, or are they a special case. I would like to do something like the following:
pseudocode:
def run_function(function_to_run: fn):
function_to_run(data)
is this possible? If so, what type should I use?
edit:
I was hoping to distinguish a function from other callables such as classes... but maybe that doesn't matter and is overly cautious.
as mentioned in the documentation: you can use Callable.
you could for example use it this way:
from typing import Callable
def run_function(function_to_run: Callable[[int], str]):
function_to_run(data)
if function_to_run is a function from int to str.
typing.Callable should work fine. You don't actually care that it's specifically a function, it could be a class or a callable instance of some sort.
While you almost certainly don't care about the distinction between the various callable types, you can enforce that if you wish The types module provides quite a few specific names for different kinds of functions (some of which you may not even be thinking of as different types). Just to name a few:
types.FunctionType and types.LambdaType (alias for the same underlying type)
types.BuiltinMethodType and types.BuiltinFunctionType
types.MethodWrapperType
types.MethodDescriptorType

With python3 function annotation, how do I check type of function as input?

I want to use function to check whether an argument passed to a function is a function or not.
I was doing something like:
from types import FunctionType
def f(ff: FunctionType):
# do something
It seems wrong, as I can literaly pass anything to f.
How do I check argument passed in is a function or not with annotation?
Python on its own does not enforce types whatsoever - what you shown is just type hinting, which can be use by tools like linters to warn you that you're about to do something that may not work correctly.
If you want to assert the type, you have to do it explicitly inside the function body. Or you can use a static type checker, like mypy.

Creating a customized language using Python

I have started playing with Sage recently, and I've come to suspect that the standard Python int is wrapped in a customized class called Integer in Sage. If I type in type(1) in Python, I get <type 'int'>, however, if I type in the same thing in the sage prompt I get <type 'sage.rings.integer.Integer'>.
If I wanted to replace Python int (or list or dict) with my own custom class, how might it be done? How difficult would it be (e.g. could I do it entirely in Python)?
As an addendum to the other answers: when running any code, Sage has a preprocessing step which converts the Sage-Python to true Python (which is then executed). This is done by the preparse function, e.g.
sage: preparse('a = 1')
'a = Integer(1)'
sage: preparse('2^40')
'Integer(2)**Integer(40)'
sage: preparse('F.<x> = PolynomialRing(ZZ)')
"F = PolynomialRing(ZZ, names=('x',)); (x,) = F._first_ngens(1)"
This step is precisely what allows the transparent use of Integers (in place of ints) and the other non-standard syntax (like the polynomial ring example above and [a..b] etc).
As far as I understand, this is the only way to completely transparently use replacements for the built-in types in Python.
You are able to subclass all of Python's built-in types. For example:
class MyInt(int):
pass
i = MyInt(2)
#i is now an instance of MyInt, but still will behave entirely like an integer.
However, you need to explicitly say each integer is a member of MyInt. So type(1) will still be int, you'll need to do type(MyInt(1)).
Hopefully that's close to what you're looking for.
In the case of Sage, it's easy. Sage has complete control of its own REPL (read-evaluate-print loop), so it can parse the commands you give it and make the parts of your expression into whatever classes it wants. It is not so easy to have standard Python automatically use your integer type for integer literals, however. Simply reassigning the built-in int() to some other type won't do it. You could probably do it with an import filter, that scans each file imported for (say) integer literals and replaces them with MyInt(42) or whatever.

Categories