Is this really encapsulated? - python-3.x

I wondered wheather it is actually impossible to make a protected python class, there always seems to be a way of getting around that, but i can't find one for this:
I attempted to code out this properly encapsulated class. Challenge:
Attempt setting somevalue to the value 69, without:
changing the code from line 1 - 32
polymorphism
from sbNative.debugtools import log # module not neccessary, only for logging and debugging purposes imported
from inspect import stack
import traceback
class Protected:
def __init__(self):
self._somevalue = "Unset"
log(self._somevalue)
def __setattr__(self, name, value):
if isinstance(stack()[1][0].f_locals.get("self"), Protected) or not name.startswith("_"):
super(Protected, self).__setattr__(name, value)
else:
raise AttributeError("Protected class from setting")
def __getattribute__(self, name):
if isinstance(stack()[1][0].f_locals.get("self"), Protected) or not name.startswith("_"):
return super(Protected, self).__getattribute__(name)
else:
raise AttributeError("Protected class from getting")
#property
def somevalue(self):
return self._somevalue
#somevalue.setter
def somevalue(self, value):
if value == 69:
raise ValueError(f"{value} is an illegal value.")
self._somevalue = value
log("Instantiates without a problem:")
p = Protected()
print("\n")
log("Fails because it is not allowed to set to this value:")
try:
p.somevalue = 69
except ValueError:
traceback.print_exc()
print("\n")
log("Fails because it attemps setting and getting directly:")
try:
p._somevalue = 69
except AttributeError:
traceback.print_exc()
print("")
try:
log(p._somevalue)
except AttributeError:
traceback.print_exc()
print("\n")
log("Succeeds because it is allowed to set and get this value:")
p.somevalue = 420
log(p.somevalue)
print("ⁿᶦᶜᵉ ˡᶦⁿᵉ ⁿᵘᵐᵇᵉʳ ᵇᵗʷ")

Related

How could I create a docstring decorator in the presence of properties?

I have a collection of ever more specialized classes which correspond to collections of the same kind of data (temperature, density, etc) but for different drifts, for example, one subclass has dimensions (nx, ny) and a different suclass has dimensions (ncv), and I want to reflect that in the docstrings, for having a better documentation using Sphinx.
After reading many very useful threads here in Stack Overflow, I have arrived to this model:
import numpy as np
from functools import wraps
def class_decorator(cls):
import ipdb; ipdb.set_trace()
clsdict = {}
mro = cls.mro()
mro.reverse()
for tmp in mro[1:]: ##Ignore object class parent.
clsdict.update(tmp.__dict__)
for name, method in clsdict.items():
if hasattr(method, '__og_doc__'):
try:
method.__doc__ = method.__og_doc__.format(**clsdict)
except:
pass
else:
try:
method.__og_doc__ = method.__doc__
method.__doc__ = method.__doc__.format(**clsdict)
except:
pass
return cls
def mark_documentation(fn):
if not hasattr(fn, '__og_doc__'):
try:
fn.__og_doc__ = fn.__doc__
except:
pass
#wraps(fn)
def wrapped(*args, **kwargs):
return fn(*args, **kwargs)
return wrapped
def documented_property(fn):
if not hasattr(fn, '__og_doc__'):
try:
fn.__og_doc__ = fn.__doc__
except:
pass
#wraps(fn)
def wrapped(*args, **kwargs):
return fn(*args, **kwargs)
prp= property(wrapped)
prp.__og_doc__ = fn.__og_doc__
return prp
#class_decorator
class Base(object):
_GRID_DIM = 'nx, ny'
_TYPE = 'BaseData'
def __init__(self, name):
self.name = name
def shape(self):
""" This docstring contains the type '{_TYPE}' of class."""
print('Simple')
def operation(self, a, b, oper=np.sum, **kwargs):
""" Test for functions with args and kwargs in {_TYPE}"""
return oper([a,b])
#classmethod
def help(cls, var):
try:
print(get(cls, var).__doc__)
except:
print("No docstring yet.")
#class_decorator
class Advanced(Base):
_GRID_DIM = 'ncv'
_TYPE = 'AdvancedData'
def __init__(self,name):
super().__init__(name)
#property
#mark_documentation
# #documented_property
def arkansas(self):
"""({_GRID_DIM}, ns): Size of Arkansaw."""
return 'Yeah'
I am aiming to get the correctly formatted docstring when I call the help method or I use Sphinx, so that:
> adv = Advanced('ADV')
> adv.help("arkansas")
(ncv, ns): Size of Arkansaw.
> adv.help("operation")
Test for functions with args and kwargs in AdvancedData
I have managed to make it work so far, except for properties, because I assigned __og_doc__ to the function, but the property does not have that attribute. My last attempt at monkeypatching this, documented_property, fails because property is inmutable (as expected), and I cannot come up with any way to avoid this roadblock.
Is there any way around this problem?

How to raise an error with decorator for unit testing?

I would like to do something in my unit testing before it fails and using decorator to do so
Here is my code :
import requests
import unittest
import test
class ExceptionHandler(object):
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
try:
self.f(*args, **kwargs)
except Exception as err:
print('do smth')
raise err
class Testing(unittest.TestCase):
#ExceptionHandler
def test_connection_200(self):
r = requests.get("http://www.google.com")
self.assertEqual(r.status_code, 400)
if __name__ == '__main__':
unittest.main(verbosity=2)
But it throws me an :
TypeError: test_connection_200() missing 1 required positional argument: 'self'
How can I do something when my test fail, then having the normal failing behavior of unitest?
Edit :
I would like to do something before my test fail like write a log and then continue the normal process of failing.
If possible with a decorator.
Edit bis the solution thanks to #Thymen :
import requests
import unittest
import test
class ExceptionHandler(object):
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
try:
self.f(*args, **kwargs)
except Exception as err:
print('do smth')
raise err
class Testing(unittest.TestCase):
def test_connection_200(self):
#ExceptionHandler
def test_connection_bis(self):
r = requests.get("https://www.google.com")
print(r.status_code)
self.assertEqual(r.status_code, 400)
test_connection_bis(self)
if __name__ == '__main__':
unittest.main(verbosity=2)
My comment may not have been clear, so hereby the solution in code
class Testing(unittest.TestCase):
def test_connection_200(self):
#ExceptionHandler
def test_connection():
r = requests.get("http://www.google.com")
self.assertEqual(r.status_code, 400)
with self.assertRaises(AssertionError):
test_connection()
The reason that this works is that there is no dependency on the call for the test (test_connection_200) , and the actual functionality that you are trying to test (the ExceptionHandler).
Edit
The line
with self.assertRaises(AssertionError):
test_connection()
Checks if test_connection() raises an AssertionError. If it does not raises this error, it will fail the test.
If you want the test to fail (because of the AssertionError), you can remove the with statement, and only call test_connection(). This will make the test fail directly.

assigning textvariable to entry causes validation to be disabled on DoubleVar

I am modifying tk.Entry() to validate a numeric input and notify a user if there was an error in applying it.
Beginning with the code below:
import tkinter as tk
class FloatEntry(tk.Entry):
def __init__(self, parent, *args, **kwargs):
tk.Entry.__init__(self, parent, validate="focusout", validatecommand=self.validate, *args, **kwargs)
def validate(self):
try:
print(float(self.get()))
return True
except ValueError as e:
print(e)
return False
if __name__ == "__main__":
root = tk.Tk()
root.bind_all("<1>", lambda event:event.widget.focus_set()) # make all widgets focusable
var = tk.DoubleVar()
frame = tk.Frame()
frame.pack(fill="both", expand=True)
FloatEntry(frame, textvariable=var).pack()
tk.Label(frame, textvariable=var).pack()
root.mainloop()
This results in the exception being triggered and printing, "could not convert string to float:". After this, neither the try nor the except in validate() are triggered again, so I assume it somehow returned None and disabled validation (I could be wrong here).
If I change the variable var = DoubleVar() to var =tk.StringVar(), then the validation works as expected, printing the float if the string can be parsed as a float, and printing the exception otherwise.
Finally, if I add a check to the value returned by get() before the try/except block, validation works as expected as well.
def validate(self):
try:
val = self.get()
if(val is not ''):
print(float(self.get()))
return True
except ValueError as e:
print(e)
return False
What is causing validation to be disabled on creation of a FloatEntry object?(or if that isn't happening, what is?)
I'll be honest and say I don't fully understand why this is happening. However, a simple workaround seems to be to configure the validatecommand after you've called the __init__ of the superclass:
class FloatEntry(tk.Entry):
def __init__(self, parent, *args, **kwargs):
tk.Entry.__init__(self, parent, validate="focusout", *args, **kwargs)
self.configure(validatecommand=self.validate)
When I make the above changes, I see the validate being called every time the widget loses focus.

The trouble with a calculation of the set len

I can't resolve this problem:/ My code returned a bound method, not a len of the set. Help me with your advise, please!)
class PowerSet():
def __init__(self):
self.powerset = set()
def size(self):
return len(self.powerset)
def put(self, value):
if value in self.powerset:
raise KeyError
else:
return self.powerset.add(value)
a = PowerSet()
for i in range(10):
a.put(i)
print(a.size)
# <bound method PowerSet.size of <__main__.PowerSet object at 0x7f7291042940>>
but
print(len(a.powerset))
# 10
I think you just have to write len(a.size()) with brackets. Now you are asking to print the method, you are not callng it.

Exception handling with method chaining

class A:
def __init__(self, data):
self.data = data
def first():
# Some functionality
# raise Exception on some condition
return self
def second():
#Some functionality
a = A('test string').first().second()
In this case I want that if first() raises an error then the chaining breaks and that error is raised. Currently it just fails silently.
How can I fix this?
Probably the best you can do is localize the type of errors that are possible in the chain and then ladder the exception handlers:
class A:
def __init__(self, data):
self.data = data
def first(self):
# Some functionality
# raise Exception on some condition
if self.data=='A':
1/0
return self
def second(self):
next(0)
pass
#Some functionality
You would know that first is numerical, and second is an iterable function that might have a TypeError.
So try this:
try:
A('A').first().second()
except ZeroDivisionError as e:
print('Likely first:', e)
except TypeError as e:
print('Likely second:', e)
Would print Likely first: division by zero with A('A') and print Likely second: 'int' object is not an iterator if you use A('something_else'). You would use this construct if you wanted to stop the chain as executed.
You can also add raise to re-raise the error and get Python's diagnosis of when are where:
try:
A('B').first().second()
except ZeroDivisionError as e:
print('Likely first:', e)
raise
except TypeError as e:
print('Likely second:', e)
raise
Prints:
Likely second: 'int' object is not an iterator
Traceback (most recent call last):
File "Untitled 79.py", line 19, in <module>
A('B').first().second()
File "Untitled 79.py", line 14, in second
next(0)
TypeError: 'int' object is not an iterator
Or, better still, use the try and except in each method:
class A:
def __init__(self, data):
self.data = data
def first(self):
try:
1/0
except ZeroDivisionError as e:
print('DEFINITELY first:', e)
return self
def second(self):
try:
next(0)
except TypeError as e:
print('DEFINITELY second:', e)
return self
>>> A('').first().second()
DEFINITELY first: division by zero
DEFINITELY second: 'int' object is not an iterator
I don't think it could fail silently if its actually failing, as this would error out python. However, you might want something like this, maybe it will jog your thinking.
class A:
def __init__(self, data):
self.data = data
def first():
try:
# Some functionality
raise ERROR_YOU WANT_RAISE:
# raise Exception on some condition
except ERROR_TO_CATCH_FROM_RAISE:
# This is where you would handle the exception after its caught
# and break your chaining possibly. Set a flag for second()
# for example that handles the error.
return self
def second():
#Some functionality

Resources