I have an unorthodox Enum that I plan to use in my code, but I've come to a problem where I need my property needed to throw an error when the Enum is used incorrectly, however, instead of throwing the Exception, it instead outputted my property's address.
How I want my code to work is:
When user writes Enum.MEMBER.text, return Enum.MEMBER alt text.
When user writes Enum.text, throw an error.
Here's the code snippet
class MyEnum(Enum):
#property
def text(self):
if isinstance(self._value_,MyCapsule): return self._value_.text
raise Exception('You are not using an Enum!')
return None
#property
def value(self):
if isinstance(self._value_,MyCapsule): return self._value_.value
raise Exception('You are not using an Enum!')
return None
class MyCapsule:
def __init__(self,value,text,more_data):
self._value_, self._text_ = (value,text)
#property
def text(self): return self._text_
#property
def value(self): return self._value_
class CustomData(MyEnum):
ONE = MyCapsule(1,'One','Lorem')
TWO = MyCapsule(2,'Two','Ipsum')
TRI = MyCapsule(3,'Tri','Loipsum')
A = CustomData.ONE
B = CustomData
print(A.text, A.value,sep=' | ')
print(B.text, B.value,sep=' | ')
The output is:
One | 1
<property object at 0x0000016CA56DF0E8> | <property object at 0x0000016CA56DF278>
What I expect was
One | 1
Unexpected exception at ....
Is there a solution to this problem, or I shouldn't write my Enum this way to begin with?
A custom descriptor will do the trick:
class property_only(object):
#
def __init__(self, func):
self.func = func
#
def __get__(self, instance, cls):
if instance is None:
raise Exception('You are not using an Enum!')
else:
return self.func(instance)
#
def __set__(self, instance, value):
# raise error or set value here
pass
Then change your base Enum to use it:
class MyEnum(Enum):
#property_only
def text(self):
return self._value_.text
#property_only
def value(self):
return self._value_.value
class MyCapsule:
def __init__(self, value, text, more_data):
self._value_, self._text_ = (value, text)
class CustomData(MyEnum):
ONE = MyCapsule(1, 'One', 'Lorem')
TWO = MyCapsule(2, 'Two', 'Ipsum')
TRI = MyCapsule(3, 'Tri', 'Loipsum')
and in use:
>>> CustomData.text
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in __get__
Exception: You are not using an Enum!
While that solves the "access-only-from-enum" problem, you still have a lot of indirection when you want to access text and value:
>>> CustomData.ONE.value._value_
1
>>> CustomData.ONE.value._text_
'One'
The solution is to incorporate MyCapsule directly into CustomData:
from enum import Enum
class property_only(object):
#
def __init__(self, func):
self.func = func
#
def __get__(self, instance, cls):
if instance is None:
raise Exception('You are not using an Enum!')
else:
return self.func(instance)
#
def __set__(self, instance, value):
# raise error or set value here
pass
class CustomData(Enum):
#
ONE = 1, 'One', 'Lorem'
TWO = 2, 'Two', 'Ipsum'
TRI = 3, 'Tri', 'Loipsum'
#
def __new__(cls, value, text, more_data):
member = object.__new__(cls)
member._value_ = value
member._text_ = text
# ignoring more_data for now...
return member
#
#property_only
def text(self):
return self._text_
#
#property_only
def value(self):
return self._value_
and in use:
>>> CustomData.ONE
<CustomData.ONE: 1>
>>> CustomData.ONE.value
1
>>> CustomData.ONE.text
'One'
>>> CustomData.ONE.name
'ONE'
>>> CustomData.text
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in __get__
Exception: You are not using an Enum!
>>> CustomData.value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in __get__
Exception: You are not using an Enum!
Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.
Related
Let's say I have this class:
class Person:
def __init__(self, name):
self.name = name
If I want to instantiate Person I can do:
me = Person("António")
But what if I only want to instantiate Person if name has type str?
I tried this:
class Person:
def __init__(self, name):
if type(name) == str:
self.name = name
But then when I do:
me = Person("António")
print(me.name)
you = Person(1)
print(you.name)
I get this:
So all that's happening is:
If name is str, the instance has a .name method
If name is not str, the instance has no .name method
But what I actually want, is to stop instantiation all together if name is not an str.
In other words, I want it to be impossible to create an object from the Person class with a non str name.
How can I do that?
You could use a factory that checks the parameters, and returns a Person object if everything is fine, or raises an error:
maybe something line this:
class PersonNameError(Exception):
pass
class Person:
def __init__(self):
self.name = None
def person_from_name(name: str) -> Person:
"""Person factory that checks if the parameter name is valid
returns a Person object if it is, or raises an error without
creating an instance of Person if not.
"""
if isinstance(name, str):
p = Person()
p.name = name
return p
raise PersonNameError('a name must be a string')
p = person_from_name('Antonio')
Whereas:
p = person_from_name(123) # <-- parameter name is not a string
throws an exception:
PersonNameError Traceback (most recent call last)
<ipython-input-41-a23e22774881> in <module>
14
15 p = person_from_name('Antonio')
---> 16 p = person_from_name(123)
<ipython-input-41-a23e22774881> in person_from_name(name)
11 p.name = name
12 return p
---> 13 raise PersonNameError('a name must be a string')
14
15 p = person_from_name('Antonio')
PersonNameError: a name must be a string
How about :
class Person:
def __init__(self, name):
if type(name) == str:
self.name = name
else:
raise Exception("name attribute should be a string")
You should use factory design pattern. You can read more about it here. To put it simple:
Create Class/method that will check for the conditions and return new class instance only if those conditions are met.
If you want to modify instantiation behaviour,
You can create a constructor,
using a class method.
class Person:
def __init__(self, name):
self.name = name
print("ok")
#classmethod
def create(cls, name):
if not isinstance(name, str):
raise ValueError(f"Expected name to be a string, got {type(name)}")
return cls(name)
me = Person.create("António")
print(me.name)
you = Person.create(1)
print(you.name)
OK prints once proving only once instantiation
ok
António
Traceback (most recent call last):
File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 31, in <module>
start(fakepyfile,mainpyfile) File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 30, in start
exec(open(mainpyfile).read(), __main__.__dict__)
File "<string>", line 17, in <module>
File "<string>", line 11, in create
ValueError: Expected name to be a string, got <class 'int'>
[Program finished]
Here, it's an explicit test that's being done.
Overriding new is very rarely needed and for everyday normal classes I think it should be avoided. Doing so keeps the class implementation simple.
class Test(object):
print("ok")
def __new__(cls, x):
if isinstance(x, str) :
print(x)
else:
raise ValueError(f"Expected name to be a string, got {type(x)}")
obj1 = Test("António")
obj2 = Test(1)
ok
António
Traceback (most recent call last):
File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 31, in <module>
start(fakepyfile,mainpyfile) File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 30, in start
exec(open(mainpyfile).read(), __main__.__dict__)
File "<string>", line 14, in <module>
File "<string>", line 10, in __new__
ValueError: Expected name to be a string, got <class 'int'>
[Program finished]
I'm kinda stuck at this code, it's supposed to create a string-like object, which, upon using, will return internal lambda's value. I use collections.UserString.
from collections import UserString
class instrx(UserString):
func = None
data = ''
def __init__(self,func):
self.func = func
def __getattribute__(self,name):
print("get",name)
if name=='data':
self.data = self.func()
return UserString.__getattribute__(self,name)
a = instrx(lambda: 'aa')
print(a,type(a))
print(a.lower())
Running this code gives me this:
Traceback (most recent call last):
File "C:\path\a.py", line 14, in <module>
print(a.lower())
File "C:\Python35\lib\collections\__init__.py", line 1082, in __str__
def __str__(self): return str(self.data)
File "C:\path\a.py", line 10, in __getattribute__
self.data = self.func()
TypeError: 'str' object is not callable
Python version: 3.4, 3.5.
Okay, I figured this out.
It seems that in some functions in UserString (like .lower() in example) self.__class__(self.data) construction is used. I did a little workaround:
import time
from collections import UserString
class instrx(UserString):
func = None
data = ''
def __init__(self,data,func=None):
self.data = data
if func==None:
self.func = lambda: UserString.__getattribute__(self,'data')
else:
self.func = func
def __getattribute__(self,name):
if name=='data':
self.data = str(self.func())
return UserString.__getattribute__(self,name)
a = instrx('',func=lambda: time.time())
print(a,type(a))
print(a.lower())
print(a+"test")
Works just fine.
Is it possible to assign a numeric value to a variable in such a way that it is limited to a certain range? More specifically I want a variable that can never go below zero, because if that was about to happen an exception would be raised.
Imaginary example:
>>> var = AlwaysPositive(0)
>>> print var
0
>>> var += 3
>>> print var
3
>>> var -= 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AlwaysPositiveError: dropping AlwaysPositive integer below zero
The reason I ask is because I am debugging a game I am writing. Where humans understand implicitly you can never have -1 cards in your hand, a computer does not. I can make functions that check all values used in the game and call those functions at multiple positions throughout the script and see if any weird values appear. But I was wondering if there perhaps was an easier way to do this?
Sub-classing int is probably the best way to do this if you really need to, but the implementations shown so far are naive. I would do:
class NegativeValueError(ValueError):
pass
class PositiveInteger(int):
def __new__(cls, value, base=10):
if isinstance(value, basestring):
inst = int.__new__(cls, value, base)
else:
inst = int.__new__(cls, value)
if inst < 0:
raise NegativeValueError()
return inst
def __repr__(self):
return "PositiveInteger({})".format(int.__repr__(self))
def __add__(self, other):
return PositiveInteger(int.__add__(self, other))
# ... implement other numeric type methods (__sub__, __mul__, etc.)
This allows you to construct a PositiveInteger just like a regular int:
>>> PositiveInteger("FFF", 16)
PositiveInteger(4095)
>>> PositiveInteger(5)
PositiveInteger(5)
>>> PositiveInteger(-5)
Traceback (most recent call last):
File "<pyshell#24>", line 1, in <module>
PositiveInteger(-5)
File "<pyshell#17>", line 8, in __new__
raise NegativeValueError()
NegativeValueError
See e.g. the datamodel docs on numeric type emulation for details of the methods you will need to implement. Note that you don't need to explicitly check for negative numbers in most of those methods, as when you return PositiveInteger(...) the __new__ will do it for you. In use:
>>> i = PositiveInteger(5)
>>> i + 3
PositiveInteger(8)
Alternatively, if these non-negative integers will be attributes of a class, you could enforce positive values using the descriptor protocol, e.g.:
class PositiveIntegerAttribute(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, typ=None):
return getattr(obj, self.name)
def __set__(self, obj, val):
if not isinstance(val, (int, long)):
raise TypeError()
if val < 0:
raise NegativeValueError()
setattr(obj, self.name, val)
def __delete__(self, obj):
delattr(obj, self.name)
You can then use this as follows:
>>> class Test(object):
foo = PositiveIntegerAttribute('_foo')
>>> t = Test()
>>> t.foo = 1
>>> t.foo = -1
Traceback (most recent call last):
File "<pyshell#34>", line 1, in <module>
t.foo = -1
File "<pyshell#28>", line 13, in __set__
raise NegativeValueError()
NegativeValueError
>>> t.foo += 3
>>> t.foo
4
>>> t.foo -= 5
Traceback (most recent call last):
File "<pyshell#37>", line 1, in <module>
t.foo -= 5
File "<pyshell#28>", line 13, in __set__
raise NegativeValueError()
NegativeValueError
You can subclass your own data type from int and provide it with a bunch of magic methods overloading the operators you need.
class Alwayspositive(int):
def __init__(self, *args, **kwargs):
super(Alwayspositive, self).__init__(*args, **kwargs)
def __neg__(self):
raise AlwayspositiveError()
def __sub__(self, other):
result = super(Alwayspositive, self).__sub__(other)
if result < 0:
raise AlwayspositiveError()
return result
And so on. This is quite a lot of work and debug to make such a class safe, but it will allow you to debug your code with a very little changes between debug and release mode.
While learning the concepts of decorators in python I came to the question if it is possible to use decorators to simulate a state machine.
Example:
from enum import Enum
class CoffeeMachine(object):
def __init__(self):
self.state = CoffeeState.Initial
##Statemachine(shouldbe, willbe)
#Statemachine(CoffeeState.Initial, CoffeeState.Grounding)
def ground_beans(self):
print("ground_beans")
#Statemachine(CoffeeState.Grounding, CoffeeState.Heating)
def heat_water(self):
print("heat_water")
#Statemachine(CoffeeState.Heating, CoffeeState.Pumping)
def pump_water(self):
print("pump_water")
class CoffeeState(Enum):
Initial = 0
Grounding = 1
Heating = 2
Pumping = 3
So all the statemachine does is to check if my current state is the requested one, if it is, it should call the underlying function and lastly it should set the state further.
How would you implement this?
Sure you can, provided your decorator makes an assumption about where the state is stored:
from functools import wraps
class StateMachineWrongState(Exception):
def __init__(self, shouldbe, current):
self.shouldbe = shouldbe
self.current = current
super().__init__((shouldbe, current))
def statemachine(shouldbe, willbe):
def decorator(f):
#wraps(f)
def wrapper(self, *args, **kw):
if self.state != shouldbe:
raise StateMachineWrongState(shouldbe, self.state)
try:
return f(self, *args, **kw)
finally:
self.state = willbe
return wrapper
return decorator
The decorator expects to get self passed in; i.e. it should be applied to methods in a class. It then expects self to have a state attribute to track the state machine state.
Demo:
>>> cm = CoffeeMachine()
>>> cm.state
<CoffeeState.Initial: 0>
>>> cm.ground_beans()
ground_beans
>>> cm.state
<CoffeeState.Grounding: 1>
>>> cm.ground_beans()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in wrapper
__main__.StateMachineWrongState: (<CoffeeState.Initial: 0>, <CoffeeState.Grounding: 1>)
>>> cm.heat_water()
heat_water
>>> cm.pump_water()
pump_water
>>> cm.state
<CoffeeState.Pumping: 3>
I have a class A with method do_something(self,a,b,c) and another instance method that validates the input and check permissions named can_do_something(self,a,b,c).
This is a common pattern in my code and I want to write a decorator that accepts a validation function name and perform the test.
def validate_input(validation_fn_name):
def validation_decorator(func):
def validate_input_action(self,*args):
error = getattr(self,validation_fn_name)(*args)
if not error == True:
raise error
else:
return func(*args)
return validate_input_action
return validation_decorator
Invoking the functions as follows
#validate_input('can_do_something')
def do_something(self,a,b,c):
return a + b + c
Problem is that i'm not sure how to maintain self through out the validation function. I've used the validation fn name with getattr so the fn could be ran in the context of the instance but i cannot do that for func(*args).
So what is the proper way to achieve this ?
Thanks.
EDIT
So following #André Laszlo answer I realized that self is just the first argument so there is no need to use getattr at all but just pass on the *args.
def validate_input(validation_fn):
def validation_decorator(func):
def validate_input_action(*args):
error = validation_fn(*args)
if not error == True:
raise error
else:
return func(*args)
return validate_input_action
return validation_decorator
Much more elegant and it also supports static methods as well.
Adding a static method to #André Laszlo example proves the decorator is working :
class Foo(object):
#staticmethod
def validate_baz(a,b,c):
if a > b:
return ValueError('a gt b')
#staticmethod
#validate_input(Foo.validate_baz)
def baz(a,b,c):
print a,b,c
>>> Foo.baz(1,2,3)
1 2 3
>>> Foo.baz(2,1,3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in validate_input_action
ValueError: a gt b
But, when i'm trying to do them same thing in a django model:
from django.db import models
from django.conf import settings
settings.configure()
class Dummy(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
def can_say_name(self):
if name is None:
return Exception('Does not have a name')
#validate_input(can_say_name)
def say_name(self):
print self.name
#staticmethod
def can_create_dummy(name):
if name == 'noname':
return Exception('No name is not a name !')
#staticmethod
#validate_input(Dummy.can_create_dummy)
def create_dummy(name):
return Dummy.objects.create(name=name)
I get the following :
NameError: name 'Dummy' is not defined
So what is the different between a django model and an Object in relation to this issue ?
I think this does what you want:
def validate_input(validation_fn_name):
def validation_decorator(func):
def validate_input_action(self, *args):
error = getattr(self, validation_fn_name)(*args)
if error is not None:
raise error
else:
arglist = [self] + list(args)
return func(*arglist)
return validate_input_action
return validation_decorator
class Foo(object):
def validate_length(self, arg1):
if len(arg1) < 3:
return ValueError('%r is too short' % arg1)
#validate_input('validate_length')
def bar(self, arg1):
print "Arg1 is %r" % arg1
if __name__ == "__main__":
f = Foo()
f.bar('hello')
f.bar('')
Output is:
Arg1 is 'hello'
Traceback (most recent call last):
File "validator.py", line 27, in <module>
f.bar('')
File "validator.py", line 6, in validate_input_action
raise error
ValueError: '' is too short
Updated answer
The error (NameError: name 'Dummy' is not defined) occurs because the Dummy class is not defined yet when the validate_input decorator gets Dummy as an argument. I guess this could have been implemented differently, but for now that's the way Python works. The easiest solution that I see is to stick to using getattr, which will work because it looks up the method at run time.