I am looking to return a status when setting an instance variable. This return value will either indicate that the value was set or the value was invalid. For example:
class C():
def __init__(self):
self._attr = 1
#property
def attr(self):
return self._attr
#attr.setter
def attr(self, val):
if val < 10:
self._attr = val
return 'value set'
else:
return 'value must be < 10'
status = C().attr = 11 # should return failed
status = C().attr = 9 # should return worked
Adding return values to all my setters would be time-consuming and seems to be bad practice. Is there a better way to get a status from a setter?
The other solution I thought of was to write a "setSetter()" function that calls attr = val then checks if attr == val (which works like a status variable). However, it seems like there should be a better method.
Also, if there is a better way to structure the flow control so that attributes aren't set to invalid values I'm open to changing my strategy.
Thanks in advance for the help!
The standard way to indicate failure in Python and/or prevent code from reaching an invalid state is to raise an exception to indicate you've encountered a situation where you can't continue execution. This doesn't require you to modify the return type:
#attr.setter
def attr(self, val: int) -> None:
if val < 10:
self._attr = val
else:
raise ValueError('value must be < 10')
try:
C().attr = 9 # works
C().attr = 11 # raises ValueError and goes to nearest matching except
C().attr = 13 # is never executed
except ValueError as e:
print(e) # prints 'value must be < 10'
Related
I need to put a limit when entering a number for a certain attribute, how can I do that?
class Escuelas:
def __init__(self):
self.cod_provincia = 0:1
You should use Encapsulation principle.
class Escuelas:
def __init__(self):
self.__cod_provincia = 0
#property
def cod_provincia(self):
return self.__cod_provincia
#cod_provincia.setter
def cod_provincia(self, value):
if isinstance(value, int) and value in (0, 1):
self.__cod_provincia = value
else:
raise ValueError('Not Valid Code!')
if you want set value in specific range you just use range(start, end) instead of the tuple (0, 1) in setter method.
Maybe somewhere in your code, you need something like below:
if self.cod_provincia not in [0, 1]:
raise ValueError("Entrada de código no válida.")
If I understand you correctly, you want to force an int value to be within a certain range when it is set.
Is this what you require?
class Escuelas:
def __init__(self):
self.intValue = None
self.upperLimit = 100
def setIntValue(self,newValue):
self.intValue = self.upperLimit if newValue>self.upperLimit else newValue
e = Escuelas()
e.setIntValue(200)
print(e.intValue)
This sets the value to 100 (the upper limit) and not 200.
I have the following dataclass Gear that I want to limit the maximum value for gear_level from 0 to 5. But as you can see when I increment gear_level, it goes higher than 5, which is not what I want. I tried method as well as postinit. How do I fix this problem?
from dataclasses import dataclass
#dataclass
class Gear:
gear_level: int = 0
direction: str = None
# more codes ...
def __postinit__(self):
if self.gear_level <= 0:
self.gear_level = 0
elif 5 > self.gear_level > 0:
self.gear_level = self.gear_level
else:
self.gear_level = 5
def set_gear_level(self, level):
if level <= 0:
self.gear_level = 0
elif 5 > level > 0:
self.gear_level = level
else:
self.gear_level = 5
g = Gear()
g.set_gear_level(6)
print(g)
g.gear_level += 1
print(g)
g.set_gear_level(-1)
print(g)
g.gear_level -= 1
print(g)
Ideally, I prefer to use the g.gear_level += 1 notation, because I want to increment gear_level. It should not jump from gear level 1 to 5. Also, when it decrement, it should stop at 0. It should take both an assignment of 0 and be allowed to decrement to 0. Can this be done?
Gear(gear_level=5, direction=None)
Gear(gear_level=6, direction=None)
Gear(gear_level=0, direction=None)
Gear(gear_level=-1, direction=None)
In this case I would simply use a property:
#dataclass
class Gear:
gear_level: int
# Rest of the class excluded for simplicity
#property
def gear_level(self) -> int:
return self._gear_level
#gear_level.setter
def gear_level(self, value: int) -> None:
self._gear_level = min(max(value, 0), 5)
This way you don't need to write a __post_init__ or have to remember to call specific methods: assignment to gear_level will be kept 0 <= gear_level <= 5, even with +=.
The suggested link in the comments provides an elegant solution for tackling this issue, e.g. using a custom descriptor class which should work with minimal changes needed on your end.
For example, here's how I'd define a BoundsValidator descriptor class to check that a class attribute is within an expected lower and upper bounds (note that either bounds are optional in this case):
from typing import Optional
try:
from typing import get_args
except ImportError: # Python 3.7
from typing_extensions import get_args
class BoundsValidator:
"""Descriptor to validate an attribute x remains within a specified bounds.
That is, checks the constraint `low <= x <= high` is satisfied. Note that
both low and high are optional. If none are provided, no bounds will be
applied.
"""
__slots__ = ('name',
'type',
'validator')
def __init__(self, min_val: Optional[int] = None,
max_val: Optional[int] = float('inf')):
if max_val is None: # only minimum
def validator(name, val):
if val < min_val:
raise ValueError(f"values for {name!r} have to be > {min_val}; got {val!r}")
elif min_val is None: # only maximum
def validator(name, val):
if val > max_val:
raise ValueError(f"values for {name!r} have to be < {max_val}; got {val!r}")
else: # both upper and lower bounds are given
def validator(name, val):
if not min_val <= val <= max_val:
raise ValueError(f"values for {name!r} have to be within the range "
f"[{min_val}, {max_val}]; got {val!r}")
self.validator = validator
def __set_name__(self, owner, name):
# save the attribute name on an initial run
self.name = name
# set the valid types based on the annotation for the attribute
# for example, `int` or `Union[int, float]`
tp = owner.__annotations__[name]
self.type = get_args(tp) or tp
def __get__(self, instance, owner):
if not instance:
return self
return instance.__dict__[self.name]
def __delete__(self, instance):
del instance.__dict__[self.name]
def __set__(self, instance, value):
# can be removed if you don't need the type validation
if not isinstance(value, self.type):
raise TypeError(f"{self.name!r} values must be of type {self.type!r}")
# validate that the value is within expected bounds
self.validator(self.name, value)
# finally, set the value on the instance
instance.__dict__[self.name] = value
Finally, here's the sample code I came up with to test that it's working as we'd expect:
from dataclasses import dataclass
from typing import Union
#dataclass
class Person:
age: int = BoundsValidator(1) # let's assume a person must at least be 1 years
num: Union[int, float] = BoundsValidator(-1, 1)
gear_level: int = BoundsValidator(0, 5)
def main():
p = Person(10, 0.7, 5)
print(p)
# should raise a ValueError now
try:
p.gear_level += 1
except ValueError as e:
print(e)
# and likewise here, for the lower bound
try:
p.gear_level -= 7
except ValueError as e:
print(e)
# all these should now raise an error
try:
_ = Person(0, 0, 2)
except ValueError as e:
print(e)
try:
_ = Person(120, -3.1, 2)
except ValueError as e:
print(e)
if __name__ == '__main__':
main()
This provides the output below when we run the code:
Person(age=10, num=0.7, gear_level=5)
values for 'gear_level' have to be within the range [0, 5]; got 6
values for 'gear_level' have to be within the range [0, 5]; got -2
values for 'age' have to be within the range [1, inf]; got 0
values for 'num' have to be within the range [-1, 1]; got -3.1
There is also an excellent library called param that enables you to achieve this rather easily. In your case it would look something like this:
import param
#dataclass
class Gear:
gear_level: int = param.Integer(1, bounds=(0,5))
direction: str = None
g = Gear()
print(g)
g.gear_level = 42 # This throws an exception
It has many other neat features for designing robust interfaces.
I have a DynamicArray class shown below. (I have only included relevant methods. The rest can be viewed from https://www.geeksforgeeks.org/implementation-of-dynamic-array-in-python/)
import ctypes
class DynamicArray:
'''
Dynamic Array class
'''
def __init__(self):
self.n = 0 # count number of elements
self.capacity = 1 # default capacity
self.A = self.make_array(self.capacity)
def __len__(self):
"""
Return number of elements in the array
"""
return self.n
def __getitem__(self,k):
"""
Return element at k index
"""
#Check if K index is out of bounds#
if not 0 <= k < self.n:
return IndexError('{} is out of bounds'.format(k))
return self.A[k] #Retrieve from the array at index k#
Then I have another unit test file down below
from DynamicArray import DynamicArray
import unittest
class MyTestCase(unittest.TestCase):
def setUp(self) -> None:
self.a = DynamicArray() # empty array
self.b = DynamicArray()
self.b.append(0)
self.c = DynamicArray()
self.c.append(0)
self.c.append(1)
def test_getitem(self):
self.assertEqual(self.a.__getitem__(0),IndexError('0 is out of bounds'))
When I run the test I expect self.a.__getitem__(0) to throw IndexError('0 is out of bounds') and I can't see why the assertion fails? The only difference is that self.a.__getitem__(0) will yield IndexError('{} is out of bounds'.format(0)), which seems to me the same as IndexError('0 is out of bounds')
I tried running below code to see if the string by themselves were any different
if '{} is out of bounds'.format(0) == '0 is out of bounds':
print('str equal')
if '{} is out of bounds'.format(0).__len__() == '0 is out of bounds'.__len__():
print('len equal')
if IndexError('{} is out of bounds'.format(0)) == IndexError('0 is out of bounds'):
print('IndexError equal')
and confirmed that only the third if statement did not print
below is the photo of the console
Thanks in advance. Constructive criticisms and feedbacks are welcome.
Exceptions can't be conpared with assertEqual.
with self.assertRaises(IndexError, msg='0 is out of bounds'):
self.a[0]
And Exceptions must be raiseed to be captured.
You're returning IndexError
raise IndexError('{} is out of bounds'.format(k))
https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises
I have data like data = [[t1, t2, ...], [v1, v2, ...]]. I want to wrap this in a class so I can call data.t instead of having to use data[0].
I tried to do this with the following:
class Variable:
def __init__(self, data):
self.t = data[0]
self.v = data[1]
def __getitem__(self, key):
if key == 0:
return self.t
elif key == 1:
return self.v
else:
raise ValueError("not valid key '{}'".format(key))
def __setitem__(self, key, value):
if key == 0:
self.t = value
elif key == 1:
self.v = value
else:
raise ValueError("not valid key '{}'".format(key))
The reason for the __getitem__ and __setitem__ overloading is for backwards compability so that data[0] still works. This works for most things, however I run into problems with the following call:
func_that_takes_two_arguments(*data) # unpacking data
The error I get is
/Users/pingul/Workspace/lhcfill/oml.py in __getitem__(self, key)
52 return self.val
53 else:
---> 54 raise ValueError("not valid key '{}'".format(key))
55
56 def __setitem__(self, key, value):
ValueError: not valid key '2'
How can I make my class work properly with the argument unpacking operator?
The * operator works by iterating over the object. This iteration can well be performed with only implementing __getitem__(), but your implementation is faulty. Instead if raising ValueError, you should throw IndexError which signals the end of the iteration.
See also https://docs.python.org/3/reference/datamodel.html#object.getitem which explicitly states
Note: for loops expect that an IndexError will be raised for illegal indexes to allow proper detection of the end of the sequence.
https://docs.python.org/2/library/functions.html#iter states that this is called the "sequence protocol".
I really enjoy using the Option and Either monads in Scala. Are there any equivalent for these things in Python? If there aren't, then what is the pythonic way of handling errors or "absence of value" without throwing exceptions?
The pythonic way for a function to say "I am not defined at this point" is to raise an exception.
>>> int("blarg")
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'blarg'
>>> dict(foo=5)['bar']
Traceback (most recent call last):
...
KeyError: 'bar'
>>> 1 / 0
Traceback (most recent call last):
...
ZeroDivisionError: integer division or modulo by zero
This is, in part, because there's no (generally useful) static type checker for python. A Python function cannot syntactically state, at compile time, that it has a particular codomain; there's no way to force callers to match all of the cases in the function's return type.
If you prefer, you can write (unpythonically) a Maybe wrapper:
class Maybe(object):
def get_or_else(self, default):
return self.value if isinstance(self, Just) else default
class Just(Maybe):
def __init__(self, value):
self.value = value
class Nothing(Maybe):
pass
But I would not do this, unless you're trying to port something from Scala to Python without changing much.
You can play with typing package (Python 3.6.9). Using following makes type checker happy
from typing import Optional, Union
def parse_int(s: str) -> Optional[int]:
try:
return int(s)
except:
return None
print('-- optional --')
print(parse_int('123'))
print(parse_int('a'))
def parse_int2(s: str) -> Union[str, int]:
try:
return int(s)
except Exception as e:
return f'Error during parsing "{s}": {e}'
print('-- either --')
print(parse_int2('123'))
print(parse_int2('a'))
Result
-- optional --
123
None
-- either --
123
Error during parsing "a": invalid literal for int() with base 10: 'a'
If you want to add monadic behaviour to Either you can try this
from typing import TypeVar, Generic, Callable
A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')
Either = NewType('Either', Union['Left[A]', 'Right[C]'])
class Left(Generic[A]):
def __init__(self, value: A):
self.__value = value
def get(self) -> A:
raise Exception('it is left')
def get_left(self) -> A:
return self.__value
def flat_map(self, f: Callable[[B], Either]) -> Either:
return self
def map(self, f: Callable[[B], C]) -> Either:
return self
def __str__(self):
return f'Left({self.__value})'
and right type
class Right(Generic[B]):
def __init__(self, value: B):
self.__value = value
def flat_map(self, f: Callable[[B], Either]) -> Either:
return f(self.__value)
def map(self, f: Callable[[B], C]) -> Either:
return Right(f(self.__value))
def __str__(self):
return f'Right({self.__value})'
def parse_int(s: str) -> Union[Left[str], Right[int]]:
try:
return Right(int(s))
except Exception as e:
return Left(f'Error during parsing {s}: {e}')
def divide(x: int) -> Union[Left[str], Right[int]]:
return Right(4 / x) if x != 0 else Left('zero !!!')
print(parse_int('1').map(lambda x: x * 2))
print(parse_int('a').map(lambda x: x * 2))
print(parse_int('2').flat_map(divide))
print(parse_int('0').flat_map(divide))
Result
Right(2)
Left(Error during parsing a: invalid literal for int() with base 10: 'a')
Right(2.0)
Left(zero !!!)
mypy adds type definitions and type checking (not at runtime) over regular Python. They have an Optional: https://docs.python.org/3/library/typing.html#typing.Optional. More here https://www.python.org/dev/peps/pep-0484/#rationale-and-goals. Intellij has plugin support which makes it all very professional and smooth.
In python, for an absence of value, the variable is None, so you can do it this way.
vars = None
vars = myfunction()
if vars is None:
print 'No value!'
else:
print 'Value!'
or even just check if a value is present like this
if vars is not None:
print vars
I realize this is pretty late to the party but I came to this page on top of google before deciding to implement it so maybe I can help others googling with this. I implemented it, you can get it from pypi as pyther-maybe, it implements both Either and Maybe with Maybe as a special subclass of Either. This example should explain how it works:
import sys
from pyther_maybe import *
def save_div ( x, y ):
if y == 0:
return nothing() # alias of Maybe()
else:
return value(x / y) # alias of Maybe(x / y)
float_test = save_div(1.0, 3.0)
assert isinstance(float_test, Maybe)
if float_test: #nothing tests as false:
float = float_test() # calling the container with no arguments returns its value
else:
sys.exit("something went wrong")
print float
# or if you want to encode a reason:
def save_div ( x, y ):
if y == 0:
return left("You can't divide by zero, silly") # alias of Either(left=...)
else:
return right(x / y) # alis of Either(...)
float_test = save_div(4.2, 0.0)
assert isinstance(float_test, Either)
def fake_exit ( string ):
print "We would have exited with:"
print string
return "Whatever value"
if float_test:
# these two are te same
float = float_test()
float = float_test.right()
else:
fake_exit(float_test.left())
# or in a shorter and more pleasant format
# does the same as above
float = float_test.extract(fake_exit)
print float # prints "Whatever value"
# Also, these containers are mutable:
box = nothing()
try:
print box() # raises exception
except RightEitherException:
print "We caught an exception"
def change_box():
box(4)
change_box()
print box() # 4
It has more features than that, some of which are pretty useless in practise (it's also an iterator for instance and has subscript notation like pyther_maybe.either(x)[pyther_maybe.Right] == x.
Try This:
from monad import Monad
class Either(Monad):
# pure :: a -> Either a
#staticmethod
def pure(value):
return Right(value)
# flat_map :: # Either a -> (a -> Either b) -> Either b
def flat_map(self, f):
if self.is_left:
return self
else:
return f(self.value)
class Left(Either):
def __init__(self, value):
self.value = value
self.is_left = True
class Right(Either):
def __init__(self, value):
self.value = value
self.is_left = False
A list that happens to always be of length zero or one fulfills some of the same goals as optional/maybe types. You won't get the benefits of static typing in Python, but you'll probably get a run-time error even on the happy path if you write code that tries to use the "maybe" without explicitly "unwrapping" it.
Natively, Python has a Literal Type Optional but it's not the same. Alternatively this is a representation of the Either data type for python 3.
https://gist.github.com/MatteoGuadrini/98e79a9ab2bd6ae5badc41df89cfe338
pip install either
The library was created in 2012, its Development status is: Stable, according to https://pypi.org/project/either/.
There is a package called Pymonad that implements option types as maybe and an either type as well as other functional ideas such as a wrapper for currying, monads, monoids, and others.
General Details
https://pypi.org/project/PyMonad/
Reference
https://jasondelaat.github.io/pymonad_docs/reference/pymonad.html
You can write your own Optional implement
from __future__ import annotations
from typing import *
T = TypeVar("T")
U = TypeVar("U")
class Option(Generic[T]):
_value: Optional[T]
#classmethod
def ofNullable(cls, value: Optional[T]) -> Option[T]:
option = cls()
option._value = value
return option
#classmethod
def of(cls, value: Optional[T]) -> Option[T]:
assert value is not None
return cls.ofNullable(value)
#classmethod
def empty(cls) -> Option[T]:
return cls.ofNullable(None)
def isPresent(self) -> bool:
return self._value is not None
def ifPresent(self, consumer: Callable[[T], Any]) -> None:
if self._value is not None:
consumer(self._value)
def get(self) -> T:
return self._value
def orElse(self, other: T) -> T:
return self._value if self._value is not None else other
def orElseGet(self, other: Callable[[], T]) -> T:
return self._value if self._value is not None else other()
def orElseThrow(self, exceptionSupplier: Callable[[], BaseException]):
if self._value is not None:
return self._value
else:
raise exceptionSupplier()
def filter(self, predicate: Callable[[T], bool]) -> Option[T]:
if predicate(self):
return self
return self.__class__.empty()
def map(self, mapper: Callable[[T], U]) -> Option[U]:
if self._value is not None:
return self.__class__.of(mapper(self._value))
return self.__class__.empty()
def flatMap(self, mapper: Callable[[T], Option[U]]) -> Option[U]:
if self._value is not None:
return mapper(self._value)
return self.__class__.empty()
def __str__(self) -> str:
return f"<Option:{self._value}>"
def __repr__(self) -> str:
return f"<Option:{repr(self._value)}>"
def __eq__(self, other: Union[T, Any]):
if isinstance(other, Option):
return self._value == other._value
return False
Or use a 3rd Optional library
IceSpringRealOptional
Real Optional type in python, not #Nullable annotation.
Official sites
Home: https://baijifeilong.github.io/2022/01/09/ice-spring-real-optional/index.html
Github: https://github.com/baijifeilong/IceSpringRealOptional
PyPI: https://pypi.org/project/IceSpringRealOptional
Features
All Java 8 style Optional API support
All Generic Type annotation support
Install
PyPI: pip install IceSpringRealOptional
Usage
from IceSpringRealOptional import Option
option = Option.ofNullable("CommonSense")
print("{}: isPresent={}".format(option, option.isPresent()))
print("{}: value={}".format(option, option.get()))
option.ifPresent(lambda x: print(f"{x} exist"))
print("{}'s length: {}".format(option, option.map(len)))
empty = Option.empty()
print(empty.orElse("{} is empty".format(empty)))
print(empty.orElseGet(lambda: "{} is empty again".format(empty)))
try:
Option.empty().orElseThrow(lambda: RuntimeError("Unlucky"))
except RuntimeError as e:
print("Runtime error caught: {}".format(e))
Example Output
<Option:CommonSense>: isPresent=True
<Option:CommonSense>: value=CommonSense
CommonSense exist
<Option:CommonSense>'s length: <Option:11>
<Option:None> is empty
<Option:None> is empty again
Runtime error caught: Unlucky