Skip to content

Instantly share code, notes, and snippets.

@tiagopog
Last active February 28, 2021 12:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tiagopog/9114513e3a1ce8e3d8dda76fa1ee492d to your computer and use it in GitHub Desktop.
Save tiagopog/9114513e3a1ce8e3d8dda76fa1ee492d to your computer and use it in GitHub Desktop.
Python - Techniques & Concepts
# An usual class is a subclass of object
class Human(object):
# Defining class variables
species = "H. Sapiens"
def __init__(self, first_name, last_name):
# Assign instance variables (also called attributes):
self.__internal = "secret" # Private attribute (externally accessed via: human._Human_internal)
self.first_name = first_name # Public attribute (externally accessed via human.first_name)
self.last_name = last_name # Since last_name has not a getter/setter below
# here the "=" will be dealing directly with
# the instance variable
# Define public instance method.
# Instance method is any method that has "self" as its first argument.
def say(self, speech):
print(self.__build_say(speech))
# Define private instance method
def __build_say(self, speech):
return "{} is saying \"{}\"".format(self.full_name, speech)
# Define class methods.
# The class itself is passed as the first argument so
# class variable can be called for instance
@classmethod
def get_species(cls):
return cls.species
# Define static methods.
# This kind of method has no references for classes or instances.
@staticmethod
def something():
return "somthing"
# Define a property for an instance variable.
# It works as getter method.
@property
def first_name(self):
return self._first_name
# Define a setter method for the Human's first name.
# This way when setting its value via "=", it will be using
# this method rather than operating directly on the instance variable.
@first_name.setter
def first_name(self, name):
self._first_name = name
# Allow the first_name property to be deleted
@first_name.deleter
def first_name(self):
del self._first_name
# Define a virtual property (not backed by an instance variable).
# This way human.full_name will be called rather than human.full_name().
@property
def full_name(self):
return "{0} {1}".format(self.first_name, self.last_name)
# Define a setter for full_name so some logic can be applied
# to the instance variables first_name and last_name.
@full_name.setter
def full_name(self, full_name):
if not full_name:
self.first_name = ""
self.last_name = ""
elif isinstance(full_name, str):
names = full_name.split()
self.first_name = names[0]
self.last_name = " ".join(names[1:])
def __str__(self):
return "First name: {}; Last name: {}".format(self.first_name, self.last_name)
# Define a class (Developer) as subclass of other (Human)
class Developer(Human):
# Override the superclass method extending its behavior:
def __init__(self, first_name, last_name, tech_skills=[]):
# Human.__init__(self, first_name, last_name)
super(Developer, self).__init__(first_name, last_name)
self.tech_skills = tech_skills
# Override the superclass method extending its behavior:
def __str__(self):
human_str = super(Developer, self).__str__()
dev_str = ', '.join(self.tech_skills)
return "{}; Tech skill: {}".format(human_str, dev_str)
# Calling class methods:
print(Human.get_species())
# Calling static methods:
print(Human.something())
# Test constructor:
human = Human(first_name="Tiago", last_name="Guedes")
print(human.first_name)
print(human.last_name)
print("---")
# Test virtual setter:
human.full_name = "Foo Bar"
print(human.first_name)
print(human.last_name)
print(human.full_name)
print("---")
# Test virtual setter(2):
human.full_name = ""
print(human.first_name)
print(human.last_name)
print(human.full_name)
print("---")
human.full_name = "Tiago Guedes"
# Test public instance method:
human.say("Travel the world they said!")
# Test private instance method:
print(human._Human__build_say("Travel the world they said! (private)"))
# Test private attribute:
print(human._Human__internal)
print("---")
# Test deleting a property
print("After deleting human.first_name, let's try to call it again:")
try:
del human.first_name
human.first_name
except AttributeError as e:
print("Error raised: {}".format(e))
print("---")
print("Instatiating a developer:")
human = Human(first_name="Tiago", last_name="Guedes")
dev = Developer(first_name="Tiago", last_name="Guedes", tech_skills=["Ruby", "Elixir", "Python"])
print(dev.tech_skills)
print(dev.first_name)
print(dev.last_name)
print(dev.full_name)
print(human)
print(dev)
print("---")
# Debugger:
# import code; code.interact(local=dict(globals(), **locals()))
import abc
class OperationResult(object):
"""
Result object for structuring and standarizing the way the application
interacts with service objects results.
"""
def __init__(self, data, source):
"""
Result object constructor.
:param any data: the operation result of a service object.
against the fields defined on the `Service` class
:param Service, BaseService source: service object that
originated the result.
"""
self.data = data
self.source = source
self.errors = source.errors
self.failed_objects = source.failed_objects
self.processed = source.processed
def success(self):
"""
Returns whether the service object operation has succeed.
:return: boolean
"""
return self.processed and not self.source.errors
def fail(self):
"""
Returns whether the service object operation has failed.
:return boolean:
"""
return not self.success()
class BaseService(object):
"""
Extends and creates an abstraction for the :class:`Service` class so that
base logic for service objects can be added here without needing to directly
modify the `source` lib.
"""
"""
:cvar tuple known_exceptions: static list of kown operation expections that
can be raise while calling :meth:`process`. This class var is expected
to be overridden in subclasses where custom operation errors can be
raised.
"""
known_exceptions = ()
def __init__(self, params={}, **kwargs):
"""
Service object constructor.
:param dictionary params: data parameters for `Service`, checked
against the fields defined on the `Service` class.
:param dictionary **kwargs: any additional parameters `Service` may
need, can be an empty dictionary.
"""
self.raw_result = None
self.processed = False
self.errors = None
self.failed_objects = []
self.params = params
for key, value in kwargs.iteritems():
setattr(self, key, value)
@classmethod
def do_execute(cls, params={}, **kwargs):
"""
Runs the service's main operation in a "safe" way i.e. all known
expections will be catched.
:param dictionary params: data parameters for `Service`, checked
against the fields defined on the `Service` class.
:param dictionary **kwargs: any additional parameters `Service` may
need, can be an empty dictionary.
:return OperationResult: the operation result object.
"""
return cls(params, **kwargs).execute()
@classmethod
def do_process(cls, params={}, **kwargs):
"""
Runs the service's main operation in a "dangerous" way i.e. it can raises known exceptions
in case of operation failure.
:param dictionary params: data parameters for `Service`, checked
against the fields defined on the `Service` class.
:param dictionary **kwargs: any additional parameters `Service` may
need, can be an empty dictionary.
:return OperationResult: the operation result object.
"""
return cls(params, **kwargs).process()
def execute(self):
"""
Runs the service in a "safe" way i.e. all known expections will be catched.
"""
try:
self.raw_result = self.process()
except type(self).known_exceptions as error:
self.errors = [error.message]
self.failed_objects.append(error)
finally:
self.processed = True
return OperationResult(data=self.raw_result, source=self)
@abc.abstractmethod
def process(self):
"""
Main method to be overridden, contains the business logic.
"""
pass
##
# Example 1: simulate opening and closing resources after is usage.
##
class Operation(object):
def __init__(self):
print('Initializing...')
self.status = 'initial'
def __enter__(self):
print('Opening...')
self.status = 'open'
return self
def __exit__(self, type, value, traceback):
print('Closing...')
self.status = 'closed'
print('Status: {}'.format(self.status))
# print('Type: {}'.format(type))
# print('Traceback: {}'.format(traceback))
# return True
with Operation() as operation:
print('Running...')
print('Status: {}'.format(operation.status))
# raise NotImplementedError('foo')
##
# Example 2: imitate the TestCase.assertRaises() method.
##
class UnitTest(object):
def assertRaises(self, exception):
self.exception = exception
return self
def __enter__(self):
return self.exception
def __exit__(self, type, value, traceback):
if self.exception == type:
print('Exception mached!')
return True
else:
print('Exception not mached or not raised!')
return False
test = UnitTest()
with test.assertRaises(NotImplementedError) as error:
raise NotImplementedError
with test.assertRaises(NotImplementedError) as error:
print('Not raising any exception...')
##
# Example 3: try using the contextmanager decorator + generator
##
from contextlib import contextmanager
class UnitTestPlus(object):
@contextmanager
def assertRaises(self, exception):
try:
yield exception
print('Exception not mached or not raised!')
except exception:
print('Exception mached!')
test = UnitTestPlus()
with test.assertRaises(NotImplementedError) as error:
raise NotImplementedError
with test.assertRaises(NotImplementedError) as error:
print('Not raising any exception...')
##
# With no arguments
##
def upcase(function):
def decorate():
return function().upper()
return decorate
@upcase
def foo(): return 'foo'
foo() #=>'FOO'
def call_twice(old_function):
def new_function(arg):
for _ in range(2):
old_function(arg)
return new_function
def foo(arg):
print(arg)
# The upper def would be the equivalent of doing:
# foo = call_twice(foo)
foo('Foobar')
##
# With arguments
##
def type_check(correct_type):
def type_check_generator(old_function):
def new_function(arg):
if isinstance(arg, correct_type):
return old_function(arg)
else:
print("Bad Type")
return new_function
return type_check_generator
@type_check(int)
def double(num):
return num * 2
# The upper def would be the equivalent of doing:
# double = type_check(int)(double)
print(double(2))
double('Not A Number')
@type_check(str)
def first_letter(word):
return word[0]
# That would be the equivalent of doing:
# double = type_check(int)(double)
print(first_letter('Hello World'))
first_letter(['Not', 'A', 'String'])
import time
from datetime import datetime
def log(func):
print(f"logging: {func}")
return func
@log
def foo():
return "foo"
foo()
def double(func) -> int:
def decorated(*args):
return func(*args) * 2
return decorated
@double
def calculate(x, y):
return x + y
@double
def new_calculate(x, y, z):
return x + y + z
print(calculate(1, 2))
print(new_calculate(1, 2, 3))
def memoize(func):
memoized = {}
def decorated(*args, **kwargs):
print(locals())
if func in memoized:
result = memoized.get(func)
else:
result = func(*args, **kwargs)
memoized[func] = result
return result
return decorated
@memoize
def expensive_calculation():
time.sleep(2)
return 42
print(datetime.now())
print(expensive_calculation())
print(datetime.now())
print(expensive_calculation())
print(datetime.now())
@memoize
def another_expensive_calculation():
time.sleep(2)
return 42
print(datetime.now())
print(another_expensive_calculation())
print(datetime.now())
print(another_expensive_calculation())
print(datetime.now())
def fibonacci(size):
a, b = 1, 1
for x in range(size):
if x in (0, 1): x = 1
x = a + b
a, b = b, x
yield x
# Called in a "for" loop
for x in fibonacci(10):
print(x)
# Assigned to a variable
foo = fibonacci(10)
print(next(foo))
print(next(foo))
# Function Scope
x = 5
def set_x(num):
# Local var x not the same as global variable x
x = num # => 43
print x # => 43
def set_global_x(num):
global x
print x # => 5
x = num # global var x is now set to 6
print x # => 6
set_x(43)
set_global_x(6)
def even_numbers(n):
for number in range(1, n * 2 + 1):
if number % 2 == 0:
yield number
for x in even_numbers(10):
print(x)
# Be careful using mutable data structures in method definitions.
#
# The assignment of the "foo" argument's default value is made only the
# first time the function is actually evaluated on the runtime.
#
# Then it uses the same memory address for the default argument value,
# sharing it with all instances of that class.
class Foo(object):
def __init__(self, foo=[]):
foo.append(1)
self.foo = foo
Foo().foo #=> [1]
Foo().foo #=> [1, 1]
from unittest import TestCase
from mock import Mock, patch
from backend.common.services import BaseService, OperationResult
class KnownError(Exception):
pass
class UnknownError(Exception):
pass
def raise_known_error():
raise KnownError('You should catch me!')
def raise_unknown_error():
raise UnknownError('You will not catch me!')
class ServiceMock(BaseService):
known_exceptions = (KnownError)
def process(self):
return self.params
class OperationResultTest(TestCase):
def setUp(self):
self.service = ServiceMock({ 'foo': 'bar' })
self.data = self.service.process()
def test_constructor(self):
result = OperationResult(data=self.data, source=self.service)
self.assertEqual(self.data, result.data)
self.assertEqual(self.service, result.source)
self.assertEqual(self.service.errors, result.errors)
self.assertEqual(self.service.failed_objects, result.failed_objects)
self.assertEqual(self.service.processed, result.processed)
def test_when_not_processed(self):
self.service.processed = False
result = OperationResult(data=self.data, source=self.service)
self.assertFalse(result.success())
self.assertTrue(result.fail())
def test_when_processed_without_errors(self):
self.service.processed = True
result = OperationResult(data=self.data, source=self.service)
self.assertTrue(result.success())
self.assertFalse(result.fail())
self.assertFalse(result.source.errors)
def test_when_processed_with_dict_errors(self):
self.service.processed = True
self.service.errors = { 'foo' : 'foo is not bar' }
result = OperationResult(data=self.data, source=self.service)
self.assertFalse(result.success())
self.assertTrue(result.fail())
self.assertTrue(result.source.errors)
def test_when_processed_with_list_errors(self):
self.service.processed = True
self.service.errors = ['foo is not bar']
result = OperationResult(data=self.data, source=self.service)
self.assertFalse(result.success())
self.assertTrue(result.fail())
self.assertTrue(result.source.errors)
class BaseServiceTest(TestCase):
def setUp(self):
self.service = ServiceMock(
params={ 'foo': 'bar' },
foo_required=True,
bar_allowed=False
)
def test_constructor(self):
self.assertEqual(None, self.service.raw_result)
self.assertEqual(None, self.service.errors)
self.assertEqual([], self.service.failed_objects)
self.assertEqual('bar', self.service.params['foo'])
self.assertFalse(self.service.processed)
self.assertFalse(self.service.bar_allowed)
self.assertTrue(self.service.foo_required)
@patch('backend.common.tests.tests_services.ServiceMock.execute', Mock())
def test_do_execute_call(self):
ServiceMock.do_execute({ 'foo': 'bar' })
ServiceMock.execute.assert_called_with()
def test_do_process_return(self):
result = ServiceMock.do_execute({ 'foo': 'bar' })
self.assertIsInstance(result, OperationResult)
@patch('backend.common.tests.tests_services.ServiceMock.process', Mock())
def test_do_process_call(self):
ServiceMock.do_process({ 'foo': 'bar' })
ServiceMock.process.assert_called_with()
def test_do_process_return(self):
result = ServiceMock.do_process({ 'foo': 'bar' })
self.assertIsNotNone(result)
@patch('backend.common.tests.tests_services.ServiceMock.process', Mock())
def test_process_call(self):
ServiceMock().execute()
ServiceMock.process.assert_called_with()
def test_operation_result(self):
result = self.service.execute()
self.assertIsInstance(result, OperationResult)
def test_processed(self):
self.assertFalse(self.service.processed)
self.service.execute()
self.assertTrue(self.service.processed)
class BaseServiceWithErrorsTest(TestCase):
def setUp(self):
self.service = ServiceMock(params={ 'foo': 'bar' })
@patch('backend.common.tests.tests_services.ServiceMock.process', side_effect=raise_known_error)
def test_execute_with_known_exceptions(self, _process):
result = self.service.execute()
self.assertFalse(result.success())
self.assertEqual('You should catch me!', result.errors[0])
self.assertIsInstance(result.failed_objects[0], KnownError)
@patch('backend.common.tests.tests_services.ServiceMock.process', side_effect=raise_unknown_error)
def test_execute_with_unknown_exceptions(self, _process):
self.assertRaises(UnknownError, self.service.execute)
self.assertTrue(self.service.processed)
self.assertIsNone(self.service.errors)
self.assertIsNone(self.service.raw_result)
self.assertEqual([], self.service.failed_objects)
@patch('backend.common.tests.tests_services.ServiceMock.process', side_effect=raise_known_error)
def test_process_with_known_exceptions(self, _process):
self.assertRaises(KnownError, self.service.process)
self.assertFalse(self.service.processed)
self.assertIsNone(self.service.errors)
self.assertIsNone(self.service.raw_result)
self.assertEqual([], self.service.failed_objects)
@patch('backend.common.tests.tests_services.ServiceMock.process', side_effect=raise_unknown_error)
def test_process_with_unknown_exceptions(self, _process):
self.assertRaises(UnknownError, self.service.process)
self.assertFalse(self.service.processed)
self.assertIsNone(self.service.errors)
self.assertIsNone(self.service.raw_result)
self.assertEqual([], self.service.failed_objects)
# Works on Python 2.6 and up:
try:
# Use "raise" to raise an error
raise IndexError("This is an index error")
except IndexError as e:
pass # Pass is just a no-op. Usually you would do recovery here.
except (TypeError, NameError):
pass # Multiple exceptions can be handled together, if required.
else: # Optional clause to the try/except block. Must follow all except blocks
print "All good!" # Runs only if the code in try raises no exceptions
finally: # Execute under all circumstances
print "We can clean up resources here"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment