From 7879674c491b16646ad1dc8ea4a1f5ff8fb7e933 Mon Sep 17 00:00:00 2001 From: <> Date: Thu, 27 Apr 2023 09:56:38 +0000 Subject: [PATCH] Deployed 3d01c98 with MkDocs version: 1.4.2 --- .nojekyll | 0 404.html | 1006 +++ assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.51198bba.min.js | 29 + assets/javascripts/bundle.51198bba.min.js.map | 8 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.208ed371.min.js | 42 + .../workers/search.208ed371.min.js.map | 8 + assets/stylesheets/main.ded33207.min.css | 1 + assets/stylesheets/main.ded33207.min.css.map | 1 + assets/stylesheets/palette.a0c5b2b5.min.css | 1 + .../stylesheets/palette.a0c5b2b5.min.css.map | 1 + best_practices/code_principles/index.html | 1620 ++++ best_practices/styleguide/index.html | 5701 ++++++++++++++ best_practices/zen_of_python/index.html | 1060 +++ django/image_field/index.html | 1268 ++++ index.html | 1038 +++ machine_learning/1_introduction/index.html | 1265 ++++ machine_learning/2_regression/index.html | 1193 +++ machine_learning/3_evaluation/index.html | 1405 ++++ .../4_regression_first_contact/index.html | 3643 +++++++++ .../assets/classification-vs-regression.png | Bin 0 -> 286908 bytes machine_learning/assets/clustering.png | Bin 0 -> 183499 bytes machine_learning/assets/lifecycle.png | Bin 0 -> 183773 bytes machine_learning/assets/linear-regression.png | Bin 0 -> 40902 bytes machine_learning/assets/linear-vs-poly.png | Bin 0 -> 144106 bytes .../assets/reinforcement-learning.png | Bin 0 -> 120403 bytes machine_learning/assets/subfield.png | Bin 0 -> 188279 bytes onboarding/index.html | 1182 +++ onboarding/modules/module1-python/index.html | 1148 +++ onboarding/modules/module2-django/index.html | 1210 +++ onboarding/modules/module3-drf/index.html | 1185 +++ onboarding/modules/module4-pandas/index.html | 1139 +++ onboarding/modules/module5-graphql/index.html | 1137 +++ .../modules/module6-pipelines/index.html | 1039 +++ onboarding/modules/module7-docker/index.html | 1227 +++ onboarding/onboarding-sample/index.html | 1412 ++++ onboarding/onboarding-template/index.html | 1181 +++ onboarding/project-setup-guide/index.html | 1477 ++++ onboarding/projects/project-1-smul/index.html | 1125 +++ .../projects/project-2-library/index.html | 1098 +++ pre-commit/index.html | 1295 ++++ python_virtual_env/index.html | 1611 ++++ sitemap.xml | 178 + sitemap.xml.gz | Bin 0 -> 219 bytes stack_and_tools/ides/index.html | 1182 +++ stack_and_tools/macos_setup/index.html | 1137 +++ .../1_different_ways_of_testing/index.html | 1191 +++ testing/2_sample_ticket/index.html | 1611 ++++ testing/3_unittest/index.html | 1515 ++++ testing/4_pytest/index.html | 1416 ++++ testing/5_mock/index.html | 1463 ++++ testing/6_factory_boy/index.html | 1380 ++++ testing/7_github_actions/index.html | 1252 +++ testing/8_coverage/index.html | 1197 +++ testing/9_performance/index.html | 1096 +++ 83 files changed, 59569 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.51198bba.min.js create mode 100644 assets/javascripts/bundle.51198bba.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.208ed371.min.js create mode 100644 assets/javascripts/workers/search.208ed371.min.js.map create mode 100644 assets/stylesheets/main.ded33207.min.css create mode 100644 assets/stylesheets/main.ded33207.min.css.map create mode 100644 assets/stylesheets/palette.a0c5b2b5.min.css create mode 100644 assets/stylesheets/palette.a0c5b2b5.min.css.map create mode 100644 best_practices/code_principles/index.html create mode 100644 best_practices/styleguide/index.html create mode 100644 best_practices/zen_of_python/index.html create mode 100644 django/image_field/index.html create mode 100644 index.html create mode 100644 machine_learning/1_introduction/index.html create mode 100644 machine_learning/2_regression/index.html create mode 100644 machine_learning/3_evaluation/index.html create mode 100644 machine_learning/4_regression_first_contact/index.html create mode 100644 machine_learning/assets/classification-vs-regression.png create mode 100644 machine_learning/assets/clustering.png create mode 100644 machine_learning/assets/lifecycle.png create mode 100644 machine_learning/assets/linear-regression.png create mode 100644 machine_learning/assets/linear-vs-poly.png create mode 100644 machine_learning/assets/reinforcement-learning.png create mode 100644 machine_learning/assets/subfield.png create mode 100644 onboarding/index.html create mode 100644 onboarding/modules/module1-python/index.html create mode 100644 onboarding/modules/module2-django/index.html create mode 100644 onboarding/modules/module3-drf/index.html create mode 100644 onboarding/modules/module4-pandas/index.html create mode 100644 onboarding/modules/module5-graphql/index.html create mode 100644 onboarding/modules/module6-pipelines/index.html create mode 100644 onboarding/modules/module7-docker/index.html create mode 100644 onboarding/onboarding-sample/index.html create mode 100644 onboarding/onboarding-template/index.html create mode 100644 onboarding/project-setup-guide/index.html create mode 100644 onboarding/projects/project-1-smul/index.html create mode 100644 onboarding/projects/project-2-library/index.html create mode 100644 pre-commit/index.html create mode 100644 python_virtual_env/index.html create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz create mode 100644 stack_and_tools/ides/index.html create mode 100644 stack_and_tools/macos_setup/index.html create mode 100644 testing/1_different_ways_of_testing/index.html create mode 100644 testing/2_sample_ticket/index.html create mode 100644 testing/3_unittest/index.html create mode 100644 testing/4_pytest/index.html create mode 100644 testing/5_mock/index.html create mode 100644 testing/6_factory_boy/index.html create mode 100644 testing/7_github_actions/index.html create mode 100644 testing/8_coverage/index.html create mode 100644 testing/9_performance/index.html diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..424b62c --- /dev/null +++ b/404.html @@ -0,0 +1,1006 @@ + + + +
+ + + + + + + + + + + + + +Code should be clear and easy to maintain, by maintaining we also mean writing and +revisiting tests if necessary. Below there's a list of principles which used +with The Zen of Python, will help us to write better code as well as to test it.
+The snippet below is hard to read and takes time to understand
+f = lambda x: x if x in {0, 1} else f(x - 1) + f(x - 2)
+Alternatively, the next code snippet is much easier to understand and maintain.
+def fibonacci(number: int) -> int:
+ if number in {0, 1}:
+ return number
+ return fibonacci(number - 1) + fibonacci(number - 2)
+By adding descriptive names, type hints, and splitting the line into multiple ones it gets +easier to read and maintain.
+++The main goal in design should always be to be as easy to understand +as possible.
+
This principle is about writing functions and automating sections of code that are repeated. If you perform the same +task multiple times in your code, consider a function or a loop to make your workflow more efficient.
+Let's consider the next example where we'll be using a math expression to convert temperature values from fahrenheit +to celsius.
+temp_1 = 32
+res_1 = (temp_1-32) * 5/9
+
+temp_2 = 40
+res_2 = (temp_2-32) * 5/9
+Some points to consider:
+Let's create a method for the math expression.
+def conv_fahr_to_celsius(fahr: float) -> float:
+ """Convert temperature in Fahrenheit to Celsius.
+
+ Parameters:
+ -----------
+ fahr: float
+ The temperature in Fahrenheit.
+
+ Returns:
+ -----------
+ Celsius : int or float
+ The temperature in Celsius.
+ """
+ celsius = (fahr-32) * 5/9
+ return celsius
+And update the previous sample to use this method.
+fahr_1 = 32
+celsius_1 = conv_fahr_to_celsius(fahr_1)
+
+fahr_2 = 40
+celsius_2 = conv_fahr_to_celsius(fahr_2)
+In summary
+A known example of this is the model-view-controller (MVC) design. MVC separates a program +into three distinct areas: the data (model), the logic (controller), and what the page displays (view).
+SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible, +and maintainable.
+Every component of your code (in general a class, but also a function) should have one and only one responsibility. +As a consequence of that, there should be only a reason to change it.
+Too often you see a piece of code that takes care of an entire process all at once. I.e., A function that loads data, +modifies and, plots them, all before returning its result.
+Let’s take a simpler example, where we have a list of number L = [n1, n2, …, nx] and we compute some mathematical +functions to this list. For example, compute the mean, median, etc.
+A bad approach would be to have a single function doing all the work:
+import numpy as np
+
+def math_operations(list_):
+ # Compute Average
+ print(f"the mean is {np.mean(list_)}")
+ # Compute Max
+ print(f"the max is {np.max(list_)}")
+
+math_operations(list_ = [1,2,3,4,5])
+# the mean is 3.0
+# the max is 5
+The first thing we should do, to make this more SRP compliant, is to split the function math_operations into atomic +functions! Thus, when a function’s responsibility cannot be divided into more sub-parts.
+The second step is to make a single function (or class), generically named, “main”. This will call all the other +functions one-by-one in a step-to-step process.
+import numpy as np
+
+def get_mean(list_):
+ """
+ Compute Mean
+ """
+ print(f"the mean is {np.mean(list_)}")
+
+def get_max(list_):
+ """
+ Compute Max
+ """
+ print(f"the max is {np.max(list_)}")
+
+def main(list_):
+ # Compute Average
+ get_mean(list_)
+ # Compute Max
+ get_max(list_)
+
+main([1,2,3,4,5])
+# the mean is 3.0
+# the max is 5
+Now, you would only have one single reason to change each function connected with “main”.
+The result of this simple action is that now:
+This is already a much bigger improvement with respect to the first code example. But, having created a “main” and +calling functions with single responsibility is not the full fulfilment of the SR principle. Indeed, our “main” has +many reasons to be changed. The class is actually fragile and hard to maintain. To solve that, let’s introduce the +next principle.
+You should not need to modify the code you have already written to accommodate new functionality, but simply add what +you now need.
+This does not mean that you cannot change your code when the code premises needs to be modified, but that if you need +to add new functions similar to the one present, you should not require to change other parts of the code. To clarify +this point let’s refer to the example we saw earlier. If we wanted to add new functionality, for example, compute the +median, we should have created a new method function and add its invocation to “main”. That would have added an +extension but also modified the main.
+We can solve this by turning all the functions we wrote into subclasses of a class. In this case, I have created an +abstract class called “Operations” with an abstract method “get_operation”. (Abstract classes are generally an +advanced topic. If you don’t know what an abstract class is, you can run the following code even without).
+Now, all the old functions, now classes are called by the subclasses() method. That will find all classes inheriting +from Operations and operate the function “operations” that is present in all subclasses.
+++for additional information on abstractmethod decorator please +check https://docs.python.org/3/library/abc.html#abc.abstractmethod
+
import numpy as np
+from abc import ABC, abstractmethod
+
+class Operations(ABC):
+ """Operations"""
+ @staticmethod
+ @abstractmethod
+ def operation(list_):
+ pass
+
+class Mean(Operations):
+ """Compute Max"""
+ @staticmethod
+ def operation(list_):
+ print(f"The mean is {np.mean(list_)}")
+
+class Max(Operations):
+ """Compute Max"""
+ @staticmethod
+ def operation(list_):
+ print(f"The max is {np.max(list_)}")
+
+class Main:
+ """Main"""
+ @staticmethod
+ @abstractmethod
+ def get_operations(list_):
+ # __subclasses__ will find all classes inheriting from Operations
+ for operation in Operations.__subclasses__():
+ operation.operation(list_)
+
+
+if __name__ == "__main__":
+ Main.get_operations([1,2,3,4,5])
+# The mean is 3.0
+# The max is 5
+If now we want to add a new operation e.g.: median, we will only need to add a class “Median” inheriting from the class +“Operations”. The newly formed subclass will be immediately picked up by subclasses() and no modification in any +other part of the code needs to happen.
+The result is a very flexible class, that requires minimum time to be maintained.
+Functions that use pointers or references to base classes must be able to use objects of derived classes without +knowing it, that alternatively can be expressed as, derived classes must be substitutable for their base classes.
+In (maybe) simpler words, if a subclass redefines a function also present in the parent class, a client-user should not +be noticing any difference in behaviour, and it is a substitute for the base class. For example, if you are using a +function and your colleague change the base class, you should not notice any difference in the function that you are +using.
+Among all the SOLID principle, this is the most abstruse to understand and to explain. For this principle, there is no +standard “template-like” solution where it must be applied, and it is hard to offer a “standard example” to showcase.
+In the most simplistic way, I can put it, this principle can be summarised by saying: +If in a subclass, you redefine a function that is also present in the base class, the two functions ought to have the +same behaviour. This, though, does not mean that they must be mandatory equal, but that the user, should expect that +the same type of result, given the same input. +In the example ocp.py, the “operation” method is present in the subclasses and in the base class, and an end-user should +expect the same behaviour from the two.
+The result of this principle is that we’d write our code in a consistent manner and, the end-user will need to learn how +our code works, only one.
+++A consequence of LSP is that: the new redefined function in the subclass should be valid and +be possibly used wherever the same function in the parent class is used.
+This is not, typically the case, indeed usually we, human, think in terms of set theory. +Having a class that define a concept and subclasses that expand the first with an +exception or different behaviour.
+For example, the subclass “Platypus”, of the base class “Mammals”, would have the +exception that these mammals lay eggs. The LSP, tell us that it would create a +function called “give_birth”, this function will have different behaviour +for the subclass Platypus and the subclass Dog. Therefore, we should have had a +more abstract base class than Mammals that accommodate this. +If this sounds very confusing, do not worry, the application of this latter aspect +of the LSP is rarely fully implemented, and it rarely leaves the theoretical textbooks.
+
Many client-specific interfaces are better than one general-purpose interface. In the context of classes, an interface +is considered, all the methods and properties exposed, thus, everything that a user can interact with that belongs to +that class.
+In this sense, the IS principles tell us that a class should only have the interface needed (SRP) and avoid methods +that won’t work or that have no reason to be part of that class.
+This problem arises, primarily, when, a subclass inherits methods from a base class that it does not need.
+Let’s see an example:
+++for additional information on abstractmethod decorator please +check https://docs.python.org/3/library/abc.html#abc.abstractmethod
+
from abc import ABC, abstractmethod
+
+class Mammals(ABC):
+ @staticmethod
+ @abstractmethod
+ def swim():
+ print("Can Swim")
+
+ @staticmethod
+ @abstractmethod
+ def walk():
+ print("Can Walk")
+
+class Human(Mammals):
+ @staticmethod
+ def swim():
+ return print("Humans can swim")
+
+ @staticmethod
+ def walk():
+ return print("Humans can walk")
+
+class Whale(Mammals):
+ @staticmethod
+ def swim():
+ return print("Whales can swim")
+For this example, we have got the abstract class “Mammals” that has two abstract methods: “walk” and “swim”. These two +elements will belong to the subclass “Human”, whereas only “swim” will belong to the subclass “Whale”.
+And indeed, if we run this code we could have:
+Human.swim()
+Human.walk()
+
+Whale.swim()
+Whale.walk()
+
+# Humans can swim
+# Humans can walk
+# Whales can swim
+# Can Walk
+The subclass whale can still invoke the method “walk” but it shouldn’t, and we must avoid it.
+The way suggested by ISP is to create more client-specific interfaces rather than one general-purpose interface. +So, our code example becomes:
+from abc import ABC, abstractmethod
+
+class Walker(ABC):
+ @staticmethod
+ @abstractmethod
+ def walk():
+ return print("Can Walk")
+
+class Swimmer(ABC):
+ @staticmethod
+ @abstractmethod
+ def swim():
+ return print("Can Swim")
+
+class Human(Walker, Swimmer):
+ @staticmethod
+ def walk():
+ return print("Humans can walk")
+ @staticmethod
+ def swim():
+ return print("Humans can swim")
+
+class Whale(Swimmer):
+ @staticmethod
+ def swim():
+ return print("Whales can swim")
+
+if __name__ == "__main__":
+ Human.walk()
+ Human.swim()
+
+ Whale.swim()
+ Whale.walk()
+
+# Humans can walk
+# Humans can swim
+# Whales can swim
+# AttributeError: type object 'Whale' has no attribute 'walk'
+Now, every subclass inherits only what it needs, avoiding invoking an out-of-context (wrong) sub-method. That might +create an error hard to catch.
+This principle is closely connected with the other ones and specifically, it tells us to keep the content of a subclass +clean from elements of no use to that subclass. This has the final aim to keep our classes clean and minimise mistakes.
+Abstractions should not depend on details. Details should depend on abstraction. High-level modules should not depend +on low-level modules. Both should depend on abstractions. So, that abstractions (e.g., the interface, as seen above) +should not be dependent on low-level methods but both should depend on a third interface.
+To better explain this concept, I prefer to think of a sort of information flow.
+Imagine that you have a program that takes in input a specific set of info (a file, a format, etc) and you wrote a +script to process it. What would happen if that info were subject to change? +You would have to rewrite your script and adjust the new format. Losing the retro compatibility with the older files.
+However, you could solve this by creating a third abstraction that takes the info as input and passes it to the others. +This is basically what an API is also, used for.
+flowchart LR
+ ObjectA --> |references| ObjectB
+ subgraph Package B
+ ObjectB
+ end
+ subgraph Package A
+ ObjectA
+ end
+flowchart TB
+ ObjectB --> |inherits| Interface
+ subgraph Package B
+ ObjectB
+ end
+ subgraph Package A
+ ObjectA--> |references| Interface
+ end
+The interesting design concept of this principle is that it is the reverse approach to what we would normally do.
+With the DIP in mind, we would start from the end of the project, in which our code is independent of what takes in +input, and it is not susceptible to changes and out of our direct control.
+It's a mantra from Extreme Programming that's often used generally in agile software teams. It's a statement that some +capability we presume our software needs in the future should not be built now because "you aren't gonna need it".
+++For additional information on this make sure to +check https://www.martinfowler.com/bliki/Yagni.html
+
++Inspired by https://google.github.io/styleguide/pyguide.html and +https://phalt.github.io/django-api-domains/styleguide/.
+
This style guide is split into two sections, one for the project structure and another which is a list of +dos and don'ts for Python programs.
+A domain is a piece of software that provides a distinct business value for your application. What this styleguide +calls a domain is roughly an extension of what Django would call an app. Therefore a business domain should have at +least one distinct software domain mirroring it.
+This guide tries to keep the key benefits of Django's app pattern - namely Django's models to represent tables in a +datastore, but with an emphasis on skinny models.
+++A domain should allow between 4-6 developers (3 pairs) to comfortably work on it. If you find your developers being +blocked by each other then it is time to consider splitting the domain or checking whether the software has not +diverged too far from the styleguide.
+
++This applies even in situations where you extract one domain into two domains to increase velocity, but they still +have to maintain a dependency between one another. We have found that if you relax the bounded context between domains, +the boundary will erode and you will lose the ability to work on them independent of each other.
+
You can mask one of the required files as a directory for better file organisation. For example, you might want to split views.py file into this structure:
+views/
+ __init__.py
+ brand.py
+ legacy_brand.py
+++You might notice that Django creates migrations even when nothing has changed in the model definition, these migrations should not be added to the codebase.
+
Besides the initial migration, all the remaining migrations must include a name that represent the change while including the date and time of it's creation, in the following format <migration_number>_<migration_description>_<date>_<time>.py which as an example can be represented as 0004_added_status_to_sample_table_20210404_12_30.py.
Following the Django documentation this folder holds the definition for the custom Django commands for a given Django app/domain. For instance, instead of a defining a traditional custom python script, a custom Django command should be added to take advantage of the already existing logic.
+The ruling for absolute or relative imports is as follows:
+++TL;DR - relative imports inside a domain, absolute for everything else!
+
With this ruling domains are easy to package and move around. When it comes time to move it into it's own project; tidying up imports will be one less thing you have to do.
+Run pylint over your code using this pylintrc.
pylint
+is a tool for finding bugs and style problems in Python source code. It finds
+problems that are typically caught by a compiler for less dynamic languages like
+C and C++. Because of the dynamic nature of Python, some
+warnings may be incorrect; however, spurious warnings should be fairly
+infrequent.
Catches easy-to-miss errors like typos, using-vars-before-assignment, etc.
+pylint
+isn't perfect. To take advantage of it, sometimes we'll need to write around it,
+suppress its warnings or fix it.
Make sure you run
+pylint
+on your code.
Suppress warnings if they are inappropriate so that other issues are not hidden. +To suppress warnings, you can set a line-level comment:
+dict = 'something awful' # Bad Idea... pylint: disable=redefined-builtin
+pylint
+warnings are each identified by symbolic name (empty-docstring)
+Google-specific warnings start with g-.
If the reason for the suppression is not clear from the symbolic name, add an +explanation.
+Suppressing in this way has the advantage that we can easily search for +suppressions and revisit them.
+You can get a list of
+pylint
+warnings by doing:
pylint --list-msgs
+To get more information on a particular message, use:
+pylint --help-msg=C6409
+Prefer pylint: disable to the deprecated older form pylint: disable-msg.
Unused argument warnings can be suppressed by deleting the variables at the +beginning of the function. Always include a comment explaining why you are +deleting it. "Unused." is sufficient. For example:
+def viking_cafe_order(spam, beans, eggs=None):
+ del beans, eggs # Unused by vikings.
+ return spam + spam + spam
+Other common forms of suppressing this warning include using '_' as the
+identifier for the unused argument or prefixing the argument name with
+'unused_', or assigning them to '_'. These forms are allowed but no longer
+encouraged. These break callers that pass arguments by name and do not enforce
+that the arguments are actually unused.
Use import statements for packages and modules only, not for individual
+classes or functions. Note that there is an explicit exemption for imports from
+the typing module.
Reusability mechanism for sharing code from one module to another.
+The namespace management convention is simple. The source of each identifier is
+indicated in a consistent way; x.Obj says that object Obj is defined in
+module x.
Module names can still collide. Some module names are inconveniently long.
+import x for importing packages and modules.from x import y where x is the package prefix and y is the module
+ name with no prefix.from x import y as z if two modules named y are to be imported or if
+ y is an inconveniently long name.import y as z only when z is a standard abbreviation (e.g., np for
+ numpy).For example the module sound.effects.echo may be imported as follows:
from sound.effects import echo
+...
+echo.EchoFilter(input, output, delay=0.7, atten=4)
+Do not use relative names in imports. Even if the module is in the same package, +use the full package name. This helps prevent unintentionally importing a +package twice.
+Imports from the typing module and the +six.moves module +are exempt from this rule.
+Import each module using the full pathname location of the module.
+Avoids conflicts in module names or incorrect imports due to the module search +path not being what the author expected. Makes it easier to find modules.
+Makes it harder to deploy code because you have to replicate the package +hierarchy. Not really a problem with modern deployment mechanisms.
+All new code should import each module by its full package name.
+Imports should be as follows:
+Yes:
+# Reference absl.flags in code with the complete name (verbose).
+import absl.flags
+from doctor.who import jodie
+
+FLAGS = absl.flags.FLAGS
+# Reference flags in code with just the module name (common).
+from absl import flags
+from doctor.who import jodie
+
+FLAGS = flags.FLAGS
+No: (assume this file lives in doctor/who/ where jodie.py also exists)
# Unclear what module the author wanted and what will be imported. The actual
+# import behavior depends on external factors controlling sys.path.
+# Which possible jodie module did the author intend to import?
+import jodie
+The directory the main binary is located in should not be assumed to be in
+sys.path despite that happening in some environments. This being the case,
+code should assume that import jodie refers to a third party or top level
+package named jodie, not a local jodie.py.
Exceptions are allowed but must be used carefully.
+Exceptions are a means of breaking out of normal control flow to handle errors +or other exceptional conditions.
+The control flow of normal operation code is not cluttered by error-handling +code. It also allows the control flow to skip multiple frames when a certain +condition occurs, e.g., returning from N nested functions in one step instead of +having to plumb error codes through.
+May cause the control flow to be confusing. Easy to miss error cases when making +library calls.
+Exceptions must follow certain conditions:
+Make use of built-in exception classes when it makes sense. For example,
+ raise a ValueError to indicate a programming mistake like a violated
+ precondition (such as if you were passed a negative number but required a
+ positive one). Do not use assert statements for validating argument values
+ of a public API. assert is used to ensure internal correctness, not to
+ enforce correct usage nor to indicate that some unexpected event occurred.
+ If an exception is desired in the latter cases, use a raise statement. For
+ example:
Yes:
+ def connect_to_next_port(self, minimum):
+ """Connects to the next available port.
+
+ Args:
+ minimum: A port value greater or equal to 1024.
+
+ Returns:
+ The new minimum port.
+
+ Raises:
+ ConnectionError: If no available port is found.
+ """
+ if minimum < 1024:
+ # Note that this raising of ValueError is not mentioned in the doc
+ # string's "Raises:" section because it is not appropriate to
+ # guarantee this specific behavioral reaction to API misuse.
+ raise ValueError(f'Min. port must be at least 1024, not {minimum}.')
+ port = self._find_next_open_port(minimum)
+ if not port:
+ raise ConnectionError(
+ f'Could not connect to service on port {minimum} or higher.')
+ assert port >= minimum, (
+ f'Unexpected port {port} when minimum was {minimum}.')
+ return port
+No:
+ def connect_to_next_port(self, minimum):
+ """Connects to the next available port.
+
+ Args:
+ minimum: A port value greater or equal to 1024.
+
+ Returns:
+ The new minimum port.
+ """
+ assert minimum >= 1024, 'Minimum port must be at least 1024.'
+ port = self._find_next_open_port(minimum)
+ assert port is not None
+ return port
+Libraries or packages may define their own exceptions. When doing so they
+ must inherit from an existing exception class. Exception names should end in
+ Error and should not introduce stutter (foo.FooError).
Never use catch-all except: statements, or catch Exception or
+ StandardError, unless you are
re-raising the exception, or
+creating an isolation point in the program where exceptions are not + propagated but are recorded and suppressed instead, such as protecting a + thread from crashing by guarding its outermost block.
+Python is very tolerant in this regard and except: will really catch
+everything including misspelled names, sys.exit() calls, Ctrl+C interrupts,
+unittest failures and all kinds of other exceptions that you simply don't
+want to catch.
Minimize the amount of code in a try/except block. The larger the body
+ of the try, the more likely that an exception will be raised by a line of
+ code that you didn't expect to raise an exception. In those cases, the
+ try/except block hides a real error.
Use the finally clause to execute code whether or not an exception is
+ raised in the try block. This is often useful for cleanup, i.e., closing a
+ file.
Avoid global variables.
+Variables that are declared at the module level or as class attributes.
+Occasionally useful.
+Has the potential to change module behavior during the import, because +assignments to global variables are done when the module is first imported.
+Avoid global variables.
+While they are technically variables, module-level constants are permitted and
+encouraged. For example: MAX_HOLY_HANDGRENADE_COUNT = 3. Constants must be
+named using all caps with underscores. See Naming below.
If needed, globals should be declared at the module level and made internal to
+the module by prepending an _ to the name. External access must be done
+through public module-level functions. See Naming below.
Nested local functions or classes are fine when used to close over a local +variable. Inner classes are fine.
+A class can be defined inside of a method, function, or class. A function can be +defined inside a method or function. Nested functions have read-only access to +variables defined in enclosing scopes.
+Allows definition of utility classes and functions that are only used inside of +a very limited scope. Very +ADT-y. +Commonly used for implementing decorators.
+Nested functions and classes cannot be directly tested. Nesting can make the +outer function longer and less readable.
+They are fine with some caveats. Avoid nested functions or classes except when +closing over a local value. Do not nest a function just to hide it from users of +a module. Instead, prefix its name with an _ at the module level so that it can +still be accessed by tests.
+Okay to use for simple cases.
+List, Dict, and Set comprehensions as well as generator expressions provide a
+concise and efficient way to create container types and iterators without
+resorting to the use of traditional loops, map(), filter(), or lambda.
Simple comprehensions can be clearer and simpler than other dict, list, or set +creation techniques. Generator expressions can be very efficient, since they +avoid the creation of a list entirely.
+Complicated comprehensions or generator expressions can be hard to read.
+Okay to use for simple cases. Each portion must fit on one line: mapping
+expression, for clause, filter expression. Multiple for clauses or filter
+expressions are not permitted. Use loops instead when things get more
+complicated.
Yes:
+ result = [mapping_expr for value in iterable if filter_expr]
+
+ result = [{'key': value} for value in iterable
+ if a_long_filter_expression(value)]
+
+ result = [complicated_transform(x)
+ for x in iterable if predicate(x)]
+
+ descriptive_name = [
+ transform({'key': key, 'value': value}, color='black')
+ for key, value in generate_iterable(some_input)
+ if complicated_condition_is_met(key, value)
+ ]
+
+ result = []
+ for x in range(10):
+ for y in range(5):
+ if x * y > 10:
+ result.append((x, y))
+
+ return {x: complicated_transform(x)
+ for x in long_generator_function(parameter)
+ if x is not None}
+
+ squares_generator = (x**2 for x in range(10))
+
+ unique_names = {user.name for user in users if user is not None}
+
+ eat(jelly_bean for jelly_bean in jelly_beans
+ if jelly_bean.color == 'black')
+No:
+ result = [complicated_transform(
+ x, some_argument=x+1)
+ for x in iterable if predicate(x)]
+
+ result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]
+
+ return ((x, y, z)
+ for x in range(5)
+ for y in range(5)
+ if x != y
+ for z in range(5)
+ if y != z)
+Use default iterators and operators for types that support them, like lists, +dictionaries, and files.
+Container types, like dictionaries and lists, define default iterators and +membership test operators ("in" and "not in").
+The default iterators and operators are simple and efficient. They express the +operation directly, without extra method calls. A function that uses default +operators is generic. It can be used with any type that supports the operation.
+You can't tell the type of objects by reading the method names (e.g. has_key()
+means a dictionary). This is also an advantage.
Use default iterators and operators for types that support them, like lists, +dictionaries, and files. The built-in types define iterator methods, too. Prefer +these methods to methods that return lists, except that you should not mutate a +container while iterating over it.
+Yes: for key in adict: ...
+ if key not in adict: ...
+ if obj in alist: ...
+ for line in afile: ...
+ for k, v in adict.items(): ...
+ for k, v in six.iteritems(adict): ...
+No: for key in adict.keys(): ...
+ if not adict.has_key(key): ...
+ for line in afile.readlines(): ...
+ for k, v in dict.iteritems(): ...
+Use generators as needed.
+A generator function returns an iterator that yields a value each time it +executes a yield statement. After it yields a value, the runtime state of the +generator function is suspended until the next value is needed.
+Simpler code, because the state of local variables and control flow are +preserved for each call. A generator uses less memory than a function that +creates an entire list of values at once.
+None.
+Fine. Use "Yields:" rather than "Returns:" in the docstring for generator +functions.
+Okay for one-liners. Prefer generator expressions over map() or filter()
+with a lambda.
Lambdas define anonymous functions in an expression, as opposed to a statement.
+Convenient.
+Harder to read and debug than local functions. The lack of names means stack +traces are more difficult to understand. Expressiveness is limited because the +function may only contain an expression.
+Okay to use them for one-liners. If the code inside the lambda function is +longer than 60-80 chars, it's probably better to define it as a regular +nested function.
+For common operations like multiplication, use the functions from the operator
+module instead of lambda functions. For example, prefer operator.mul to
+lambda x, y: x * y.
Okay for simple cases.
+Conditional expressions (sometimes called a “ternary operator”) are mechanisms
+that provide a shorter syntax for if statements. For example: x = 1 if cond
+else 2.
Shorter and more convenient than an if statement.
+May be harder to read than an if statement. The condition may be difficult to +locate if the expression is long.
+Okay to use for simple cases. Each portion must fit on one line: +true-expression, if-expression, else-expression. Use a complete if statement +when things get more complicated.
+Yes:
+ one_line = 'yes' if predicate(value) else 'no'
+ slightly_split = ('yes' if predicate(value)
+ else 'no, nein, nyet')
+ the_longest_ternary_style_that_can_be_done = (
+ 'yes, true, affirmative, confirmed, correct'
+ if predicate(value)
+ else 'no, false, negative, nay')
+No:
+ bad_line_breaking = ('yes' if predicate(value) else
+ 'no')
+ portion_too_long = ('yes'
+ if some_long_module.some_long_predicate_function(
+ really_long_variable_name)
+ else 'no, false, negative, nay')
+Okay in most cases.
+You can specify values for variables at the end of a function's parameter list,
+e.g., def foo(a, b=0):. If foo is called with only one argument, b is set
+to 0. If it is called with two arguments, b has the value of the second
+argument.
Often you have a function that uses lots of default values, but on rare +occasions you want to override the defaults. Default argument values provide an +easy way to do this, without having to define lots of functions for the rare +exceptions. As Python does not support overloaded methods/functions, default +arguments are an easy way of "faking" the overloading behavior.
+Default arguments are evaluated once at module load time. This may cause +problems if the argument is a mutable object such as a list or a dictionary. If +the function modifies the object (e.g., by appending an item to a list), the +default value is modified.
+Okay to use with the following caveat:
+Do not use mutable objects as default values in the function or method +definition.
+Yes: def foo(a, b=None):
+ if b is None:
+ b = []
+Yes: def foo(a, b: Optional[Sequence] = None):
+ if b is None:
+ b = []
+Yes: def foo(a, b: Sequence = ()): # Empty tuple OK since tuples are immutable
+ ...
+No: def foo(a, b=[]):
+ ...
+No: def foo(a, b=time.time()): # The time the module was loaded???
+ ...
+No: def foo(a, b=FLAGS.my_thing): # sys.argv has not yet been parsed...
+ ...
+No: def foo(a, b: Mapping = {}): # Could still get passed to unchecked code
+ ...
+Use properties for accessing or setting data where you would normally have used +simple, lightweight accessor or setter methods.
+A way to wrap method calls for getting and setting an attribute as a standard +attribute access when the computation is lightweight.
+Readability is increased by eliminating explicit get and set method calls for +simple attribute access. Allows calculations to be lazy. Considered the Pythonic +way to maintain the interface of a class. In terms of performance, allowing +properties bypasses needing trivial accessor methods when a direct variable +access is reasonable. This also allows accessor methods to be added in the +future without breaking the interface.
+Can hide side-effects much like operator overloading. Can be confusing for +subclasses.
+Use properties in new code to access or set data where you would normally have
+used lightweight accessor or setter methods. Properties should be created with
+the @property decorator.
Inheritance with properties can be non-obvious if the property itself is not +overridden. Thus one must make sure that accessor methods are called indirectly +to ensure methods overridden in subclasses are called by the property (using the +template method design pattern).
+Yes: import math
+
+ class Square:
+ """A square with two properties: a writable area and a read-only perimeter.
+
+ To use:
+ >>> sq = Square(3)
+ >>> sq.area
+ 9
+ >>> sq.perimeter
+ 12
+ >>> sq.area = 16
+ >>> sq.side
+ 4
+ >>> sq.perimeter
+ 16
+ """
+
+ def __init__(self, side):
+ self.side = side
+
+ @property
+ def area(self):
+ """Area of the square."""
+ return self._get_area()
+
+ @area.setter
+ def area(self, area):
+ return self._set_area(area)
+
+ def _get_area(self):
+ """Indirect accessor to calculate the 'area' property."""
+ return self.side ** 2
+
+ def _set_area(self, area):
+ """Indirect setter to set the 'area' property."""
+ self.side = math.sqrt(area)
+
+ @property
+ def perimeter(self):
+ return self.side * 4
+Use the "implicit" false if at all possible.
+Python evaluates certain values as False when in a boolean context. A quick
+"rule of thumb" is that all "empty" values are considered false, so 0, None,
+[], {}, '' all evaluate as false in a boolean context.
Conditions using Python booleans are easier to read and less error-prone. In +most cases, they're also faster.
+May look strange to C/C++ developers.
+Use the "implicit" false if possible, e.g., if foo: rather than if foo !=
+[]:. There are a few caveats that you should keep in mind though:
Always use if foo is None: (or is not None) to check for a None value.
+ E.g., when testing whether a variable or argument that defaults to None
+ was set to some other value. The other value might be a value that's false
+ in a boolean context!
Never compare a boolean variable to False using ==. Use if not x:
+ instead. If you need to distinguish False from None then chain the
+ expressions, such as if not x and x is not None:.
For sequences (strings, lists, tuples), use the fact that empty sequences
+ are false, so if seq: and if not seq: are preferable to if len(seq):
+ and if not len(seq): respectively.
When handling integers, implicit false may involve more risk than benefit
+ (i.e., accidentally handling None as 0). You may compare a value which is
+ known to be an integer (and is not the result of len()) against the
+ integer 0.
Yes: if not users:
+ print('no users')
+
+ if foo == 0:
+ self.handle_zero()
+
+ if i % 10 == 0:
+ self.handle_multiple_of_ten()
+
+ def f(x=None):
+ if x is None:
+ x = []
+No: if len(users) == 0:
+ print('no users')
+
+ if foo is not None and not foo:
+ self.handle_zero()
+
+ if not i % 10:
+ self.handle_multiple_of_ten()
+
+ def f(x=None):
+ x = x or []
+Note that '0' (i.e., 0 as string) evaluates to true.
Okay to use.
+A nested Python function can refer to variables defined in enclosing functions, +but cannot assign to them. Variable bindings are resolved using lexical scoping, +that is, based on the static program text. Any assignment to a name in a block +will cause Python to treat all references to that name as a local variable, even +if the use precedes the assignment. If a global declaration occurs, the name is +treated as a global variable.
+An example of the use of this feature is:
+def get_adder(summand1):
+ """Returns a function that adds numbers to a given number."""
+ def adder(summand2):
+ return summand1 + summand2
+
+ return adder
+Often results in clearer, more elegant code. Especially comforting to +experienced Lisp and Scheme (and Haskell and ML and ...) programmers.
+Can lead to confusing bugs. Such as this example based on +PEP-0227:
+i = 4
+def foo(x):
+ def bar():
+ print(i, end='')
+ # ...
+ # A bunch of code here
+ # ...
+ for i in x: # Ah, i *is* local to foo, so this is what bar sees
+ print(i, end='')
+ bar()
+So foo([1, 2, 3]) will print 1 2 3 3,
+not 1 2 3 4.
Okay to use.
+Use decorators judiciously when there is a clear advantage. Avoid staticmethod
+and limit use of classmethod.
Decorators for Functions and Methods
+(a.k.a "the @ notation"). One common decorator is @property, used for
+converting ordinary methods into dynamically computed attributes. However, the
+decorator syntax allows for user-defined decorators as well. Specifically, for
+some function my_decorator, this:
class C:
+ @my_decorator
+ def method(self):
+ # method body ...
+is equivalent to:
+class C:
+ def method(self):
+ # method body ...
+ method = my_decorator(method)
+Elegantly specifies some transformation on a method; the transformation might +eliminate some repetitive code, enforce invariants, etc.
+Decorators can perform arbitrary operations on a function's arguments or return +values, resulting in surprising implicit behavior. Additionally, decorators +execute at import time. Failures in decorator code are pretty much impossible to +recover from.
+Use decorators judiciously when there is a clear advantage. Decorators should +follow the same import and naming guidelines as functions. Decorator pydoc +should clearly state that the function is a decorator. Write unit tests for +decorators.
+Avoid external dependencies in the decorator itself (e.g. don't rely on files,
+sockets, database connections, etc.), since they might not be available when the
+decorator runs (at import time, perhaps from pydoc or other tools). A
+decorator that is called with valid parameters should (as much as possible) be
+guaranteed to succeed in all cases.
Decorators are a special case of "top level code" - see main for +more discussion.
+Never use staticmethod unless forced to in order to integrate with an API
+defined in an existing library. Write a module level function instead.
Use classmethod only when writing a named constructor or a class-specific
+routine that modifies necessary global state such as a process-wide cache.
Do not rely on the atomicity of built-in types.
+While Python's built-in data types such as dictionaries appear to have atomic
+operations, there are corner cases where they aren't atomic (e.g. if __hash__
+or __eq__ are implemented as Python methods) and their atomicity should not be
+relied upon. Neither should you rely on atomic variable assignment (since this
+in turn depends on dictionaries).
Use the Queue module's Queue data type as the preferred way to communicate
+data between threads. Otherwise, use the threading module and its locking
+primitives. Prefer condition variables and threading.Condition instead of
+using lower-level locks.
Avoid these features.
+Python is an extremely flexible language and gives you many fancy features such
+as custom metaclasses, access to bytecode, on-the-fly compilation, dynamic
+inheritance, object reparenting, import hacks, reflection (e.g. some uses of
+getattr()), modification of system internals, etc.
These are powerful language features. They can make your code more compact.
+It's very tempting to use these "cool" features when they're not absolutely +necessary. It's harder to read, understand, and debug code that's using unusual +features underneath. It doesn't seem that way at first (to the original author), +but when revisiting the code, it tends to be more difficult than code that is +longer but is straightforward.
+Avoid these features in your code.
+Standard library modules and classes that internally use these features are okay
+to use (for example, abc.ABCMeta, dataclasses, and enum).
Python 3 is here! While not every project is ready to use it yet, +all code should be written to be 3 compatible (and tested under 3 when +possible).
+Python 3 is a significant change in the Python language. While existing code is +often written with 2.7 in mind, there are some simple things to do to make code +more explicit about its intentions and thus better prepared for use under Python +3 without modification.
+Code written with Python 3 in mind is more explicit and easier to get running +under Python 3 once all of the dependencies of your project are ready.
+Some people find the additional boilerplate to be ugly. It's unusual to add +imports to a module that doesn't actually require the features added by the +import.
+Use of from __future__ import statements is encouraged. All new code should
+contain the following and existing code should be updated to be compatible when
+possible:
from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+For more information on these imports, see
+absolute imports,
+/ division behavior, and
+the print function.
Please don't omit or remove these imports, even if they're not currently used in +the module, unless the code is Python 3 only. It is better to always have the +future imports in all files so that they are not forgotten during later edits +when someone starts using such a feature.
+There are other from __future__ import statements. Use them as you see fit. We
+do not include unicode_literals in our recommendations as it is not a clear
+win due to implicit default codec conversion consequences it introduces in many
+places within Python 2.7. Most code is better off with explicit use of b'' and
+u'' bytes and unicode string literals as necessary.
When your project needs to actively support use under both Python 2 and 3, use +the six, +future, and +past libraries as you see fit. They exist to +make your code cleaner and life easier.
+You can annotate Python 3 code with type hints according to +PEP-484, and type-check the code at +build time with a type checking tool like pytype.
+Type annotations can be in the source or in a +stub pyi file. Whenever +possible, annotations should be in the source. Use pyi files for third-party or +extension modules.
+Type annotations (or "type hints") are for function or method arguments and +return values:
+def func(a: int) -> List[int]:
+You can also declare the type of a variable using similar +PEP-526 syntax:
+a: SomeType = some_func()
+Or by using a type comment in code that must support legacy Python versions.
+a = some_func() # type: SomeType
+Type annotations improve the readability and maintainability of your code. The +type checker will convert many runtime errors to build-time errors, and reduce +your ability to use Power Features.
+You will have to keep the type declarations up to date. +You might see type errors that you think are +valid code. Use of a +type checker +may reduce your ability to use Power Features.
+You are strongly encouraged to enable Python type analysis when updating code. +When adding or modifying public APIs, include type annotations and enable +checking via pytype in the build system. As static analysis is relatively new to +Python, we acknowledge that undesired side-effects (such as +wrongly +inferred types) may prevent adoption by some projects. In those situations, +authors are encouraged to add a comment with a TODO or link to a bug describing +the issue(s) currently preventing type annotation adoption in the BUILD file or +in the code itself as appropriate.
+Do not terminate your lines with semicolons, and do not use semicolons to put +two statements on the same line.
+Maximum line length is 120 characters.
+Explicit exceptions to the 120 character limit:
+# pylint: disable=invalid-name)Do not use backslash line continuation except for with statements requiring
+three or more context managers.
Make use of Python's +implicit line joining inside parentheses, brackets and braces. +If necessary, you can add an extra pair of parentheses around an expression.
+Yes: foo_bar(self, width, height, color='black', design=None, x='foo',
+ emphasis=None, highlight=0)
+
+ if (width == 0 and height == 0 and
+ color == 'red' and emphasis == 'strong'):
+When a literal string won't fit on a single line, use parentheses for implicit +line joining.
+x = ('This will build a very long long '
+ 'long long long long long long string')
+Within comments, put long URLs on their own line if necessary.
+Yes: # See details at
+ # http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html
+No: # See details at
+ # http://www.example.com/us/developer/documentation/api/content/\
+ # v2.0/csv_file_name_extension_full_specification.html
+It is permissible to use backslash continuation when defining a with statement
+whose expressions span three or more lines. For two lines of expressions, use a
+nested with statement:
Yes: with very_long_first_expression_function() as spam, \
+ very_long_second_expression_function() as beans, \
+ third_thing() as eggs:
+ place_order(eggs, beans, spam, beans)
+No: with VeryLongFirstExpressionFunction() as spam, \
+ VeryLongSecondExpressionFunction() as beans:
+ PlaceOrder(eggs, beans, spam, beans)
+Yes: with very_long_first_expression_function() as spam:
+ with very_long_second_expression_function() as beans:
+ place_order(beans, spam)
+Make note of the indentation of the elements in the line continuation examples +above; see the indentation section for explanation.
+In all other cases where a line exceeds 80 characters, and the +yapf +auto-formatter does not help bring the line below the limit, the line is allowed +to exceed this maximum.
+Use parentheses sparingly.
+It is fine, though not required, to use parentheses around tuples. Do not use +them in return statements or conditional statements unless using parentheses for +implied line continuation or to indicate a tuple.
+Yes: if foo:
+ bar()
+ while x:
+ x = bar()
+ if x and y:
+ bar()
+ if not x:
+ bar()
+ # For a 1 item tuple the ()s are more visually obvious than the comma.
+ onesie = (foo,)
+ return foo
+ return spam, beans
+ return (spam, beans)
+ for (x, y) in dict.items(): ...
+No: if (x):
+ bar()
+ if not(x):
+ bar()
+ return (foo)
+Indent your code blocks with 4 spaces.
+Never use tabs or mix tabs and spaces. In cases of implied line continuation, +you should align wrapped elements either vertically, as per the examples in the +line length section; or using a hanging indent of 4 spaces, +in which case there should be nothing after the open parenthesis or bracket on +the first line.
+Yes: # Aligned with opening delimiter
+ foo = long_function_name(var_one, var_two,
+ var_three, var_four)
+ meal = (spam,
+ beans)
+
+ # Aligned with opening delimiter in a dictionary
+ foo = {
+ long_dictionary_key: value1 +
+ value2,
+ ...
+ }
+
+ # 4-space hanging indent; nothing on first line
+ foo = long_function_name(
+ var_one, var_two, var_three,
+ var_four)
+ meal = (
+ spam,
+ beans)
+
+ # 4-space hanging indent in a dictionary
+ foo = {
+ long_dictionary_key:
+ long_dictionary_value,
+ ...
+ }
+No: # Stuff on first line forbidden
+ foo = long_function_name(var_one, var_two,
+ var_three, var_four)
+ meal = (spam,
+ beans)
+
+ # 2-space hanging indent forbidden
+ foo = long_function_name(
+ var_one, var_two, var_three,
+ var_four)
+
+ # No hanging indent in a dictionary
+ foo = {
+ long_dictionary_key:
+ long_dictionary_value,
+ ...
+ }
+Trailing commas in sequences of items are recommended only when the closing
+container token ], ), or } does not appear on the same line as the final
+element. The presence of a trailing comma is also used as a hint to our Python
+code auto-formatter YAPF to direct it to auto-format the container
+of items to one item per line when the , after the final element is present.
Yes: golomb3 = [0, 1, 3]
+Yes: golomb4 = [
+ 0,
+ 1,
+ 4,
+ 6,
+ ]
+No: golomb4 = [
+ 0,
+ 1,
+ 4,
+ 6
+ ]
+Two blank lines between top-level definitions, be they function or class
+definitions. One blank line between method definitions and between the class
+line and the first method. No blank line following a def line. Use single
+blank lines as you judge appropriate within functions or methods.
Follow standard typographic rules for the use of spaces around punctuation.
+No whitespace inside parentheses, brackets or braces.
+Yes: spam(ham[1], {eggs: 2}, [])
+No: spam( ham[ 1 ], { eggs: 2 }, [ ] )
+No whitespace before a comma, semicolon, or colon. Do use whitespace after a +comma, semicolon, or colon, except at the end of the line.
+Yes: if x == 4:
+ print(x, y)
+ x, y = y, x
+No: if x == 4 :
+ print(x , y)
+ x , y = y , x
+No whitespace before the open paren/bracket that starts an argument list, +indexing or slicing.
+Yes: spam(1)
+No: spam (1)
+Yes: dict['key'] = list[index]
+No: dict ['key'] = list [index]
+No trailing whitespace.
+Surround binary operators with a single space on either side for assignment
+(=), comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not), and
+Booleans (and, or, not). Use your better judgment for the insertion of spaces
+around arithmetic operators (+, -, *, /, //, %, **, @).
Yes: x == 1
+No: x<1
+Never use spaces around = when passing keyword arguments or defining a default
+parameter value, with one exception:
+when a type annotation is present, do use spaces
+around the = for the default parameter value.
Yes: def complex(real, imag=0.0): return Magic(r=real, i=imag)
+Yes: def complex(real, imag: float = 0.0): return Magic(r=real, i=imag)
+No: def complex(real, imag = 0.0): return Magic(r = real, i = imag)
+No: def complex(real, imag: float=0.0): return Magic(r = real, i = imag)
+Don't use spaces to vertically align tokens on consecutive lines, since it
+becomes a maintenance burden (applies to :, #, =, etc.):
Yes:
+ foo = 1000 # comment
+ long_name = 2 # comment that should not be aligned
+
+ dictionary = {
+ 'foo': 1,
+ 'long_name': 2,
+ }
+No:
+ foo = 1000 # comment
+ long_name = 2 # comment that should not be aligned
+
+ dictionary = {
+ 'foo' : 1,
+ 'long_name': 2,
+ }
+Most .py files do not need to start with a #! line. Start the main file of a
+program with
+#!/usr/bin/env python3 (to support virtualenvs) or #!/usr/bin/python3 per
+PEP-394.
This line is used by the kernel to find the Python interpreter, but is ignored by Python when importing modules. It is only necessary on a file intended to be executed directly.
+Be sure to use the right style for module, function, method docstrings and +inline comments.
+Python uses docstrings to document code. A docstring is a string that is the
+first statement in a package, module, class or function. These strings can be
+extracted automatically through the __doc__ member of the object and are used
+by pydoc.
+(Try running pydoc on your module to see how it looks.) Always use the three
+double-quote """ format for docstrings (per
+PEP 257).
+A docstring should be organized as a summary line (one physical line not
+exceeding 80 characters) terminated by a period, question mark, or exclamation
+point. When writing more (encouraged), this must be followed by a blank line,
+followed by the rest of the docstring starting at the same cursor position as
+the first quote of the first line. There are more formatting guidelines for
+docstrings below.
Every file should contain license boilerplate. Choose the appropriate boilerplate for the license used by the project (for example, Apache 2.0, BSD, LGPL, GPL)
+Files should start with a docstring describing the contents and usage of the +module.
+"""A one line summary of the module or program, terminated by a period.
+
+Leave one blank line. The rest of this docstring should contain an
+overall description of the module or program. Optionally, it may also
+contain a brief description of exported classes and functions and/or usage
+examples.
+
+ Typical usage example:
+
+ foo = ClassFoo()
+ bar = foo.FunctionBar()
+"""
+In this section, "function" means a method, function, or generator.
+A function must have a docstring, unless it meets all of the following criteria:
+A docstring should give enough information to write a call to the function
+without reading the function's code. The docstring should be descriptive-style
+("""Fetches rows from a Bigtable.""") rather than imperative-style ("""Fetch
+rows from a Bigtable."""), except for @property data descriptors, which
+should use the same style as attributes. A docstring
+should describe the function's calling syntax and its semantics, not its
+implementation. For tricky code, comments alongside the code are more
+appropriate than using docstrings.
A method that overrides a method from a base class may have a simple docstring
+sending the reader to its overridden method's docstring, such as """See base
+class.""". The rationale is that there is no need to repeat in many places
+documentation that is already present in the base method's docstring. However,
+if the overriding method's behavior is substantially different from the
+overridden method, or details need to be provided (e.g., documenting additional
+side effects), a docstring with at least those differences is required on the
+overriding method.
Certain aspects of a function should be documented in special sections, listed +below. Each section begins with a heading line, which ends with a colon. All +sections other than the heading should maintain a hanging indent of two or four +spaces (be consistent within a file). These sections can be omitted in cases +where the function's name and signature are informative enough that it can be +aptly described using a one-line docstring.
+Args:
+: List each parameter by name. A description should follow the name, and be
+ separated by a colon followed by either a space or newline. If the
+ description is too long to fit on a single 80-character line, use a hanging
+ indent of 2 or 4 spaces more than the parameter name (be consistent with the
+ rest of the docstrings in the file). The description should include required
+ type(s) if the code does not contain a corresponding type annotation. If a
+ function accepts *foo (variable length argument lists) and/or **bar
+ (arbitrary keyword arguments), they should be listed as *foo and **bar.
Returns: (or Yields: for generators)
+: Describe the type and semantics of the return value. If the function only
+ returns None, this section is not required. It may also be omitted if the
+ docstring starts with Returns or Yields (e.g. """Returns row from Bigtable
+ as a tuple of strings.""") and the opening sentence is sufficient to
+ describe return value.
Raises: +: List all exceptions that are relevant to the interface followed by a + description. Use a similar exception name + colon + space or newline and + hanging indent style as described in Args:. You should not document + exceptions that get raised if the API specified in the docstring is violated + (because this would paradoxically make behavior under violation of the API + part of the API).
+def fetch_smalltable_rows(table_handle: smalltable.Table,
+ keys: Sequence[Union[bytes, str]],
+ require_all_keys: bool = False,
+) -> Mapping[bytes, Tuple[str]]:
+ """Fetches rows from a Smalltable.
+
+ Retrieves rows pertaining to the given keys from the Table instance
+ represented by table_handle. String keys will be UTF-8 encoded.
+
+ Args:
+ table_handle: An open smalltable.Table instance.
+ keys: A sequence of strings representing the key of each table
+ row to fetch. String keys will be UTF-8 encoded.
+ require_all_keys: Optional; If require_all_keys is True only
+ rows with values set for all keys will be returned.
+
+ Returns:
+ A dict mapping keys to the corresponding table row data
+ fetched. Each row is represented as a tuple of strings. For
+ example:
+
+ {b'Serak': ('Rigel VII', 'Preparer'),
+ b'Zim': ('Irk', 'Invader'),
+ b'Lrrr': ('Omicron Persei 8', 'Emperor')}
+
+ Returned keys are always bytes. If a key from the keys argument is
+ missing from the dictionary, then that row was not found in the
+ table (and require_all_keys must have been False).
+
+ Raises:
+ IOError: An error occurred accessing the smalltable.
+ """
+Similarly, this variation on Args: with a line break is also allowed:
def fetch_smalltable_rows(table_handle: smalltable.Table,
+ keys: Sequence[Union[bytes, str]],
+ require_all_keys: bool = False,
+) -> Mapping[bytes, Tuple[str]]:
+ """Fetches rows from a Smalltable.
+
+ Retrieves rows pertaining to the given keys from the Table instance
+ represented by table_handle. String keys will be UTF-8 encoded.
+
+ Args:
+ table_handle:
+ An open smalltable.Table instance.
+ keys:
+ A sequence of strings representing the key of each table row to
+ fetch. String keys will be UTF-8 encoded.
+ require_all_keys:
+ Optional; If require_all_keys is True only rows with values set
+ for all keys will be returned.
+
+ Returns:
+ A dict mapping keys to the corresponding table row data
+ fetched. Each row is represented as a tuple of strings. For
+ example:
+
+ {b'Serak': ('Rigel VII', 'Preparer'),
+ b'Zim': ('Irk', 'Invader'),
+ b'Lrrr': ('Omicron Persei 8', 'Emperor')}
+
+ Returned keys are always bytes. If a key from the keys argument is
+ missing from the dictionary, then that row was not found in the
+ table (and require_all_keys must have been False).
+
+ Raises:
+ IOError: An error occurred accessing the smalltable.
+ """
+Classes should have a docstring below the class definition describing the class.
+If your class has public attributes, they should be documented here in an
+Attributes section and follow the same formatting as a
+function's Args section.
class SampleClass:
+ """Summary of class here.
+
+ Longer class information....
+ Longer class information....
+
+ Attributes:
+ likes_spam: A boolean indicating if we like SPAM or not.
+ eggs: An integer count of the eggs we have laid.
+ """
+
+ def __init__(self, likes_spam=False):
+ """Inits SampleClass with blah."""
+ self.likes_spam = likes_spam
+ self.eggs = 0
+
+ def public_method(self):
+ """Performs operation blah."""
+The final place to have comments is in tricky parts of the code. If you're going +to have to explain it at the next code review, +you should comment it now. Complicated operations get a few lines of comments +before the operations commence. Non-obvious ones get comments at the end of the +line.
+# We use a weighted dictionary search to find out where i is in
+# the array. We extrapolate position based on the largest num
+# in the array and the array size and then do binary search to
+# get the exact number.
+
+if i & (i-1) == 0: # True if i is 0 or a power of 2.
+To improve legibility, these comments should start at least 2 spaces away from
+the code with the comment character #, followed by at least one space before
+the text of the comment itself.
On the other hand, never describe the code. Assume the person reading the code +knows Python (though not what you're trying to do) better than you do.
+# BAD COMMENT: Now go through the b array and make sure whenever i occurs
+# the next element is i+1
+Pay attention to punctuation, spelling, and grammar; it is easier to read +well-written comments than badly written ones.
+Comments should be as readable as narrative text, with proper capitalization and +punctuation. In many cases, complete sentences are more readable than sentence +fragments. Shorter comments, such as comments at the end of a line of code, can +sometimes be less formal, but you should be consistent with your style.
+Although it can be frustrating to have a code reviewer point out that you are +using a comma when you should be using a semicolon, it is very important that +source code maintain a high level of clarity and readability. Proper +punctuation, spelling, and grammar help with that goal.
+Use an
+f-string,
+the % operator, or the format method for formatting strings, even when the
+parameters are all strings. Use your best judgment to decide between + and %
+(or format) though. Do not use % or the format method for pure
+concatenation.
Yes: x = a + b
+ x = '%s, %s!' % (imperative, expletive)
+ x = '{}, {}'.format(first, second)
+ x = 'name: %s; score: %d' % (name, n)
+ x = 'name: {}; score: {}'.format(name, n)
+ x = f'name: {name}; score: {n}'
+No: x = '%s%s' % (a, b) # use + in this case
+ x = '{}{}'.format(a, b) # use + in this case
+ x = first + ', ' + second
+ x = 'name: ' + name + '; score: ' + str(n)
+Avoid using the + and += operators to accumulate a string within a loop. In
+some conditions, accumulating a string with addition can lead to quadratic
+rather than linear running time. Although common accumulations of this sort may
+be optimized on CPython, that is an implementation detail. The conditions under
+which an optimization applies are not easy to predict and may change. Instead,
+add each substring to a list and ''.join the list after the loop terminates,
+or write each substring to an io.StringIO buffer. These techniques
+consistently have amortized-linear run time complexity.
Yes: items = ['<table>']
+ for last_name, first_name in employee_list:
+ items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
+ items.append('</table>')
+ employee_table = ''.join(items)
+No: employee_table = '<table>'
+ for last_name, first_name in employee_list:
+ employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
+ employee_table += '</table>'
+Be consistent with your choice of string quote character within a file. Pick '
+or " and stick with it. It is okay to use the other quote character on a
+string to avoid the need to \\ escape within the string.
Yes:
+ Python('Why are you hiding your eyes?')
+ Gollum("I'm scared of lint errors.")
+ Narrator('"Good!" thought a happy Python reviewer.')
+No:
+ Python("Why are you hiding your eyes?")
+ Gollum('The lint. It burns. It burns us.')
+ Gollum("Always the great lint. Watching. Watching.")
+Prefer """ for multi-line strings rather than '''. Projects may choose to
+use ''' for all non-docstring multi-line strings if and only if they also use
+' for regular strings. Docstrings must use """ regardless.
Multi-line strings do not flow with the indentation of the rest of the program.
+If you need to avoid embedding extra space in the string, use either
+concatenated single-line strings or a multi-line string with
+textwrap.dedent()
+to remove the initial space on each line:
No:
+ long_string = """This is pretty ugly.
+Don't do this.
+"""
+ Yes:
+ long_string = """This is fine if your use case can accept
+ extraneous leading spaces."""
+ Yes:
+ long_string = ("And this is fine if you cannot accept\n" +
+ "extraneous leading spaces.")
+ Yes:
+ long_string = ("And this too is fine if you cannot accept\n"
+ "extraneous leading spaces.")
+ Yes:
+ import textwrap
+
+ long_string = textwrap.dedent("""\
+ This is also fine, because textwrap.dedent()
+ will collapse common leading spaces in each line.""")
+For logging functions that expect a pattern-string (with %-placeholders) as +their first argument: Always call them with a string literal (not an f-string!) +as their first argument with pattern-parameters as subsequent arguments. Some +logging implementations collect the unexpanded pattern-string as a queryable +field. It also prevents spending time rendering a message that no logger is +configured to output.
+ Yes:
+ import tensorflow as tf
+ logger = tf.get_logger()
+ logger.info('TensorFlow Version is: %s', tf.__version__)
+ Yes:
+ import os
+ from absl import logging
+
+ logging.info('Current $PAGER is: %s', os.getenv('PAGER', default=''))
+
+ homedir = os.getenv('HOME')
+ if homedir is None or not os.access(homedir, os.W_OK):
+ logging.error('Cannot write to home directory, $HOME=%r', homedir)
+ No:
+ import os
+ from absl import logging
+
+ logging.info('Current $PAGER is:')
+ logging.info(os.getenv('PAGER', default=''))
+
+ homedir = os.getenv('HOME')
+ if homedir is None or not os.access(homedir, os.W_OK):
+ logging.error(f'Cannot write to home directory, $HOME={homedir!r}')
+Error messages (such as: message strings on exceptions like ValueError, or
+messages shown to the user) should follow three guidelines:
The message needs to precisely match the actual error condition.
+Interpolated pieces need to always be clearly identifiable as such.
+They should allow simple automated processing (e.g. grepping).
+ Yes:
+ if not 0 <= p <= 1:
+ raise ValueError(f'Not a probability: {p!r}')
+
+ try:
+ os.rmdir(workdir)
+ except OSError as error:
+ logging.warning('Could not remove directory (reason: %r): %r',
+ error, workdir)
+ No:
+ if p < 0 or p > 1: # PROBLEM: also false for float('nan')!
+ raise ValueError(f'Not a probability: {p!r}')
+
+ try:
+ os.rmdir(workdir)
+ except OSError:
+ # PROBLEM: Message makes an assumption that might not be true:
+ # Deletion might have failed for some other reason, misleading
+ # whoever has to debug this.
+ logging.warning('Directory already was deleted: %s', workdir)
+
+ try:
+ os.rmdir(workdir)
+ except OSError:
+ # PROBLEM: The message is harder to grep for than necessary, and
+ # not universally non-confusing for all possible values of `workdir`.
+ # Imagine someone calling a library function with such code
+ # using a name such as workdir = 'deleted'. The warning would read:
+ # "The deleted directory could not be deleted."
+ logging.warning('The %s directory could not be deleted.', workdir)
+Explicitly close files and sockets when done with them.
+Leaving files, sockets or other file-like objects open unnecessarily has many +downsides:
+Furthermore, while files and sockets are automatically closed when the file +object is destructed, tying the lifetime of the file object to the state of the +file is poor practice:
+The preferred way to manage files is using the
+with statement:
with open("hello.txt") as hello_file:
+ for line in hello_file:
+ print(line)
+For file-like objects that do not support the with statement, use
+contextlib.closing():
import contextlib
+
+with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
+ for line in front_page:
+ print(line)
+TODO comments should not be merged to master, you can use them while developing your branch.
+++If the TODO is out of the scope of the Ticket a new one should be created.
+
+If it is in the scope of the ticket it should be implemented.
Imports should be on separate lines; there are
+exceptions for typing imports.
E.g.:
+Yes: import os
+ import sys
+ from typing import Mapping, Sequence
+No: import os, sys
+Imports are always put at the top of the file, just after any module comments +and docstrings and before module globals and constants. Imports should be +grouped from most generic to least generic:
+Python future import statements. For example:
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+See above for more information about those.
+Python standard library imports. For example:
+import sys
+third-party module + or package imports. For example:
+import tensorflow as tf
+Code repository + sub-package imports. For example:
+from otherproject.ai import mind
+Deprecated: application-specific imports that are part of the same + top level + sub-package as this file. For example:
+from myproject.backend.hgwells import time_machine
+You may find older Google Python Style code doing this, but it is no longer +required. New code is encouraged not to bother with this. Simply treat +application-specific sub-package imports the same as other sub-package +imports.
+Within each grouping, imports should be sorted lexicographically, ignoring case,
+according to each module's full package path (the path in from path import
+...). Code may optionally place a blank line between import sections.
import collections
+import queue
+import sys
+
+from absl import app
+from absl import flags
+import bs4
+import cryptography
+import tensorflow as tf
+
+from book.genres import scifi
+from myproject.backend import huxley
+from myproject.backend.hgwells import time_machine
+from myproject.backend.state_machine import main_loop
+from otherproject.ai import body
+from otherproject.ai import mind
+from otherproject.ai import soul
+
+# Older style code may have these imports down here instead:
+#from myproject.backend.hgwells import time_machine
+#from myproject.backend.state_machine import main_loop
+Generally only one statement per line.
+However, you may put the result of a test on the same line as the test only if
+the entire statement fits on one line. In particular, you can never do so with
+try/except since the try and except can't both fit on the same line, and
+you can only do so with an if if there is no else.
Yes:
+
+ if foo: bar(foo)
+No:
+
+ if foo: bar(foo)
+ else: baz(foo)
+
+ try: bar(foo)
+ except ValueError: baz(foo)
+
+ try:
+ bar(foo)
+ except ValueError: baz(foo)
+If an accessor function would be trivial, you should use public variables
+instead of accessor functions to avoid the extra cost of function calls in
+Python. When more functionality is added you can use property to keep the
+syntax consistent.
On the other hand, if access is more complex, or the cost of accessing the
+variable is significant, you should use function calls (following the
+Naming guidelines) such as get_foo() and set_foo(). If the
+past behavior allowed access through a property, do not bind the new accessor
+functions to the property. Any code still attempting to access the variable by
+the old method should break visibly so they are made aware of the change in
+complexity.
module_name, package_name, ClassName, method_name, ExceptionName,
+function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name,
+function_parameter_name, local_var_name.
Function names, variable names, and filenames should be descriptive; eschew +abbreviation. In particular, do not use abbreviations that are ambiguous or +unfamiliar to readers outside your project, and do not abbreviate by deleting +letters within a word.
+Always use a .py filename extension. Never use dashes.
single character names, except for specifically allowed cases:
+counters or iterators (e.g. i, j, k, v, et al.)
e as an exception identifier in try/except statements.f as a file handle in with statements
Please be mindful not to abuse single-character naming. Generally speaking,
+descriptiveness should be proportional to the name's scope of visibility.
+For example, i might be a fine name for 5-line code block but within
+multiple nested scopes, it is likely too vague.
dashes (-) in any package/module name
__double_leading_and_trailing_underscore__ names (reserved by Python)
offensive terms
+names that needlessly include the type of the variable (for example:
+ id_to_name_dict)
"Internal" means internal to a module, or protected or private within a + class.
+Prepending a single underscore (_) has some support for protecting module
+ variables and functions (linters will flag protected member access). While
+ prepending a double underscore (__ aka "dunder") to an instance variable
+ or method effectively makes the variable or method private to its class
+ (using name mangling); we discourage its use as it impacts readability and
+ testability, and isn't really private.
Place related classes and top-level functions together in a + module. + Unlike Java, there is no need to limit yourself to one class per module.
+Use CapWords for class names, but lower_with_under.py for module names.
+ Although there are some old modules named CapWords.py, this is now
+ discouraged because it's confusing when the module happens to be named after
+ a class. ("wait -- did I write import StringIO or from StringIO import
+ StringIO?")
Underscores may appear in unittest method names starting with test to
+ separate logical components of the name, even if those components use
+ CapWords. One possible pattern is test<MethodUnderTest>_<state>; for
+ example testPop_EmptyStack is okay. There is no One Correct Way to name
+ test methods.
Python filenames must have a .py extension and must not contain dashes (-).
+This allows them to be imported and unittested. If you want an executable to be
+accessible without the extension, use a symbolic link or a simple bash wrapper
+containing exec "$0.py" "$@".
| Type | +Public | +Internal | +
|---|---|---|
| Packages | +lower_with_under |
++ |
| Modules | +lower_with_under |
+_lower_with_under |
+
| Classes | +CapWords |
+_CapWords |
+
| Exceptions | +CapWords |
++ |
| Functions | +lower_with_under() |
+_lower_with_under() |
+
| Global/Class Constants | +CAPS_WITH_UNDER |
+_CAPS_WITH_UNDER |
+
| Global/Class Variables | +lower_with_under |
+_lower_with_under |
+
| Instance Variables | +lower_with_under |
+_lower_with_under(protected) |
+
| Method Names | +lower_with_under() |
+_lower_with_under()(protected) |
+
| Function/Method Parameters | +lower_with_under |
++ |
| Local Variables | +lower_with_under |
++ |
In Python, pydoc as well as unit tests require modules to be importable. If a
+file is meant to be used as an executable, its main functionality should be in a
+main() function, and your code should always check if __name__ == '__main__'
+before executing your main program, so that it is not executed when the module
+is imported.
When using absl, use app.run:
from absl import app
+...
+
+def main(argv):
+ # process non-flag arguments
+ ...
+
+if __name__ == '__main__':
+ app.run(main)
+Otherwise, use:
+def main():
+ ...
+
+if __name__ == '__main__':
+ main()
+All code at the top level will be executed when the module is imported. Be
+careful not to call functions, create objects, or perform other operations that
+should not be executed when the file is being pydoced.
Prefer small and focused functions.
+We recognize that long functions are sometimes appropriate, so no hard limit is +placed on function length. If a function exceeds about 40 lines, think about +whether it can be broken up without harming the structure of the program.
+Even if your long function works perfectly now, someone modifying it in a few +months may add new behavior. This could result in bugs that are hard to find. +Keeping your functions short and simple makes it easier for other people to read +and modify your code.
+You could find long and complicated functions when working with +some +code. Do not be intimidated by modifying existing code: if working with such a +function proves to be difficult, you find that errors are hard to debug, or you +want to use a piece of it in several different contexts, consider breaking up +the function into smaller and more manageable pieces.
+self, or cls if it is necessary for proper
+ type information. e.g., @classmethod def create(cls: Type[T]) -> T: return
+ cls()Any.Try to follow the existing indentation rules.
+After annotating, many function signatures will become "one parameter per line".
+def my_method(self,
+ first_var: int,
+ second_var: Foo,
+ third_var: Optional[Bar]) -> int:
+ ...
+Always prefer breaking between variables, and not, for example, between variable +names and type annotations. However, if everything fits on the same line, go for +it.
+def my_method(self, first_var: int) -> int:
+ ...
+If the combination of the function name, the last parameter, and the return type +is too long, indent by 4 in a new line.
+def my_method(
+ self, first_var: int) -> Tuple[MyLongType1, MyLongType1]:
+ ...
+When the return type does not fit on the same line as the last parameter, the
+preferred way is to indent the parameters by 4 on a new line and align the
+closing parenthesis with the def.
Yes:
+def my_method(
+ self, other_arg: Optional[MyLongType]
+) -> Dict[OtherLongType, MyLongType]:
+ ...
+pylint
+allows you to move the closing parenthesis to a new line and align with the
+opening one, but this is less readable.
No:
+def my_method(self,
+ other_arg: Optional[MyLongType]
+ ) -> Dict[OtherLongType, MyLongType]:
+ ...
+As in the examples above, prefer not to break types. However, sometimes they are +too long to be on a single line (try to keep sub-types unbroken).
+def my_method(
+ self,
+ first_var: Tuple[List[MyLongType1],
+ List[MyLongType2]],
+ second_var: List[Dict[
+ MyLongType3, MyLongType4]]) -> None:
+ ...
+If a single name and type is too long, consider using an +alias for the type. The last resort is to break after the +colon and indent by 4.
+Yes:
+def my_function(
+ long_variable_name:
+ long_module_name.LongTypeName,
+) -> None:
+ ...
+No:
+def my_function(
+ long_variable_name: long_module_name.
+ LongTypeName,
+) -> None:
+ ...
+If you need to use a class name from the same module that is not yet defined -- +for example, if you need the class inside the class declaration, or if you use a +class that is defined below -- use a string for the class name.
+class MyClass:
+
+ def __init__(self,
+ stack: List["MyClass"]) -> None:
+As per
+PEP-008, use
+spaces around the = only for arguments that have both a type annotation and
+a default value.
Yes:
+def func(a: int = 0) -> int:
+ ...
+No:
+def func(a:int=0) -> int:
+ ...
+In the Python type system, NoneType is a "first class" type, and for typing
+purposes, None is an alias for NoneType. If an argument can be None, it
+has to be declared! You can use Union, but if there is only one other type,
+use Optional.
Use explicit Optional instead of implicit Optional. Earlier versions of PEP
+484 allowed a: Text = None to be interpreted as a: Optional[Text] = None,
+but that is no longer the preferred behavior.
Yes:
+def func(a: Optional[Text], b: Optional[Text] = None) -> Text:
+ ...
+def multiple_nullable_union(a: Union[None, Text, int]) -> Text
+ ...
+No:
+def nullable_union(a: Union[None, Text]) -> Text:
+ ...
+def implicit_optional(a: Text = None) -> Text:
+ ...
+You can declare aliases of complex types. The name of an alias should be +CapWorded. If the alias is used only in this module, it should be _Private.
+For example, if the name of the module together with the name of the type is too +long:
+_ShortName = module_with_long_name.TypeWithLongName
+ComplexMap = Mapping[Text, List[Tuple[int, int]]]
+Other examples are complex nested types and multiple return variables from a +function (as a tuple).
+You can disable type checking on a line with the special comment # type:
+ignore.
pytype has a disable option for specific errors (similar to lint):
# pytype: disable=attribute-error
+If an internal variable has a type that is hard or impossible to infer, you can +specify its type in a couple ways.
+Type Comments:
+: Use a # type: comment on the end of the line
a = SomeUndecoratedFunction() # type: Foo
+Annotated Assignments +: Use a colon and type between the variable name and value, as with function + arguments.
+a: Foo = SomeUndecoratedFunction()
+Typed lists can only contain objects of a single type. Typed tuples can either +have a single repeated type or a set number of elements with different types. +The latter is commonly used as the return type from a function.
+a = [1, 2, 3] # type: List[int]
+b = (1, 2, 3) # type: Tuple[int, ...]
+c = (1, "2", 3.5) # type: Tuple[int, Text, float]
+The Python type system has
+generics. The factory
+function TypeVar is a common way to use them.
Example:
+from typing import List, TypeVar
+T = TypeVar("T")
+...
+def next(l: List[T]) -> T:
+ return l.pop()
+A TypeVar can be constrained:
+AddableType = TypeVar("AddableType", int, float, Text)
+def add(a: AddableType, b: AddableType) -> AddableType:
+ return a + b
+A common predefined type variable in the typing module is AnyStr. Use it for
+multiple annotations that can be bytes or unicode and must all be the same
+type.
from typing import AnyStr
+def check_length(x: AnyStr) -> AnyStr:
+ if len(x) <= 42:
+ return x
+ raise ValueError()
+The proper type for annotating strings depends on what versions of Python the +code is intended for.
+For Python 3 only code, prefer to use str. Text is also acceptable. Be
+consistent in using one or the other.
For Python 2 compatible code, use Text. In some rare cases, str may make
+sense; typically to aid compatibility when the return types aren't the same
+between the two Python versions. Avoid using unicode: it doesn't exist in
+Python 3.
The reason this discrepancy exists is because str means different things
+depending on the Python version.
No:
+def py2_code(x: str) -> unicode:
+ ...
+For code that deals with binary data, use bytes.
def deals_with_binary_data(x: bytes) -> bytes:
+ ...
+For Python 2 compatible code that processes text data (str or unicode in
+Python 2, str in Python 3), use Text. For Python 3 only code that process
+text data, prefer str.
from typing import Text
+...
+def py2_compatible(x: Text) -> Text:
+ ...
+def py3_only(x: str) -> str:
+ ...
+If the type can be either bytes or text, use Union, with the appropriate text
+type.
from typing import Text, Union
+...
+def py2_compatible(x: Union[bytes, Text]) -> Union[bytes, Text]:
+ ...
+def py3_only(x: Union[bytes, str]) -> Union[bytes, str]:
+ ...
+If all the string types of a function are always the same, for example if the +return type is the same as the argument type in the code above, use +AnyStr.
+Writing it like this will simplify the process of porting the code to Python 3.
+For classes from the typing module, always import the class itself. You are
+explicitly allowed to import multiple specific classes on one line from the
+typing module. Ex:
from typing import Any, Dict, Optional
+Given that this way of importing from typing adds items to the local
+namespace, any names in typing should be treated similarly to keywords, and
+not be defined in your Python code, typed or not. If there is a collision
+between a type and an existing name in a module, import it using import x as
+y.
from typing import Any as AnyType
+Use conditional imports only in exceptional cases where the additional imports +needed for type checking must be avoided at runtime. This pattern is +discouraged; alternatives such as refactoring the code to allow top level +imports should be preferred.
+Imports that are needed only for type annotations can be placed within an if
+TYPE_CHECKING: block.
import typing
+if typing.TYPE_CHECKING:
+ import sketch
+def f(x: "sketch.Sketch"): ...
+Circular dependencies that are caused by typing are code smells. Such code is a +good candidate for refactoring. Although technically it is possible to keep +circular dependencies, various build systems will not let you do so +because each module has to depend on the other.
+Replace modules that create circular dependency imports with Any. Set an
+alias with a meaningful name, and use the real type name from
+this module (any attribute of Any is Any). Alias definitions should be separated
+from the last import by one line.
from typing import Any
+
+some_mod = Any # some_mod.py imports this module.
+...
+
+def my_method(self, var: "some_mod.SomeType") -> None:
+ ...
+When annotating, prefer to specify type parameters for generic types; otherwise,
+the generics' parameters will be assumed to be Any.
def get_names(employee_ids: List[int]) -> Dict[int, Any]:
+ ...
+# These are both interpreted as get_names(employee_ids: List[Any]) -> Dict[Any, Any]
+def get_names(employee_ids: list) -> Dict:
+ ...
+
+def get_names(employee_ids: List) -> Dict:
+ ...
+If the best type parameter for a generic is Any, make it explicit, but
+remember that in many cases TypeVar might be more
+appropriate:
def get_names(employee_ids: List[Any]) -> Dict[Any, Text]:
+ """Returns a mapping from employee ID to employee name for given IDs."""
+T = TypeVar('T')
+def get_names(employee_ids: List[T]) -> Dict[T, Text]:
+ """Returns a mapping from employee ID to employee name for given IDs."""
+BE CONSISTENT.
+If you're editing code, take a few minutes to look at the code around you and +determine its style. If they use spaces around all their arithmetic operators, +you should too. If their comments have little boxes of hash marks around them, +make your comments have little boxes of hash marks around them too.
+The point of having style guidelines is to have a common vocabulary of coding so +people can concentrate on what you're saying rather than on how you're saying +it. We present global style rules here so people know the vocabulary, but local +style is also important. If code you add to a file looks drastically different +from the existing code around it, it throws readers out of their rhythm when +they go to read it. Avoid this.
+ + + + + + +Available in the shell
+import this
For the ImageField to work it's necessary to have the Pillow library installed.
+The setup of the model containing the image field can be done in the following way.
+class BookInformation(models.Model):
+ #...
+ cover_image = models.ImageField(upload_to="covers/", null=True)
+ #...
+upload_to option corresponds to the path in which the images are stored/saved in the media folder.
+In this case the path will be .../library_project/media/covers/.
+The easiest way to generate mock images data with factory boy is with the factory.django.ImageField() method just like the example below.
class BookInformationFactory(factory.django.DjangoModelFactory):
+ #...
+ cover_image = factory.django.ImageField(color=factory.Faker("color"))
+ #...
+Note that the color option can be Hardcoded with something like color="blue" and that will result in the
+generation of images with the color blue. In this case, factory.Faker("color") will generate random color names
+which will result in the generation of images with diferent colors for each cover_image.
Considering the previous factory, here is the correspondent serializer for POST and PUT requests.
+class BookInformationCreateUpdateSerializer(serializers.ModelSerializer):
+ cover_image = serializers.ImageField(required=False, allow_empty_file=True)
+
+ class Meta:
+ model = BookInformation
+ fields = [
+ #... ,
+ "cover_image",
+ #... ,
+ ]
+class BookInformationViewset(viewsets.ModelViewSet):
+ queryset = BookInformation.objects.all()
+ serializer_class = BookInformationCreateUpdateSerializer
+To test the views that involve the cover_image attribute you can simply generate an image based on the
+implemented factory for the cover image just like the example below.
from PIL import Image
+import shutil
+from ..fixtures import REMOVE_MEDIA_FILES_PATH
+
+class BookCreateViewTestCase(APITestCase):
+ def setUp(self):
+ #...
+ self.data = {
+ "cover_image": #...
+ #...
+ }
+
+ def tearDown(self):
+ shutil.rmtree(REMOVE_MEDIA_FILES_PATH)
+
+ def test_create_with_image(self):
+ cover = BookInformationFactory.create().cover_image
+
+ self.data["cover_image"] = cover
+ res = self.client.post(self.url, self.data, format="multipart")
+
+ self.assertEqual(res.status_code, status.HTTP_201_CREATED)
+ self.assertEqual(
+ BookInformation.objects.first().cover_image.read(), BookInformation.objects.last().cover_image.read()
+ )
+
+ def test_create_with_image_failure(self):
+ self.data["cover_image"] = Image.new(mode="RGB", size=(20, 20))
+
+ res = self.client.post(self.url, self.data, format="multipart")
+
+ self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
+It is crucial that the format option in set to "multipart" for the test to work.
+It is also important to have the tearDown method implemented in order to delete the images
+saved on the folder refered previously (.../library_project/media/covers/).
Other way of implementing a view test that includes an ImageField is with the SimpleUploadedFile() method.
The code below will generate an Image with the PIL Library, save it in a temporary file,
+process it with SimpleUploadedFile() method and then make the POST request.
In this case the tearDown method still needs to be implemented, since the POST +request will save the image in the media folder.
+from django.core.files.uploadedfile import SimpleUploadedFile
+from io import BytesIO
+from PIL import Image
+import shutil
+
+class BookCreateViewTestCase(APITestCase):
+ def setUp(self):
+ self.data = {
+ "cover_image": #...
+ #...
+ }
+
+ def tearDown(self):
+ shutil.rmtree(REMOVE_MEDIA_FILES_PATH)
+
+ def test_create_with_image(self):
+ base_image = Image.new(mode="RGB", size=(20, 20))
+
+ tempFile = BytesIO()
+ base_image.save(tempFile, format="JPEG")
+ tempFile.seek(0)
+
+ with tempFile as temp:
+ read_image = SimpleUploadedFile("sample.jpg", temp.read(), content_type="image/jpeg")
+ self.data["cover_image"] = read_image
+
+ res = self.client.post(self.url, self.data, format="multipart")
+ read_image.seek(0)
+
+ self.assertEqual(res.status_code, status.HTTP_201_CREATED)
+ self.assertEqual(BookInformation.objects.first().cover_image.read(), read_image.read())
+Here you will find all the resources you need to get you started in your journey in the world of Python, as well as +more advanced topics to explore anytime you want to expand your knowledge.
+You are also welcome to add new resources that you find interesting as well as fix any errors that you might find along +the way.
+
Subfield of Artificial Intelligence
+Systems to automatically learn and improve from experience, without being + explicitly programmed
+Algorithms (models) that can interpret and learn from complex data, + identify patterns, and make predictions or decisions based on it
+Usage: disease identification, financial projections, image recognition, + speech recognition, natural language processing, fraud detection, etc.
+
+
flowchart LR
+ subgraph Traditional Programming
+ data-->Machine
+ rules-->Machine
+ Machine-->output
+ end
+
+flowchart LR
+ subgraph Machine Learning
+ data-->Machine
+ output-->Machine
+ Machine-->model
+ end
+
+There are several machine learning algorithms that enables to build complex models. +These algorithms can be grouped into a certain category depending on its learning process.
+Supervised Learning: uses labeled data (expected output already known) to train the models. + The learning process finds the best way to map the inputs to the respective outputs.
+Unsupervised Learning: uses unlabeled data (doesn't include an output variable) to train the models. + The model discovers patterns and features in the input data.
+Semi-Supervised Learning: mix between supervised and unsupervised learning. + Only some of the ouput is known.
+Reinforcement Learning: follows trial and error to get the desired result. + Trains the machine to take the most suitable action at a given moment, and it learns from + the rewards.
+flowchart BT
+ A[Supervised Learning]---E[Machine Learning]
+ B[Unsupervised Learning]---E[Machine Learning]
+ C[Semi-Supervised Learning]---E[Machine Learning]
+ D[Reinforcement Learning]---E[Machine Learning]
+ F[Classification]---A[Supervised Learning]
+ G[Regression]---A[Supervised Learning]
+ H[Clustering]---B[Unsupervised Learning]
+Within the same machine learning, we can also categorize the different problems, +depending on what the machine learning algorithm is trying to predict.
+
+
+
+
The Machine Learning lifecycle involves several steps. This is not characterized by +being a linear process since it is common to jump to previous steps in the process, +based on conclusions drawn in future phases. The major steps are the following:
+Gathering data - identify the different data sources, data collection, data integration
+Data pre-processing - cleaning and converting raw data into a useable format + (Ex: missing values, duplicate records, invalid data, noise)
+Data Analysis - study the relationships contained in the different variables, + with vision in which could be a suitable model
+Model Selection - train and evaluate several machine learning algorithms/pipelines
+Test Model - final evaluation of the candidate model in a different dataset
+Deployment - deploy the model in the real world system! So it can be consumed by other applications
+
+
Machine Learning algorithms can be classified into different families, +depending on nature of the learning process. The four major families are the following:
+Regression, along with classification, are the most common machine learning techniques. +The difference between them is that in regression the algorithms are used to predict +continuous outcome. Regression machine learning models live in the group of +supervised learning where the output variable (dependent variable) is known, and +numeric. The goal is to understand and model the relationship between the +dependent variable (what we want to predict), and one or more independent variables.
+flowchart BT
+ A[Supervised Learning]---E[Machine Learning]
+ B[Unsupervised Learning]---E[Machine Learning]
+ C[Semi-Supervised Learning]---E[Machine Learning]
+ D[Reinforcement Learning]---E[Machine Learning]
+ F[Classification]---A[Supervised Learning]
+ G[Regression]---A[Supervised Learning]
+ H[Clustering]---B[Unsupervised Learning]
+ style G stroke:#f66,stroke-width:4px
+Statistical method used to model the relationship between a dependent variable and + one or more independent variables.
+Assumes that the relationship between the dependent variable and the + independent variable(s) is linear.
+Training: find the line of best fit that minimizes the sum of +squared differences between the predicted values and the actual values of the +dependent variable.
+The equation of the hyperplane (n independent variables) is given by:
+y = b0 + b1X1 + b2X2 + ... + bn*Xn
+In the simplest form (only one independent variable), the equation is the same +as the straight line equation.
+y = b0 + b1X1
+
+
Before using linear regression model, make sure that the data follow these assumptions:
+The variables should be measured at a continuous level.
+Relationship between the dependent variable and the independent variable(s) is linear + (scatter plot to visualize).
+The observations and variables should be independent of each other.
+Your data should have no significant outliers.
+The residuals (errors) of the best-fit regression line follow normal distribution.
+Polynomial Regression is very similar to Linear Regression. The only difference is that +it transforms the input data to include non-linear terms. It is described +by a degree, which is the highest power computed from the original input. The amount +of additional terms will consequently depend on the degree.
+A dataset with a single feature X
+X = [
+ [X1],
+ [X2],
+ [X3],
+]
+using a polynomial of second degree would be transformed to:
+X = [
+ [X1, X1²],
+ [X2, X2²],
+ [X3, X3²],
+]
+
+
The training process would be exactly the same: find the coefficients +for each of the features that minimizes the square error. In the described example, +since we would have 2 coefficients instead of 1, it would define a parable instead of +a straight line.
+Key factors to consider:
+Before deploying a model to be consumed by several applications, +we need to know what is the expected performance that it will achieve. +After training a model using the collected and transformed dataset, +concrete metrics have to be calculated so we can have a vision of +how well the algorithm can model the data that we have on hand.
+Depending on the type of the problem (supervised, unsupervised, +classification, regression), there are different metrics +to evaluate. In supervised learning, we have available target values +that we can use to compare to what was predicted by the model. +However, the metrics are different between classification and regression problems +since in the first one the target variable is not continuous.
+Another topic worth mentioning, that can lead to misleading conclusions, +is what data should be used to evaluate. If we train +the model with the entire dataset, make predictions over the same dataset, +and compare these predictions with the actual values, we are not +verifying how the model behaves with unseen data samples. That's why +before speaking about evaluation metrics, we have to jump into +data split strategies, to be able to separate the data into +different sets depending on their use.
+A data split strategy is a way of splitting the data +into different subsets, so each subset is used with a different goal. +This will prevent us from using the entire dataset for training, tuning +, and evaluating which can give false conclusions.
+The most used strategies are:
+The idea behind the train-test split, as the name suggests, is to split +the entire dataset in different sets, usually the following:
+
+
Cross-validation, and leave one out are strategies used usually when +the dataset size is not huge, since they are computationally expensive. +The idea is to divide the entire dataset into K folds. The model +will be trained using N - 1 folds and evaluated on the remaining fold. +These will be performed K (number of folds) times, so the advantage is that +the data coverage will be higher (more reliable evaluation).
+
+
Leave one out is usually applied on small datasets. It is a particular case +of cross-validation, it is equivalent to using cross-validation with +N folds, given that N is the number of samples in the dataset. +This means that the model will be trained and evaluated N times, +and the prediction for evaluation will be performed on a single +sample at a time.
+It is worth mentioning two additional topics:
+temporal data: when dealing with temporal data, it is + important to sort before applying the split, because + it is considered "cheating" if the sort operation + is not applied. Imagine having past and future samples + in the training set of a sample in the test set. This + would not reflect the real usage of the model;
+stratification: in classification problems, it is important + to maintain the class distribution along the different sets, + so we can avoid having, for example, a test set with samples + of a single class.
+Evaluation metrics can be divided into regression and classification +since the output is different depending on the type of the problem. +However, the goal of these metrics is to evaluate how well the model +can make predictions. It receives as input the actuals (y) and +the predicted values for the same samples (ŷ).
+In regression, the actuals and the predicted values are continuous. +The following metrics are usually used to evaluate this kind of models:
+MAE is the easiest metric to interpret. It represents how much +the predicted value deviates from the actual value. n is the number +of samples (size of y and ŷ).
+MAE = (1/n) * Σ|yi - ŷi|
MSE is very similar to MAE. The difference is that it is squared instead +of using the absolute difference. This will penalize +higher errors. It is usually used as a loss function in some of the +models (i.e Neural Network).
+MSE = (1/n) * Σ(yi - ŷi)²
RMSE is the root of MSE. This facilitates the interpretation +since it converts the unit from squared to the original unit.
+RMSE = sqrt((1/n) * Σ(yi - ŷi)²)
R2 is a metric that represents how much variance of the data +was successfully modeled. It indicates how well the model +could represent the data and can establish a relationship +between the independent variables and the dependent variable.
+SSres is the residual sum of squares, and SStot is the +total variance contained in the target output y. This metric is +within the range of 0.0 to 1.0, being 1.0 a perfect model, able to +represent the entire variance. It can also have negative values +in cases where there is a terrible relationship between the +target and independent variables.
+R2 = 1 - (SSres / SStot)
Visualization sometimes can help taking conclusions about the +obtained results. The listed metrics in the previous section +are good to have a clear and specific evaluation of the model. +However, some useful plots can be visualized to +complement the achieved numeric results.
+The first plot is the actuals vs predicted values. +Ideally, all the points should be laid in the diagonal line, +which represents that the predicted value is equal to the +actual value. This plot gives an overall idea of the +achieved predictions and how far they are from the actual value.
+
+
The second one is similar, however, the residual value +substitutes one of the previous variables (actuals or predictions). +Ideally, the points should be near 0.0 which means no error. +The goal of this plot is to conclude the error for each +range of values and visualize possible trends that the errors +may show. This indicates that there is some pattern contained in +the data that the model was not able to cover.
+
+
The last one is the histogram of residuals. This allows the +conclusion of where are most errors contained. Additionally, +it is also important to conclude the distribution of the errors. +For example, a Linear Regression assumes that the errors are normally +distributed.
+
+
This notebook is intended to introduce the problem of regression. It includes familiarization with a linear and polynomial regression model, as well as evaluation techniques and metrics.
+https://docs.google.com/presentation/d/1VCNsLJbL_c8IM30P4sj0hayBbk_j8_OVlyoMnHtIX54/edit?usp=sharing
+ +In this first part, the goal is to generate data that is friendly to visualize and understand. For this purpose, the make_regression function is used, in order to generate instances (independent variables) associated with a dependent variable (what is intended to be predicted). Additionally, we visualize the generated data in order to see this same relationship.
https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_regression.html
+https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html
+ +from sklearn.datasets import make_regression
+
+X, y = make_regression(n_samples=100, n_features=1, noise=1, random_state=0)
+
+X[:5], y[:5]
+(array([[-0.35955316], + [ 0.97663904], + [ 0.40234164], + [-0.81314628], + [-0.88778575]]), + array([-15.71144661, 39.38978203, 16.50379768, -32.65326103, + -37.43638625]))+
import matplotlib.pyplot as plt
+
+plt.scatter(X.flatten(), y, alpha=0.7)
+plt.title("Independent/Dependent Variables Relationship")
+plt.xlabel("X")
+plt.ylabel("Y")
+Text(0, 0.5, 'Y')+
from sklearn.linear_model import LinearRegression
+
+model = LinearRegression()
+model.fit(X, y)
+
+model.coef_, model.intercept_
+(array([42.4088974]), -0.081418182703072)+
y_pred = model.predict(X)
+
+plt.scatter(X.flatten(), y, alpha=0.7)
+plt.title("Line Generated in the Training Process")
+plt.xlabel("X")
+plt.ylabel("Y")
+
+plt.plot(X, y_pred, color="tab:orange")
+[<matplotlib.lines.Line2D at 0x7f09493d3430>]+
The data is usually divided into different subsets, each of which has its own particular purpose: training the model, fine-tuning possible hyper-parameters, testing and evaluating the model. There are several strategies to do this: random split, cross validation and leave one out. In this part, the goal is to apply this same division in the different forms for the purposes of illustration.
+https://scikit-learn.org/stable/modules/cross_validation.html
+ +from sklearn.model_selection import train_test_split
+
+X_train, X_test, y_train, y_test = train_test_split(
+ X, y, test_size=0.2, random_state=0
+)
+
+X_train.shape, X_test.shape
+((80, 1), (20, 1))+
from sklearn.model_selection import KFold
+
+kfold = KFold(n_splits=5)
+
+for i, (train_indexes, test_indexes) in enumerate(kfold.split(X)):
+ print(f"Fold {i}")
+ print(f"\t Test: {test_indexes}")
+Fold 0 + Test: [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19] +Fold 1 + Test: [20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39] +Fold 2 + Test: [40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59] +Fold 3 + Test: [60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79] +Fold 4 + Test: [80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99] ++
from sklearn.model_selection import LeaveOneOut
+
+loo = LeaveOneOut()
+for train_indexes, test_indexes in loo.split(X):
+ print(f"Test: {test_indexes}")
+
+ if test_indexes[0] > 19:
+ print("...")
+ break
+Test: [0] +Test: [1] +Test: [2] +Test: [3] +Test: [4] +Test: [5] +Test: [6] +Test: [7] +Test: [8] +Test: [9] +Test: [10] +Test: [11] +Test: [12] +Test: [13] +Test: [14] +Test: [15] +Test: [16] +Test: [17] +Test: [18] +Test: [19] +Test: [20] +... ++
What is the performance of the model? How good the model can make predictions? These are valid questions that have to be answer before deploying a model, and that are part of the model selection phase. To do so, clear metrics are calculated to evaluate the model to take conclusions about it. Some of the most used regression metrics include: Mean Absolute Error (MAE), Mean Square Error (MSE), Root Mean Square Error (RMSE), and R-Squared Score (R2). In this third part, we apply a cross validation strategy and the listed metrics are calculated.
+https://scikit-learn.org/stable/modules/model_evaluation.html#regression-metrics
+ +from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
+
+kfold = KFold(n_splits=5)
+
+for i, (train_indexes, test_indexes) in enumerate(kfold.split(X)):
+ X_train = X[train_indexes]
+ X_test = X[test_indexes]
+ y_train = y[train_indexes]
+ y_test = y[test_indexes]
+
+ model = LinearRegression()
+ model.fit(X_train, y_train)
+
+ y_pred = model.predict(X_test)
+ mae = mean_absolute_error(y_test, y_pred)
+ mse = mean_squared_error(y_test, y_pred)
+ r2 = r2_score(y_test, y_pred)
+ rmse = mse ** 0.5
+
+ print(f"Fold {i}")
+ print(f"\t MAE={mae}")
+ print(f"\t MSE={mse}")
+ print(f"\t R2={r2}")
+ print(f"\t RMSE={rmse}")
+Fold 0 + MAE=0.8621552298978855 + MSE=1.0962047425997308 + R2=0.9991740317272837 + RMSE=1.046997966855586 +Fold 1 + MAE=1.1204936504484526 + MSE=1.7660868415050117 + R2=0.9987579014443851 + RMSE=1.3289420008055324 +Fold 2 + MAE=1.0036298987412464 + MSE=1.5132931891492074 + R2=0.9993053057530135 + RMSE=1.2301598226040418 +Fold 3 + MAE=0.8524516012025206 + MSE=1.0506789465771977 + R2=0.9996546320170226 + RMSE=1.0250263150657146 +Fold 4 + MAE=0.703414249405674 + MSE=0.7691013896506624 + R2=0.9990726778559803 + RMSE=0.8769842584965037 ++
from sklearn.model_selection import cross_val_predict
+
+model = LinearRegression()
+y_pred = cross_val_predict(model, X, y, cv=5)
+
+mae = mean_absolute_error(y, y_pred)
+mse = mean_squared_error(y, y_pred)
+r2 = r2_score(y, y_pred)
+rmse = mse ** 0.5
+
+print(f"MAE={mae}")
+print(f"MSE={mse}")
+print(f"R2={r2}")
+print(f"RMSE={rmse}")
+MAE=0.9084289259391557 +MSE=1.2390730218963621 +R2=0.9993222148719967 +RMSE=1.1131365692925383 ++
Visualization can be useful and complementary to understand the results. In addition to calculated metrics, it can also be beneficial to observe the following plots: predicted vs actuals, residuals histogram, and actuals vs residuals.
+ +plt.scatter(y_pred, y, alpha=0.5)
+plt.title("Actuals vs Predicted")
+plt.xlabel("Predicted")
+plt.ylabel("Actual")
+plt.gca().axline((0, 0), slope=1, color="tab:gray", linestyle="--")
+<matplotlib.lines._AxLine at 0x7f094960b670>+
residuals = y - y_pred
+
+plt.hist(residuals)
+plt.title("Residuals Histogram")
+plt.xlabel("Residual")
+plt.ylabel("Count")
+Text(0, 0.5, 'Count')+
plt.scatter(y, residuals, alpha=0.5)
+plt.title("Residuals vs Actuals")
+plt.xlabel("Actual")
+plt.ylabel("Residual")
+plt.gca().axline((0, 0), slope=0, color="tab:gray", linestyle="--")
+<matplotlib.lines._AxLine at 0x7f09490a3a30>+
The relationship between the independent variables and the dependent variable is not always linear. In these cases, including polynomial terms may improve the performance. In this part, we will apply a polynomial regression and compare the results with was obtained in the previous sections.
+https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PolynomialFeatures.html
+ +from sklearn.preprocessing import PolynomialFeatures
+
+poly = PolynomialFeatures(degree=2, include_bias=False)
+X_poly = poly.fit_transform(X)
+y_squared = y ** 2
+
+X[:5], X_poly[:5]
+(array([[-0.35955316], + [ 0.97663904], + [ 0.40234164], + [-0.81314628], + [-0.88778575]]), + array([[-0.35955316, 0.12927848], + [ 0.97663904, 0.95382381], + [ 0.40234164, 0.1618788 ], + [-0.81314628, 0.66120688], + [-0.88778575, 0.78816353]]))+
linear_model = LinearRegression()
+linear_model.fit(X, y_squared)
+
+poly_model = LinearRegression()
+poly_model.fit(X_poly, y_squared)
+LinearRegression()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
LinearRegression()
import numpy as np
+
+y_pred_linear = linear_model.predict(X)
+y_pred_poly = poly_model.predict(X_poly)
+
+sorted_indexes = np.argsort(X.flatten())
+X_sorted = X.flatten()[sorted_indexes]
+
+plt.scatter(X_sorted, y_squared[sorted_indexes], alpha=0.5, label="actuals")
+plt.title("Linear vs Polynomial")
+plt.xlabel("X")
+plt.ylabel("Y")
+
+plt.plot(
+ X_sorted,
+ y_pred_linear[sorted_indexes],
+ "--",
+ color="tab:orange",
+ label="linear model",
+)
+plt.plot(
+ X_sorted,
+ y_pred_poly[sorted_indexes],
+ "--",
+ color="tab:green",
+ label="polynomial model",
+)
+plt.legend()
+<matplotlib.legend.Legend at 0x7f0948edee50>+
y_pred = cross_val_predict(linear_model, X, y_squared, cv=5)
+
+mae = mean_absolute_error(y_squared, y_pred)
+mse = mean_squared_error(y_squared, y_pred)
+r2 = r2_score(y_squared, y_pred)
+rmse = mse ** 0.5
+
+print("Linear Model Evaluation:")
+print(f"MAE={mae}")
+print(f"MSE={mse}")
+print(f"R2={r2}")
+print(f"RMSE={rmse}")
+Linear Model Evaluation: +MAE=1812.4334097918857 +MSE=5966835.355196196 +R2=-0.0863241551310745 +RMSE=2442.7106572814137 ++
y_pred = cross_val_predict(model, X_poly, y_squared, cv=5)
+
+mae = mean_absolute_error(y_squared, y_pred)
+mse = mean_squared_error(y_squared, y_pred)
+r2 = r2_score(y_squared, y_pred)
+rmse = mse ** 0.5
+
+print("Polynomial Model Evaluation:")
+print(f"MAE={mae}")
+print(f"MSE={mse}")
+print(f"R2={r2}")
+print(f"RMSE={rmse}")
+Polynomial Model Evaluation: +MAE=58.4698071861382 +MSE=7429.651964598892 +R2=0.9986473549355888 +RMSE=86.19542890779587 ++
czP2n{i2)qXg5{N&BWwx|La$wPk6V^qCCDPd?fx(mPYdnLPR~4!mpRXVg4?uoym#TQ$ z?XCi~c*_aGYtkHa+%F8DX*tS%C}Fb1A-?wyX3&Hy4NLo79>~GoB=si$7PnOOMKPrX zWgYrJ_v#z@Z|fh6C6InZq=pYM(7z2*q!y1b{vRg)|LDY7t_@^TnxR5w`DPDYwnV;+ z?Hy~MNfAI&L0zhY!zW)6GM)s0?s<`DlMB!Azih>U?w^q7)+P^RM~dMjXhLBQ@{!66 zM>*yhC^Rmni{*Krt8hn&ou4oFlx~@V6khX!z>JfpGN904tvSecN8GvA3~4~^K(~zu zTk37PfE1?7pK9A#f=?oJthR5=GnW g|$A4gb&^3oSL6K%QK#hmi?RszlAm4W>rmVW;q)?$T7R_3S z zCt97FH%n_Qx%+f z8^|vz5O$cLSHHt3=y4LC3jC~<{P7FN^1O&lXUXF`oRY`jiS;H58}S;3K}In`0B%%A z+?(L_#5ineED2q6oTD@w?}iBF-3WDRC`3~0EvtFbnfR0+w=hCC?G#-E&G7HF1Rz9p zMoLcC-0r8rP}3a43U#z
KAVN|#Du=fqd#^0QZ0={0}>^AX)aOh zY!D@P1h4_#p{O)S2O~(dK#Trqa|%~CJfYLP>f88VZPB{y*&Qcj7@~<3r}0|E;4h|* zCDS*Jb2m-j X;jiK-|mll!!0V-8WNe*bDSK zYrdWK1r>c2h7p40cuZ_UF@F`_oJQ*`^(VwFw@%wtruj3cH{3hZls+~$>4;H3+E_fX zGNlDYKICq<>6xh<86|vqoj8kB6;NmOKb`#F(d06<6X|;|1!5X@ii7#H-tCt^`yZQL z%pFj@cSn-;(ZFfWdx6LX9Z&Ttv^+mApCit=>GV&4#RFJSbaR{$k!X^@21<6mLfQM- zT;#ALqX&9nG&ge<;Byo+2)x+)gRI~|1rKGR!_jVxLe{<(RCFCm^=;m`M5N?N`3Nwh zlj;*?IlTPGAaBDgKk~BDqN3rtuPyF8I{kR?rGkEU`JmR%+o0L~AMWP03EZ^;L+nUl zTGUXMULI;9kx5eRR*v@&793hKqDB&pFFMjN I0G+e14??T>)_ApRc $>Pl z9`ADeoX?c+9CN;TjJ%r4k3wY3^YSZU8i?xc=8F@0@w-DQA#qJC9!d{SL=jGERXnb1 zhm<&;nt!Y4NnKs?So9pw`AA%ELr;}N{cn`iAkjBuX!idIa=?!H6!7z0^i `aB0^nOPZItmtiU zNC>_#5UiGv&mcwaoTqJ?_uykmM+YJxsGeuTPVm`&=7goPIn(8OB@rRHy_<}7upkd? zQv z{xGPND4h4tcYLc+iEVecoa<%zB^$`^3X>E|Y`_4VZ-bfSUq8kjk7VmPvBE5)fAR7> z3#)3ax=j )y ZF zfpQpwXP4$4%2gjFn(@A?HlVU{{kX`K)-iHSy%D}sIaAph)e1W&Qh$;$2uVrbDuLCg z!n_FBJ`P)(AMKbWFyiL3ZAf!!@wC>pqpS!8o?SuZqm%h%NII{&E{#XGwrAN+Vdwlh zt*JS+G8QdSEW@3^m)t;6t@m{t$LIvGqPrVjIr4laju@*%^~XHt{ ECfprCbQ&qJl#*w2JsZI8175%!@Qp- z$u)i4ttuVLAQ?r#vss3{-22%)Sh|C&VGVDp^@M{el;2iy<&|{0ZlADD#zn+RlST;a z{P7Rn`YQsRo)kh;m4q5%W|HvdtqR2H?a* LDx0V4YA zz|UU#jGzXlKI1Y%>9vK~`E;HlmpR9EJ+6@^GNFFyoToKHjPX>qp;`J-{_q9YKi@L$ z; (C*WL&3(}2+3_L~*yx9W<5ht&v%e$!g@XT1m-chFZ`(CLrH1u4e< zX@x4;$HyN*iy|&Xv^GAvj-p330ECkT$41NYhk!n1!N%1I3YUmO$j;iatSZ)d6(6}> z )asLY=pucomd2gjx{VeC`$4yFnw%nD?W0ws900U zuWc}T`*oZ22q&YC{8GDWHR}CeF>rPM=NLHbx&Sr`U(-(q(xpHrMD&{e_s;8mg-^N- zc7(&3{0Ui}=kng$dqsP9JK04k#$IWK?d({rQhFr~4KX}Ru53^1Dc~P|H-riZ$mLZ5 zF6{5oqO=OGdF#OmC(S9x5_DJLedHdZMs*@F$pw0jB!V8IJpjH-6DQ~;I&cFNccqgS zF%P!=+}bRiH@^Wn6i|BPnraUY{H~9Xk}&B6G5P@UBT;`;qj+}FSwW(t4nS<7RXBn3 zVye9_PuMyo5ETb2
k+Nv78U1#Pw=Is$FxB}<+fLQTFpzf6 O2W*@iW`zue{F zbYyQ7sif&?p2 io(w)OU$@7}vo<8dF2xbIgx>%jU6+VwFAhb62l z@QWMY%!P)j*TH5b1z6# >E-# zTGI7{&aEL3VOh!3p4FqDptc=!{`ARu(0TXHP8E7^pay7?@AgUt0ZGqd%ZqW3*jDuU zpMJ+tjfWB_=7|{w2TU|}bQmHm_B%r_0Qm%Q(d??pahVSa4si)`^cJdYWs`^1=wCjQ z<)#UGkVUuN+q8H;^3BHW_mI9v{Nj_~gFP&u4nJb`?g4wvxHjfI3Q-(UkBts$Yk!we zMr1fC_e$yql1mF@@4m|HLD1*g&YKAEqcV*xE-USvt6kU+nc1j5Y!RD_`SYUV$Kn$K zKkG(A_q?@GtfCwrY_}B!ECRM4j*_Pyh#yfA2E8hfj=-7Dnnjf2uLAIfj;EF?moBpD zNlW?@0?KlRdB`R79cpl~mR&V?`4N2_-)#sAy;^*+E>DxGo+*Gq#gEdv7Ar_pS=W6d zYGv5hl|j&-;iveM9`iQn&E7ZBonNA9LNQk7OE+T+oQ0Du2r~s#*rM?*Jg(**Ene=^ zcOGEJTLQtg$GeRBP%{7DJ!g~I%drj17Q~r=hn !oni a=7F`DP1uP966g=pW@8L}>7i#`)` zKZ-J($8? *Wa&cc~ZGW6VI;;sGFY3paZX?r%KOHV(A1+A3M)poFn_41FGr~_q&XN*&4~& zW(Rp>*JH4YxZBWV8T5g94TxTH CoaQ z{r=lV56I)EHEiH*P>_M=T5vtI=*FYR(e2Js)O-s}1zG9zDBJ;VF P DdY3w-XYc$>r5i)9RRrNxThwo2)RF_5JDo=gi%2S$OAivfq$ z VN^mqZv8~!O-~OB2-hWjG+ns$d^ayvlci~uIEmC_aUA5LOFYtU`q)re z;#yKwwOV3ozT$UN;S0IQu3B_ou*X%BSmOw NN;~2aL(Uqt`{mv33pAmjL}3Jz{?cIF}9CTpt!S zhg=p7p6ga68aRGb)w~&fNqMW_6(t_;4|%*x5_Vr8-O<<>h?DgyTfkuZuxt%6Y&fJk z-#}`Ny Vd-cZEZphPgJmIWv27M z`*A|4P-0!}4`CA@&raB0vuzqmlfu!EkQFw02fc4&1cITEFFMMQpsx5q((m8!7eyvQ zNyvO2&lIwk+iwZgw<3)nKeLrn 1M`tuIh|LGdYbPW7>OWW`jM3is3PN^ iZs882<>lVc^c#_pHa;-l2JGX5C5ht#9Sr)(Pn2 zcpMQ&_ji>JDy &hA8GQYhPfZro?L zG00*6F!)gRbndYP+La@9%TKmEmRn-NM$uk>R1CS!Nx#@$PGjFNieZ0ie^|2!k3ekB zh-6+O-h`p^GGLrgp<<4aJGa?oXJYzRCW07Ckgs0^5qOPd24X*d)mnuXx2!! zam8Qo>7XFR>Rm^$O;6uVX2Dm_1Z5s4$y6_ vENqun`D_DCJ86^bdMOa*K5tF8lh=L$E#5F5hFQtIP;&p& z!j3b6tE0)-7-GLgB66rrLDUA6XhzG2NRg#2$kqjO|7Wtk!`$91SQt7z00iAW1a0_f zdyC~ivg+0%CR__C0owVakzbO?$&xrpZMQ5ArW-+wJlBxCkDGVcHP1)|*1o=FPq_Nw zcu%tfh{`ey@&}&2x|SvP`nvq lwn=sqm+s&m+8o9$Xd?oCr0^dXIl==PZuBwsG@h@ zU$5dbXiZM#i-!oSE8`@HgWO@q0Umm?Xfo-N{6jG=9sf~Vq) zx6iR^n9mmY-$EPEIx7yIbWa4WRECs(V~;;>zi0#W#Lfn%j=ut2TYY!C-yp87)C>iI z7Na`@yb24^xw*N=g_I1Y#@dOmWJC;GKL;Y)r7`xYr1Aw+5_(`QMemJz5l55%vdm K}?5%X=NCou3t zX| %WF+B)*0?B!KnTLg$3Sv1Y}=USINY8NB8-j5J2bPs|pTid}X_T{tBjzMLp|MaBw0 zLpW5F2%gtrO>f%YQ6lvc%&7s6 |w`N;kU+cM+5n-P_c)4tkC#Bl06J5ycyDD+9 z -1a%Mnqbnkab8xOy`UsoV7( z(f}S&NvMuDin6W$h+F9*Wro8wb1sY9QnAYLeZ5!L@nwlam-RtN&Qz5@#rDoGOhb!< zFyO`DYyMy=%?|`Qy~bI8hhhv1a@YRF-%I&h(p#D0vlPpJhhiA?GU+vi6$%Lkvx#KT z8n|FtFr{xe$lGm43o1OF^;#cPH00Nb-0%zR{tU7@{nojhdc>_Vz0EDI4U?1J@Y_?) z?bCStEaI~g@Rmv~-uvsY{>09@Sq5es`9h@kr;G5jG#!+Mn&CaT_%X72-QNqSXc%WJ z0NU86l@+>G%y}2=IOnbMu%57;SChGU#_e2t+3G_Jm9u5qy)Sc>Q2+`VfCm1ngV_`| zE}5tO%biqEY~x0ZtgOutA MYKhLL8eZsQ@{C-?$r#+{T)OSEz}$M zH#Jxta>W0AQmA5UR{u8dM6FTOe@CSJHf8$zSbJHh`u~PV;X40+EIPOq=E=sj^n&9) z1BB+fbuOu7l85GZCgl`>cAiu{C#Q$BaB7yLQ=6p4sd3%aG7%si%kAlXf+S2OS0yjo z6{yhAyvLO%=26!@*{BdFk_wH_=U$O4$Rdmhr?XH05_y;cO<2-}@oq&3Czltpw(ws# zjmv&8w!*_K&^{c>sEj>HrC3=+m%JRICOKGwYn!K8EfUDMGWY(RFG%l{oBOKXVM**> z^QLR}kH|`Xh~7|xc4rG-;NS-V7GX*>Zs~z)8@DJ08q|ifWPU@*10WtcNl|8`s>f;Y z_h;Bh=QC`i?B
AACdiyRFOR)|2rppCj?2Oka-g-uu=%&|q?bPk)#S+5KU= z;;yAAlyE6l_hqw2kL|1IPSv)_5B$N`bC$+G87>6`^cq*31fLFzMDwHx1a!qw>{^dJ zepLBx4Z8k*7dnRQF#kykYb)FGh&Z$;IpO5{ko*#+y5*;w4O0VI1xiLPaxd1hXM{{O z3?b9tJ!otY`;YLP!FRbE(F$~DpSa`!ZVlVJr`h&633B)qs#Z?IJ-kiW|4+QE$VXg} zX2Bt%94;P6;W4oaCgdo}L%FyN-YU(py $R2pC+&8-6{2t?vF4DQ)G)i15^U fHK5gfZ5R5Ui3n8u1;BPwTH7dAIYx#^s~;Y^qad)@c>5fWE_ z)Ucq|=wLX`Up<*d6+Sof^&HGl3617mmh)IVv#fur+pFm#D^l^Zp@W`ce$N+hP4@Kh z+dsG*#^?y=RdccGBW);Jx?W6hXx-Ar^wO8>dMWg+<7^n9?t!_{df5ohTW|m)0jh0I znLE#J{mmIV`tMLcCTp6$N-92wHKD}YeK*;*b#D2zt-R{oFxN8l86@oqy>IGWLHcvF zS;@quld8A_-UmW`4dz9GWKMTA>Pc^q m)cz?C$Wl;RbYq=Q=Th`d_Hu= zIEQL$Hcn6N&IOQ9`3-&G&$+<>2FHV67&6Wp=mz&iV&MobLJxxmP0~jf*?k7GdEOYh zj8q9>pDRa<^OhXHTbTiS#@`IjFrrq-9K9SrASYe*{{ #WF|T{(k~n+5>>2(csAn z87rFtv%KyJnf~xAMg$_f)By S|uyXl!*i)t2gDQs0{x(fZl@>Wg zE)WMSfy)4=)vaWvEuKbz8NR}Hwouq{OM93b$(n=3yZo&;D<;(PmZhPi0}qglH@TsM z{dGlz)O$C_b-%x8V)@}vxpC2#)6i)v2^O3up6tOl+@v+R?ZoyMQxUns`R+e&?RqXx zH#t?JvDb1?S-@D;5)ZlU8$_)ui&I+80p!=_`1P6AhO_U=osEfn%y#-+d2FGH-F9EX zCkN{i;cy`lm0WnMY7fSL-)jR(H1$VxtztOM*PL;>Namj%!>Q%Y7&UX?Ri%fdEN~OU zJ89x6U_%c^|NoGQT}J?2#!uoBdDzxqe}{S_V{@p${QID`PlKwk#VgyNbwKVTpMtnn z-ZKb_4$wdF41yBNAH18@eW)Z<(Kw~brxU^1u8Ds9o@o4SwrIPYB-GIIo>eeZC*|?M zp^J_cVm~DT;1X3%8NCx4?#n3NR> zw#?#Bekn;e0JJ)=Cu_`o>N~mTej% z9xJdTXhh_}pyf1FG=C$OTv^{9#9fUG+i+);?YQo;eAMz8F4IPRLu5z<14FQgw<8F! z%U7)~oUq6^x|w4#LEgud&5WG{1|2pZ$4m@ZUcBB@f08|wu+!GO*Ld{XTjPU4%AT*9 z%Cv)W8)(Z6;*L${A-B2@y{0oAf=jv=yy`_8Z*#`;WGpIbVkJN;#?63yj_R3~FsGN8 z5*MxlwvKYGwzF1Wkc-MW3LrZsGaY4YPt875S4@SXeGJD!^#DwVQXlLJp1H41gf+b> zwF8Ow9x4 nG5((K&=uX7}Il?OvL-#pYs ?(++*65=%4G{0$7M%>9+A9;~2n;MG8 zhpMJ^1 ??k6yh-@%FV>x251b*W4qxUb9a{EiOZ#rLNT5E)&7swN zRJ0d-7=Y#vc?j4f+tor(chjCWUf9cWHPA}KR<&5Nr8A2%;&~wTkj&0GrUmb!BSMI= z!@)$s{m1r4%NGp2@(J`CY})tv$ H8PJ71u(F7-X6okp!glgmdh$gLpx0q;u3c_ zbU#X3az-%kLD$i~Wxp8~cf7Vp@0xGdxL_3h2e?Q4K4li21w1-oaj21Wa$!Z6LfybH zi0^Wx?VmenO5C>*je{Kt!v*=0CuQ6H%9D(V!N|1${0l&*%Y-oRB(C}6Ul2SxC~9!_ z=-^6-!J L_CbBIXo5N6Zo3<< zmn~~HRiN+$X; @y*Hv@1Ia?3cN(QY^~!W+08pF&8{(!|*?R*z-h6s1z+^ z?9C{VWOJ<&@uBL$9=$!9qVlZk>*BhN1!r%$Y3A2wx^3%TVwd}M)v-H0Ww_oLIxG=n z(ZuogE_tl9yZ$-tS}aC3qT8n^H}2N=Cw*U(XsB(eyBRa*ce5lb2#bg5OoJB_m(Fg3 z)G;}=&F@e*5n*7)x9VHtA6Vq=V0z(4+o?D*MP%H;{SO1;>mkq4S-JtgdUAcbD46c2 z37#!w0+{Z3{g?Eu=d<7nbN)hgLnV6`%hy+jl5Xwys>+RI@2Iu}W`SH!{PWJoa%aU2 z<1}^GcamXG2gD`&FXF-}F=61w%ggcvuhgy%EJ?POo817fLmxB1CQY H2VdV*h|nfu3fGu3 z^qq9ysOY8%Kg@gR7nP-mks)FAS2P8{zc66d;%>jfG5uWP@(7-|BoJ=d6!0Z?6Smm1 z?H^Ge2@$r4tzMe*5|;h-Nb6$F@H6`O!Cs(t^y5>&UMIuGRZCT&<(EHS@1<(;3HjSd zY93LinBYy{HgQD^>&8RThh *bP?z z3v?0}whI%NyyfoV92E-6^1If>oZH}42VGiXZY-eMGB5cK4p%O-pTh2O3)XgjX1UGi zvaCE|^NsZY*T32>;WQ_lbo7~^V9()nK^k(Ec?hFEM{}-5L>iJWmUTd)0b@U-QEDk0 z`_s^`wsmUdOS6H+ig=^GSBrtnz1{J{tamC$EAw8k7$D!N`b4m|A`=qVY>MmC$FnOy zPpM6GWXV=^O7Ydw-mX-;Y{iA8hCTL>T v&jJ@_qu9T*9`@ae=4%=Gcm@ zJY)HRF)tm=V;jt)3)1&lkW4}KrjqT@YjcF4Ew{|g*YFwEm-&fkV kO4kUa^iqWKUh?Yxl8 ztA=I4>qboWDel4E*r2drX$5Q*w1CI@BiWjDJYK)*$Mz0c(G0(5^{xnCMUH@Lev1Q` z?ux+w%`~8$bNY(>%Zi$iM#UiaBc$_$G0Pfc;RS;imr05S&BASNtgq DD ztjSEe3P8wU_MQdi31ndfGNNnR{R@@Xda1Y+kpCHaFT RKm+ zF;N9sfx2)~F!x{wcj){%T{Y=ijCP1!@<3qm27lWk;Mhpo?lW~N*9SC58}+8YYpd+@ z0ACo++cz3kczFDFIg6f})>#M=RM$9tj;F&;9=W`GbQ!3Y+rod^hgohd?fDM=uVIBP z)L@2(epzlwEcORgkFBiJY bwVkd_aB&<);^Of@$;X znZMG!+MfZ>PNj}B{~P#Xj!DaGFQU4r^XfX=(`sv$0VG>Bma*{t@jSZk9?M>-Bx`l` zT`_{IIT{FL0&bN+Yp<=;Oqa$;ZW!zto@ZnIKZB+%{Jk_dXQtL;5`k1b37F0Ow9CGe z6)5Kw0KdqN^R22e`Jzmv3=_(5y77Sa-CyLYSPqpdPshN?37W_~UhrLX2IUiq=&i_6 z#{=1MCn@&D!!^)9!~dylWej%cRSx1gQ^an{@oO4Y>R4d??el0gG#~mh?L;m(FLIPR zGfntHi1vrC$XYo9zL1##`&-N3e}TH~WB!Q Nr=L?-+|)7T(>-T90d`s17b@jF;%id8Q{N0bz^Yql{aJ zy1fz_GIfy8s@B;4GI!ps*5o*z-DCe0xy-Fs)5Z%R0AkVo%B!QVAskaw2}Sm{?0?un zNpD>CW?X{ZNH(Di^-4)bF4FUfxMqO>OnCa_!7r08zYe|k(teb5e#S`?;1VJXkc$b` zz?Z-LG8V8LL|9zevTx(PUqWYfd%8ANbs})Ne^529$l9@MlyqiCpzqLZ?y58+Dw^YW zP+{%b-y R^I%p@jG!V3L zcZasObI$WVbDo)5Gi%NC$Ld{sSKZaruDa`YUA)&8@yH~r282ZKiEZ>X*|<6#7v~<1 zo2NLk{RZqM1_$EH)}8?y5Uc?Kd**gih30zdC9kz#a>-8IU+e(eoH=CmP&&a)?^?SY za98OnF81=xb|{B1I*+g0#yaS%tSr0P>i3tOm`#e~vt _JjgsV9Q{ygGRIAx!v=vD f;ud&CU$=BapPzPzO!Q__M zE1=L0Z*scG_`>6;Jo)8o3AIJ&tZ$j_bed}UT?Bi%^K2(Ha-(8Zs%k+NSst?S9BN%< zT49lhT|vnJqP-L0=p-Vw`uzmWa3mq_F@{c(pOx5r0!i?4mIA29(rzyRmIGg<$JPP1 z;|)rwo`$}+r+6Pi7n}IMy#O+XD?c06f5bwr-DFI;@*`!OTLvgEk6h90&fNpsm#0dk zlj3JyZo3l&UJDgvljW0V8r174d(HOKyW82W1%W5uHJWV3dwg}Tpc6H8l5eS=BV}ns zZh^u~shXHSc%gY^Qb|<*_|$9myxlwjpS^`geaL{0vUI)%sqLju+jf=W2MsZ+V87H6 zr=0I>x@^-lFFhQ>F*JP&;3n;7q_R@$c0a)VfIgsN4=YRHhOW7~5pS6# s{;@=X2Yfw3Eigchg{6O?M6DR z{`3d5t$Fn=hr?1LYCMq_nib2V9iH-4r#rA-*W$?G30XOPM~1wQ9`l(A@L!|=zb9Rh zQGU`QK~1CLW$bxDU+%Au)m0h&C6I7H&W^{{Zw{BPE3p8;EW95E?S>zBig<2!Hvi5K z_2a%`sGe^z^~t2j`kVmu^D0Js0c}d@Wu~uq7hisAbnI(nY!v!F_A84ILjLUS8K$+i z{c<7sToB+GtVNTar0dgA@xh6r#rpFBHf_SkRcc#+78qLKL+zKS(0EeVt6`K6x2Wt5 z$7JCngC2Ee=ilzpv_4C*v?LPg0fzwcZr23~q8hTa&bT#WF ;7Uo= zfqma+wE^n9>sCou|D-wSrz`}6lg`T+1+IqTex m_1y5AGh=w0yQ*3;IA{Dlc10eAL9HJz7J>VlVRF894u-SQA7aax87-aYT7 z5tRhSBYNOxk}*K=O{Z+Ooq53)Z;cD>YF#csmQsiQjk>^me_ZRzAQ@b&<2FV8gX#!7 zK5EAjlU;yVD6E{#J%;aS%{|9uI w-^EX0Mrq9U}}TnfzNLajMTIN4=+d zpWjL=jcxPPOtA`JTJ-$}WZxs?o @OIN(&-$fZUEodpAP;nUz?nNrDrZAi zF2w-7;8`fz;h k4 EL5@M%i8|MP_PfZZrqOb$NRy#ZjW(YbrePMFzKf6i1@G6W ziO?pg!%V>PaGi3gJG&j;*; ` zf-jo`4>lGqQtlzS6^yERiZUd$@p<2X?&BE~6C~{&vCAood}Sqo_88;RZZ+p~IBo#y zeZRd!&*=#uM6 BOHbWG=Ja$we`^H8q@{=kMSJ!AZHEFHL~r z@;a*z{q$3X{u$!96t{A^Gc Gk2+a#^OA%oSKkw-Q z{i?HLR_Pg^?*0@dPHKw7Hf8sb1N IeyDrRY$!uX$r`4g!n9CR{9I?}g@dy08cTMba%Nd(vpO#DP zK}}m{Orw!kX5!YqbYQHZAJ`;+fLfAw @i8vB^k@}n!ytUpGPY5F;Bg;| z%*lGZ7Fiz59TF4E0Bi1m&gAFJOkNu$p4e&hgxm&T+buh(g>z?l;9orxW~<}>_Uij& zX3*+61eVc-VLpx@+}AbnU77nb$Z+LdmeVNRApYD$G=sj|COZG@1s$xTCI}tnMI`{d zHN*C6g#-Phn|^|y@&dZ4YWo5XYNCF#cXYVcO6bc%9UBs$5?;k@R?QDxAr_6%4MOwb zIay5~-*LqX6$x^_#+L6uw|#xHN#O1*zG9hn@Vvc0l*;+}iB>IO55=FXb>=I6FG1w! zg>>~tJ3EA|qwNGES9yVVY%xMip!v3p)a7TlbfuA|*Ja&1=!e2bS7* )21Kp7&_d ziX4?t8sd#zL4qFok)*zQE@8`=8UMl*tp!FcSzwmjWv7D(hmNzsY`bfCliR?{?adWw zVj7{$F_{~f0Q698fp*%iuJV4Ik tRy ziiMKQrP-FYC2*21AMEF`&Fbk}rNWam^8UHNb+QE hH-{lj?O0l+9?7U;OmR zpM8=tf6>YY*=ZUb&dUEUsb30O2c`H?h>xisRE_^v(HB +nRgB=B!wRrRLseTl;7M3RcqL{*@*N(1s zqKURgfvVuUE7zrJ`EfZk$PCZyAIsF|*%N0)2+}hQpl#-y@Y@{o)ZM~@@}B$1U1gcD z`6Rx;9p?_*)C*9exNZ>g4&E7b=PCE_F8#BtKGormew{u~aav{70?b4hD4wPT_blA4 zDW41@CX#owy377r5WG=uQvhH#3=`KYvOA=)Rfeer=ZwpJkY9_jHG$&IPPAO!XAA%O zvp!ELI$(gPa&qQD4+wMSIqT|cd>aECb)tCdq?HKk(4wMp$Y*D9!9hSbbLRH3#1jWM z+re?cxUIZMCv_x0y|(uOX(6o{{I^Rff**hcLT_6a?aqk7J=I$%I*%KjL?Fh-d7U_I z8S2_3d?m5&weNpOqa}0G3s5pXW+F#QeSWuL_Ew!(>O@^eCFj-h8S?G>yG~T(VLY_B zGp8?^dI6G3)xI}gMY;bky`=EvedNgRc^U!p9}z)IFJbO*XRn!W? vR*UEgGUOI$4BU2j(!{^Tv_NYqP;=Ug#cpf+St`9G4kGf3 cGV0uOYl98k) 957?F>^Ld*gzRFbNRx1_ojH29L*a(V(+R9^h;H+;sZn ztn;KhDzWp-^dDSGIg;-CZ*?6sSw1V)8N0SUQ8EBN dGf||NHY^fb=Y*gqY9^`84vNxe+0 zGD?`8!Qv)j$>06AFo72Hzv_>pQ#S##hx3 WOoktmJRdN(AXxlmnp36uo!Lh7r=6mqxi^=gfzf`PL?@QV|>#IL5AKyMFnA za)OWdi94K$G*Cs-p b_oJ^3#W~G&{AM|*SuhVb=^Tdj ZN89m+x$CDZaj3_CjGCBxX^!KA+WpEtsN}ddAzdb?(d)20St>@m3mf zkAHq?KY^Qk&yzFv$7?fh`+hwkF-tn1FpES 3TM|)>C1NKU;sXNI9uS8ThBOH z%Q+KNSQCGu-CZrPMFY tr4roFC8{#}_fOyAI1G7?k-EOES42mb)q)2u zyZ>UfxzVj}%8FE5S#6F2vGjq!psCkT3M&e2$vx%!+1Bo(l#yPY49XF^0sUe$*c?5j z`BIA1HPdg+)aOEM w^&c3+baXeJ`xRb+dZo|lH z&}Ye=Jt+0F|Nfa5^*cQO^P{@Iz-zd;^{wvblLtZ3pR+V=VzsCLKdN=VS(? }6;E!tS|-Pjajk2#2U4&r5=*|N`CX5*BKY?Fse?zf zp^8v-sBhdiP R8 zxu@OSbC12d1VEE6c%^FAmqN%~x)PK~WS|W6VAFuz p}8y6J+h%!Cob)KD)&ex)K|mljJAT| zb*n%17G4B433L%On(!MF+}yM!Z<6nCo@&R({?b{8`FaI?VHdPor8wzU67NRWW*_Ic zih<@^b>lXR9#bu$I>Zf#Mt(3fX#JY^dvJ9Zn*DWXn&Q@0=a=|pk1~}ln_hH_^47$^ zgxS;fD0a);dx@ZvLD-qcu?vQ+!JLX8>LxY*sx+~d90j)}an$^kY8L$p>@nSTtGTko zRe2evXZkup=l!Qh4&9fr|B`FlxhUxG_t3|@VnaV4V^i+!(E0AiSv3M-MyXRxpVPQn zt}Vsd-w|r-9rV8Z`d~qNNzFkZIvemB>q*(Nb?i|Er2Yx?q79&0Rohd?LF=8FnYrMkJ7rFYHL^ zvEm0c@-XMflu|02$Aqo%`r}qp j4+Vn4piS>c1FmM~ma ^7Wm_5a6@==e->FMhNt@j;Gc*BH#GkE|Bz3ZDbg!V7C> BKjwfMwkrO`U+*s<{s zARUL90uRtCZym#oGjI)Rh1o9++em$kW0EUO4IGO(0C0ojdp}W95Z!GUv~ UX@pPC4b1W+A%_l#?`6rU^09ERIF;)Cs3y@wOj2fOf2ec zLWC~p?s`Whh Q(8I9*Zf$C(UQ3nk=);9wvXj)EoP0SENBu 56> zOv;aVegqmGb6N0U;ytmVKm02*!NB|j7=oF-?yEK~Os(C!xOUL#jZqR6?Bmt`O!i oRo6ueLyEn7YkN>7nix)nL_C2X#bu>gey0{F6 zOD0a$g *^TCSjSwRng7uMsx;RYG1DIn}xwEi}I0rKdl5~I %T{rj@flZp$uq9IM{dBMD6h zDl4Y1w6~a?jqH9mrL28TPtjRXj!4kIn5K@rnKP;Xdd%Wr@*^A%xC4b+MHhJE>WojK z69!!6Hl%o`q(rQB8`ms9v>@FRk2hB(9vvHr6};RR^V&8x3(QML+;KT f0 zNh3WHhVZI=`owUdq#m7mvvwmbwURQRz8i1vI<++KS>6&qce48$TYTk@X2c4%qtw{~ zgRt#LG0h2RBl3q62J{HLIo7-~dY~sZ=FD~|Y%}7<4kmV44W8XAD|?2?!BSRMO>ky^ zPW%iEkql&xaBUjQ2~B|JVHBxwE;*f79ya@lGY;oLSXcu?IpAT{r&3uML%xk@6N8_{ zpOuHyIG*KWj73H5yJMa+S|25fTaIJ-DtwT+sv~w@sDAHxiri`amn5D>7#0eUeL&>> zlc=z>=)y~Iws+BgC)b{#*=kLLzPR2q%B4leYx7>afB(ch@W?PLs(`d+{>&}=7n#;= zQL4PZZFf%Oe8%SEr0}u+iSAf2Sjucqn|C_KrB;=x<0AUEy|*P?Zmq-d?B}rqkHq+a z+F5mQ(Xymo$i1gKL27+MZQgmS 3t@cJz5LaUThH6Kt^6kZ`i0?`$>HhL_(daZH~<(zgZsF}udw0&yPa{3lgTO-k@K zXE~ouyo-2pc|6z7BcN9?!Hj7D*E**v;q5OzD#O2y$K@{z6hpZ8U|M25n66mHpyHtz zK|u>UJsgO0jMrZ`p>@iX`-5Oz-p=N+uHAX!&)b9P$%DG*iPZS@6Btb=7Jk%(0qSx3 z7$-p3cFYUri8?`}_#=g^a(oHpC^qEDj+&>nGBZDl1S1{I%Z|GO*6(P8BwGWXe;z?D z8U4N ;CDiZW4v)~?;R%n^4Ck+ zu&4Ax;18S+XfcibiOagOiaURh{r9VcgJw6|e4M`iyTF1-XWx?A#pBR~UX|VceCDF; zocxKAXN41E5&*_G5ZGNu;1~MO5|29wkg@6oepYW4c4FolJ+$GC@J`|3H9XNB0K*D~ zvJ$&dA!r39LkgKACUGz`htqII1}#c?=zd*!KgwUsHj%ML7U=(P_vxj-?$a$V&_d0L zRL8-T3>A4x$-Ug|f@N#*& 24v~m(!RyojB)8we^zH6)^QWnY4*qAH)eX=u*S)P%OK#BB?B{u*weSLVz1HBw z{H$)|*=z)^v#kkV?aRr~oZ9}6hQPF2*yrDD7-?X8K`^sw95nDpd~Eo8JIr#N(ZPB+ z!=8gORBS^w)XE5k(*F~IdS5%_MF@|wBZLdX7&-Sjth}7#mK>-zYb>HpY*?tZIuf= z5lxdtM_lYS8KDZy@)l|auXidirZm!}wYjIqoz3}=m~b{$mzsp93p^i%2O0!@kPp}T zmQj#@B(Qb(lFn7poW8k2O1yP=M4>0=-9F070DVix**&h27t|`HE{^p7fsx#X=C#Lq zI{>DT83)?UHG}uSA=tuuRJuj-g7uA=cDy!nG&sz_DSPsbxL4V|rdV0G`TY}{p+ziV z450^1bcdVVKHtb*D0lF_L?FL_-fhG~O^;FH|6n%QpBBq?8xstog^Mu2!{WguZZ9N# zCIt7I({DVxnNWewQF`dmRPnGd+qWJ-jlO7dEgrNzP{SalLF^@|n$?QvV@Lqsi?tCU zzB|=tZ%41tECb;7DK)~ym9i~pxOG{*u`lmzZY9I#KSQY;e0F+NO^py1j3ITn;RMR< zZX?>Wwx1`MbO`_^73EW4;D7eDbpwX(iSWAARh_iSCVp=LZzXpHm>D-Fnf=T>5kE~I zM&k7**5Zt0&ehd&6A$)FFIv%__bdxIVP%6CuCq*56ztdrW97bj7k5it8D=GjUM+cQ z{=D6@c`L9~jvaEa=}mpJ6Z6<-_u(2&=-OwB`__1|lD~Q2>jTGb{Z}MFjNv@yxRbp7 zW+}|I_;KGP1_owh{KF6&XD+`2nSL~mYknQ{UZRJK3o!raLRJr%QI4C1aT|jfzeQ6b z?iVq+UQ-t&o0-+qc!9B>6rW&m5{A4pR&P5QG5OnNQhJ4}>@$t}3M4*-+{W8N6wD8s za?@K0e(d!ev){{+oz)ggCSE+9Bz Uv%zq7VKU#R}sX|F@sVd7|Udm($QXu(D{3 zJqzB8i7)f*KAoaNfJgczEe2WkD#PlTJRc)X`hCp~0w+YDfAEItz_1xA;_c*@KN`1& z=wsBXR+V) x*skc&kO!ugaY; L5elCTeMcv0 z0FtlEY=<97Y9qpzmaBtpW2CglURw4l-HY`Iq@ZaVA2GyiapJ6QY&UA2d#RG)zw+ui zTx!pi67%Jhc>nF(WTfH#uI};h=WmyCx{`V>44fKb?$T`8tC+wd{k^o3b)>(1@n=O+ zc@kt{0*XhyMzFofWIg)mIr$@!S=zU#&HHbYyICrVPfP_m^?f_NP(kyA7;;;nE;>b7 zk@34@)t^Jf6Q7|m (=?#YyQaqn7& z$6s#{#|tW`;|Qm%gQIm^Min)yNhWvDWQ%pmhc>&1&ZnGDKGC}-RkdY??Ttg?I|;1j znjZNtW44YeLt21t0wlbz(!&k4y4+A#iJAM_y@C+yiG1X|&e_^c#w*2;^d;pw5!p8< zty%+} OZh`Nli(W5kD8VfPty^OkgO=-~h1pbO&;-h8Do+ zazk!I*Wo!<49CF7qC14`8 z96@dg+qR8C72{fOE;Mgn5e9o`jVJWo`SwlpYYM`rbTjXEHAWt{n2y)C6>c6)zRyZB z7|ByayK?}@>T YogO5}hmZ~}sntmAw^Do_p$Ni^ov zVDbc<=y;dy*>kq5hUBeMC~#i$Ms1=_;xakw4rZnF>h#AT_nJ|iCf^LCN~$_gEh6yw zb4uR)zl4%A%ISslr)roNH&1@-_lu63QUclbik=e+@Lkp5O;*ARU~5V8;NCnDbN;4i zGi92IqW|6@uIKU>60=x8VRawZF> #%_yJn`gV?TdVZs_X z9^?KQw%{)02or7Vk>PbMo``N_sNgW6^gCcd%Qju#{08tXaiZEke==3j*P;A~2aAmd z6ul+{U?gwUWAEO7VFK42!zoP`ZUrCu<^;^usAfY?l%sBt|2QvMo9vr~d6PAcemdYL zJJ7#NEZndpf5asmmR6BrwRI(4s#Ve?7~N0K^RS!RaYHbnwI>X>GxD}Dmap(wnuyfZ zyU 1wZ{^_ny1%qCc08)NTBk|1Soi?NElC$THp&-FNDA;vy4hqWK&W zWp-!e{duc0AKjsZT!^qY#*@lx7=s{d8ksn}el?ULC)H|8b=o5>wMau+qC))Vvux|+ zGvhjSnH8V?_Y(a>$Dk;pBVpg2b TXoQwGjCl&3E?V7}{_`mUPhI}OQ(4Kpabe_VgAi==^V zOY_H6o38`H1m>Tb1@uo!*XU-PE*vi&(yQi;I4}v@ilg7W3~aoh+pnt~)1^*ZuDPvY`|M+C{X&7>Mv-LFpn_y0b4mSc=}3M*aC><4Rc% zPQI> i6t(^(?ewVaPZm7E!h-U%&jh%+ilvC@lQJ&aaIbZ+z=a1wEm{|)FqXG}MH#!lv zyWk64jjF2_?7@FK1|F6KJMP?HO!Q$Mf4Y50oHczd#%upQ%GaL56)KNBz@5U-OLkoy zn&nd6JG}_k`Gy^aw!Ty{Zra5&^M{ZuLqpVx--hPgu9?SgR+pMiu@J^BTHV48Es4A_ z*#l3n+qEdEc(9o}f^M>5&B8}6 fCq~N^*htH}w_&&G&!>8-kLp(3S#r4Gm z#fJIVoOQ8ugKRBR%0kXq{rTBwtyMLbUQtqgjUjkZ)fjy!HA2)hzVtm1Z0-%*pSEr9 z6CnB`guCtIZRl<2>kahm(Dr>-S5uz C*E!@@q8 Nbn3QmM49Z7O?QRXrnP#J>uuFOKy&N!QvjeqNH zlojO+TT)*1E{!A8DjI3xE3W2tDK>IZc$P@8W+S3T$_q&1CEAE_zP%eBD`eCBt}JZs zOS)+tCb!_#dGA@SoBnw9<|^hOq~+UHbjxN^g908R&0Cny|qeAu4blZ@vXes z`vu?3D|JDcwG5*W6W7;Btp(GYN0O>l_MS%sqDfrZ$9;KO64s~kT(f3JC3~4Gu^nZk z`C5YX9yzmuph!A QRfa!a_>LU*}csiP<8m)*i87{QS!RS9;B4-d7p!QdO{YA z<66^N)nu|BaWXakl3751l6&ANaQDVs2Ip|=D$w2pA-wPuo#xX&r}yOA-H=5fNe}x* z!+~z&dmJN_^GJC=-JQ!@`PbbB)#oAVFG^i#%{Av$fmSeWH5LFjffa=)lbZzjUzF3s zvu `DzIBpeg~5Rm|GYj!%!kp`P&K5T@Ef_bDwe30=Dv9wdV=y)o9OjS z3Ye`F?Y>=}Dd7&jX*+g^94dFnN3>A?JQ~o88#vv*d8AbW!?7g~-0)7y?$@Bg(KC|# z!w^__zyA1Y!`&}HDX=h3mJbvxns44Dh^@zb%QXY`O(e$|F M F5@R<&iAvV7;I-0aMO>DI$1api^nHG-M@ z>i(^0DQkKFLHC~qUxf);4lpCA{xKyrh@KD!Kh9q9ZYC6Tt2T(uIV<780=lk{(OYjB zuNta@@f_(pg;b`pw{nn#oxa|kvCzYRuh_S(VDyBL+>C!H8vm)qlk|R#{soo5XQCkp z30mAJGHhI8p{xD$o=@Yt&wCMmnm@|;k{Q>x_MUp^$|fG)mHyQ`$GJj zC;D2zw_r`6L6-{mBvxH*UGHeR4q1gunF%c6@a$-0>4}Jdtw?TGJpZG#x|!$?x(O4p zLImFM>!;Wi;XDNvr~k+8I12=_b;vM &Ips|@KU0|W? zj;F3B-S)z2mo_3IhUw>rmxzKPKyG?4C-GSOlQ7Pl(END=yIY=bd9>H7qODXVR`@)G zRT}9Nm6bPk3R_;rCKs|ZY*lf$c#L_kKP9#MzmOxvzMXF5D mY{$yjmTy6bz)(q6!_$#wIA_iCKfPcSijP?<5DqTz2#|8JZBe<@S|F;%Xh zaKCk(vdnCJH0vG0l2zv#Ix!3x>pRn_4t_Tzi3}nt4ml(#Of%qB>woPha33+3J&XAz zoph~7b-$X4T_wj}vDrEYqubFm+;-~ib1RzjR3uTu*JOzuH(*-K91C1vjzA>PiqbME zK_=D0ORHY+8V~V5zg?!`RCvSj79U?!-C^+AK%)Bw&Qtl`$6a5hc z|0T51b@m?WTFmoE}KY?M%9c#^JN zur<>ps8mm@-gg>7&j^DKI88ku$}|whmJuE+7H(9Xi0~KJ=fQV%U9$YpFv1XUcLo3c z B;;%^d- AWNVVv&p+jn7He(~CDmWHrs5(dc$=0s^lrAU$z zEr#5>Fs^uWyOeYbN{9Y)Z98@xT||$P;gdwkyC2?8kkDEHTN6>Rz5VSg z&Dd2RG-s>-I)6%1kz#B#5fj*nV&Z@PGa42;H9dC!9SxSx;VRjLm~4WBJ(ErGDk|t7 z&^2+?oGhFm=JekKGeNl<_MgM62-K3Qe6J_`-+fsi2(GtzJxX1CEBLRG-JP~q{T~%f z3P@*pDrmyU ;X#{>B+7n{Nyj;AHJohgM8&VqUh=+SoN@cyt|$&5bCk8GEXL_RA5kZRzC zzA|XJG~bPqKx#%9*0JR#%>RUFu{Vx&e(_LUQlSYR#~Qn-ecJVOA6fwZ@3w^YreDWH zBrM7Kk1k9l
;GV ga1O#I>-RRF}>QR=5 z$yr~gx7jy>5?TLT=4NEXS_dDZmLyf*5bA+nnBMSe@Prq+-!C6W|4Ny7Y9AU|`GAaY z?IW!@aR&IGz ;C8F#0PRyTXIYH2OlH3Mt&YvB=jwN(YC~t=;P2m&M+FHyRZ};MT#0c%FMJ}?b0>b z;I^}z3~sJ+=}P_O3h2)bP2Kmr2(I|>&hwoKKYYE*b?2kle#aMci f+1i1XGi+NMZn>Af+13Tvq#+EY?m7eXp zScK6Pj1Hbr+rwA^qX+zxeo`rAvJ;&e`JuG`@1W>o24ySVT%O{~i=3Fzevpx-CxF-x zi)~xTZ&ToYcXHee&eqTr-QG0A_<7R79_Rx&HfFBYa5*Eo;x-DYh_PxxnG7ZRIMLSQ zHseu-EglkoD(5{imw{YejAlX{RY9z3X_k=4U@E7m#qg4Kk)BYY*069zPlMmld zotE)8REL)X_uo`PR=K-FSaGc3(4KCD&swwK2*H^}cXFHt%Z_dJhm<^}ZdyMS8=1jn zkz<9;e5cO#wH)Z2p3_g;Iwyc+YsHV>I#;IOUb;~IV%NM!R8j6}+soGJb|Vv^zTOfn zGG o#olRVt5Hevqj6futF0E!YI}lp zVxD3^l{%8H= y?lpE^HIe21Amkn$@C8*{(OZ|hE*u$1*?ts zcc&3W^!ZmLFp$(BtfOTvU*@PwzeA`WTFvkN^4b^95B)ZD`N$7STyjABP?C3tfc86D zct0lrx70(jM`Y)zPeNskp`0Qyal{7U=1Ex0j-A<^@Tz(Zu!(FIC0PbkF0(a#^^W4F zspCU+1c@G4+%tt6KNxD$jph=FZFXBz#5klx?dJM!E6^MaK;-=<=|t=VkgK>ilO-7+ zG$JHiqw5Tg0K(Tsqk}1%z-n`)EjyDZM9Qf~E>)ZEtPN+)7C95~SZg0fQh%<24)Z_^ z$QyiWP(^6kL~`l(;c?ooj6bG|OjnMjfDX@uwCe>3X29;Eh>8`fFGRTkYWTWqaWZ22 zn7zyje6i*x{eB1jEV`@Az$~Umn=`M=s5UoJM&s@gd5=3qR24dVAElm_p%b)WTxlwu zxLze&+OaM}-qZbIJJClXVM_aDtdO=GY3=m*&!2I)`iH$7R}{v5w?9EAp%~MV)H^rd zvEk`uR1_Ks?C&nG7VcK?p9{$r!pVU-A<+g~J1O)d@RNC4AVl!ZI!gLWNw84Tl(1gG zmiV{~lrL?~y{@Tmh +vsbo_qzbnTc@L9!Y;96a1VpPqh&W6$UkvmOZ_Q zGvGtaz+#ZPnAgp M-E&mEp7>>E=!>byU9|tb zo9K+C03QG9 Mqtc7FeAYn0Oq9GwwF+R~3; zIvGTEl)D{FW1-ExNQiS$4*X#Fpg#Tn41)ejb4KsirsSw|lT-xZj`=YIoydL#-|Zfs z)1k2`lm4V^K-XB$6XUP3;n6Q-mU{su!LC>d))_yI`Q_Z3yjH*0?-N3#fE|LY{FolX z6!?s5?^RuRq%Jj4FQMNrOr!Wc)ifmj78(9`QJ<4daZX9_oQ2B%TcTn=dtfCqi+uw? z9WM3ZXk0{@=^3vfCg6X<{|5YTC;m@k@c&ShcEChmX$H0}%aVzyGmp4n<63K|B`oP{ z|0l49nf{%EAAdP+xOSv!YBcpv2tyn}Sz5N-$61S0gS%O|iuDq9cct|`mf(jwGCu>; zr{JW8Z(#vC#MOmvMYViSW;1u>$kP!hvr1#UDC&X0vlR=OHKev|jyT}bG|hh!@6Ev^ zL?5pxr2*4xQvS0i&vz4_tYqKnRe7(|eF}Gu6>fQ94~qH&Lr371Ety$J4rb+r!7pBC zCa?QSH6<8eSDeL5CcWE&(x}qhNy1kY{oM3S*aku$yi5ObT6CEE;=5`_-$u{fC0$*x zKHKz^cKXL>-=e0s1(~hWulJxlZxn9rwUC@K76qh06qnO2pf!lAs~IgmwAJijWQVi# z*r*Dp_tNu5`=UY6a?-1#;?&nGVC-8Ol|61ucI3crX4u6%aF5u$mgmvhj$Mmfpg`ag z(ms2s*1ii1UL2h(gRnY2_dLIzIe^c~=#SB#nu`Rrg;JJxv# (o^}N1VuMgmR)P`i_dwnRlkpPTt1 zPH~lBU(WbMZmeUhCVS_>G2V>a-0w^K>2Fp=^Pb!1qop9sT2JUaatWv&t?^Q)nG#+F zd6doK#xQzS=MnsH(L6Rp8kV34zc4G^Pu$iS?G)zh_GbVc8WwbZ*1~>Ut87A{XFRUY zag&8le&GIe31H j-#+joji##B-Se)vlz(6e?#+KW(T6`wdV=G+*a( z9uy|K0YjM2{Te%{NVNbdmAcz+{Ej!y)zM1$>r8KB5z2037S-z7T(@ghX_K8yPd@w* zZ$Wtf;eLxeldimTdV=HHIiq1jE~Y8fz+_FzsA3 KN@dU9WvZ6ro2)q8n9Ss{v=T1{lP*-OXA~e@q8?U zr&5E~T!T-)+pe}~kE-#SDEhbiV-`u0st)zS%-+;jRs2c6XjZCv76c=llI+?x#q{t} zF|)UbW#D@DZr9N7%YeO&$|nb-+6fX`gBknKGqner8EEV}$!$ N}Hg+_w;`uJf(aOds_qEw887J6mTN$v>@kB;`fERx0B=C)5?c zekE7De8x}TBxT ^SR|sc% zm{9rUurShByR^#Hq6v*#OS7eXN*CSo3~04=Bh3_$1x|%&(QMIwM`iZI4KSNZy#tHb zt^cYCei41^@OuZdnfAW4J8DT>1~NtOpc`xeE)RSh?Kr{G=qC%2?yelT4oP qTkw8{G1B>`^rCftA zuA!Am<$Z2Oh0hfv?WY6d;+Zmw6ls$Od*9^4p+wK(Ke~}w-}R>ua_M~7w4WX1ka*ZX z#4Bvpq|*LGP#FD@i|l%xd9)#8mCJ!-k*#y!ur_z2c=czXV=uN{2tms_hF$U7XyN95 zM4a3K`b#$fexdud^Tg03r4?DFz4T(t>dj&`Us!0fno0fhhzB7q6r``6iku`k^z?6f z<{#EM=fu%eux2LfVRIfPo=as d-UwCHH2?{~c3UaL7IHboWBhkq4X zC@B#c*=Tf>%`j1LfeI{e(1{V@Oh6<3i9ExucRanfEofjH+2WJyF+wpHqxe0wPKz(@ zf2@=6DuId bqjayCFJs!f zt$K>dr+=p@L3vu%>>-x2ckgG>_KoXqRU&Z)?(rhhI42*SJY>@2(^-C4p=bH+UT+-` z@B0clZ+aXbU1yM%k%f7>