Last active
February 28, 2021 12:23
-
-
Save tiagopog/9114513e3a1ce8e3d8dda76fa1ee492d to your computer and use it in GitHub Desktop.
Python - Techniques & Concepts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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())) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
# 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...') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
# 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()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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