diff --git a/.flake8 b/.flake8 deleted file mode 100644 index a8a82d9..0000000 --- a/.flake8 +++ /dev/null @@ -1,15 +0,0 @@ -[flake8] -max-line-length = 120 -max-complexity=10 -exclude = - */migrations/* - __pycache__ - manage.py - settings.py - env - .env - ./env - env/ - .env/ - .venv/ - inspectdb_models diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index b4a66db..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: ci -on: - push: - branches: - - master -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.x - - run: pip install mkdocs-material mkdocs-jupyter - - run: mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b24d71e..0000000 --- a/.gitignore +++ /dev/null @@ -1,50 +0,0 @@ -# These are some examples of commonly ignored file patterns. -# You should customize this list as applicable to your project. -# Learn more about .gitignore: -# https://www.atlassian.com/git/tutorials/saving-changes/gitignore - -# Node artifact files -node_modules/ -dist/ - -# Compiled Java class files -*.class - -# Compiled Python bytecode -*.py[cod] - -# Log files -*.log - -# Package files -*.jar - -# Maven -target/ -dist/ - -# JetBrains IDE -.idea/ - -# Unit test reports -TEST*.xml - -# Generated by MacOS -.DS_Store - -# Generated by Windows -Thumbs.db - -# Applications -*.app -*.exe -*.war - -# Large media files -*.mp4 -*.tiff -*.avi -*.flv -*.mov -*.wmv - diff --git a/sample_django_project/library/__init__.py b/.nojekyll similarity index 100% rename from sample_django_project/library/__init__.py rename to .nojekyll diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 9919d1b..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,34 +0,0 @@ -default_language_version: - python: python3.10 -repos: - - repo: https://github.com/psf/black - rev: 22.3.0 # Replace by any tag/version: https://github.com/psf/black/tags - hooks: - - id: black - - repo: https://github.com/pycqa/isort - rev: "5.10.1" # Use the revision sha / tag you want to point at - hooks: - - id: isort - - repo: https://github.com/pycqa/flake8 - rev: "4.0.1" - hooks: - - id: flake8 - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 # Use the ref you want to point at - hooks: - - id: check-added-large-files - args: ["--maxkb=1024"] # throw an error if we try to commit a 1MB or greater file - - id: check-case-conflict # so we don't rename files that will break case insensitive filesystems - - id: check-merge-conflict # don't accidentally commit files with incomplete merges - - id: end-of-file-fixer # makes all files end in a newline - - id: mixed-line-ending # fixes mixed line endings automatically - - id: no-commit-to-branch - args: ["-b master"] # no commits to master - - repo: local - hooks: - - id: tests - name: run tests - always_run: true # Not required but ensures that the tests always run even if there are no matching. - entry: make test # The command that calls the tests. In this case, it's a command in a Makefile. - language: system # It's a system hook and no specific virtual environment will be provided for the hook's execution; - pass_filenames: false # Don't try to pass the names of the files that were changed to the hook entry; diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 93989d0..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "runserver", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/sample_django_project/manage.py", - "args": [ - "runserver" - ], - "env": { - "SECRET_KEY": "qwert" - }, - "django": true, - "justMyCode": true - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 72a7850..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "python.testing.pytestArgs": [ - "sample_django_project" - ], - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true, - "python.envFile": "${workspaceFolder}/sample_django_project/.env", -} \ No newline at end of file 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())
+