In the previous parts we covered data types and operators, flow control, and functions. In this final chapter we look at **classes and objects**, **iterators**, **variable scope**, **modules and packages**, error handling with **try... except**, **reserved words**, and the **Zen of Python**.
Disclaimer: This post has been translated to English using a machine translation model. Please, let me know if you find any mistakes.
📚 **This post is part of the series _Complete Python Guide_**, divided into three chapters that are read in order:
> * Part 1: Data types
* Part 2: Operators, flow control, and functions
* 👉 **Part 3: Classes, objects, and advanced topics**
7. Classes and objects
Python is an object-oriented programming language. Almost everything in Python is an object, with its attributes and methods.
A class is like an object constructor or a "blueprint" for creating objects.
To create a class, the reserved word class is used
InputPythonclass Clase:variable = 'MaximoFN'Copied
Once the class is created, an object of that class can be created
InputPythonobjeto = Clase()Clase.variableCopied
'MaximoFN'
Normally, classes have an initial function, which is executed when an object of the class is created. This function is called *dunder init* and is written __init__(). The *dunder init* function must always be passed the variable self, which indicates the class itself, and then the variables that you want to pass
With this function, class variables are usually initialized, or the code that is needed when an object of the class is created is executed.
InputPythonclass Persona:def __init__(self, nombre, edad):self.nombre = nombreself.edad = edadobjeto_persona = Persona("Miguel", 36)print(objeto_persona.nombre)print(objeto_persona.edad)Copied
Miguel36
In addition to the initial *dunder init* function, more functions can be created. These functions are called *methods* of the class. The variable self must always be passed to these *methods*
InputPythonclass Persona:def __init__(self, nombre, edad):self.nombre = nombreself.edad = edaddef saludar(self):print(f'Hola mi nombre es {self.nombre} y tengo {self.edad} años')objeto_persona = Persona("Miguel", 36)objeto_persona.saludar()Copied
Hola mi nombre es Miguel y tengo 36 años
The self variable does not have to be called self; it can have any name, but within each class it has to always be the same. However, by convention, self is usually used
InputPythonclass Persona:def __init__(yo_mismo, nombre, edad):yo_mismo.nombre = nombreyo_mismo.edad = edaddef saludar(yo_mismo):print(f'Hola mi nombre es {yo_mismo.nombre} y tengo {yo_mismo.edad} años')objeto_persona = Persona("Miguel", 36)objeto_persona.saludar()Copied
Hola mi nombre es Miguel y tengo 36 años
Can the variables of the objects be modified
InputPythonobjeto_persona.nombre = 'Marta'objeto_persona.saludar()Copied
Hola mi nombre es Marta y tengo 36 años
Even remove them
InputPythondel objeto_persona.nombreCopied
The entire object can also be deleted
InputPythondel objeto_personaCopied
If, for example, we want to define the structure of the class, but we do not want to code its internals for the moment, we can use pass
InputPythonclass Persona:passobjeto_persona = Persona()Copied
7.1. Inheritance
Inheritance allows us to define a class that inherits all the methods and properties of another class.
The **parent class** is the class from which inheritance occurs, also called the **base class**.
The **child class** is the class that inherits from another class, also called a **derived class**.
We create a parent class
InputPythonclass Persona:def __init__(self, nombre, apellido):self.nombre = nombreself.apellido = apellidodef imprimir_nombre(self):print(f'Me llamo {self.nombre} {self.apellido}')objeto_padre = Persona("Laura", "Perez")objeto_padre.imprimir_nombre()Copied
Me llamo Laura Perez
To create the child class, you must indicate in parentheses, when declaring the class, which class it inherits from
InputPythonclass Estudiante(Persona):passCopied
And when creating the object of the child class, the parameters needed by the parent class are passed to it
InputPythonobjeto_hijo = Estudiante("Mariano", "Sanz")objeto_hijo.imprimir_nombre()Copied
Me llamo Mariano Sanz
So far, the child class has inherited the functions of the parent class, but we can modify them by rewriting them. For example, by rewriting the *dunder init* function.
If the *dunder init* function is overridden, and we want the parent class's *dunder init* function to be called, we have to call it.
For this, there are two ways, one is by using the parent class name. In this case, you need to pass the self variable.
InputPythonclass Estudiante(Persona):def __init__(self, nombre, apellido):Persona.__init__(self, nombre, apellido)objeto_hijo = Estudiante("Mariano", "Sanz")objeto_hijo.imprimir_nombre()Copied
Me llamo Mariano Sanz
Another way is by using super(), in this case there is no need to pass the self variable to it
InputPythonclass Estudiante(Persona):def __init__(self, nombre, apellido):super().__init__(nombre, apellido)objeto_hijo = Estudiante("Mariano", "Sanz")objeto_hijo.imprimir_nombre()Copied
Me llamo Mariano Sanz
By modifying the functions, new code can be added
InputPythonclass Estudiante(Persona):def __init__(self, nombre, apellido, curso):Persona.__init__(self, nombre, apellido)self.curso = cursodef imprimir_nombre(self):Persona.imprimir_nombre(self)print(f'Estoy en el curso número {self.curso}')objeto_hijo = Estudiante("Mariano", "Sanz", 4)objeto_hijo.imprimir_nombre()Copied
Me llamo Mariano SanzEstoy en el curso número 4
Finally, new methods can be added
InputPythonclass Estudiante(Persona):def __init__(self, nombre, apellido, curso):Persona.__init__(self, nombre, apellido)self.curso = cursodef imprimir_nombre(self):Persona.imprimir_nombre(self)print(f'Estoy en el curso número {self.curso}')def imprimir_estudiante(self):print(f"Soy un estudiante del curso número {self.curso}")objeto_hijo = Estudiante("Mariano", "Sanz", 4)objeto_hijo.imprimir_nombre()objeto_hijo.imprimir_estudiante()Copied
Me llamo Mariano SanzEstoy en el curso número 4Soy un estudiante del curso número 4
7.2. Operator overloading
We can define basic operations, such as addition, between several objects of a class. For example, if we have a class that represents a vector, we can define addition and multiplication between objects of that class
InputPythonclass Vector:def __init__(self, x, y):self.x = xself.y = ydef __add__(self, other):return Vector(self.x + other.x, self.y + other.y)def __mul__(self, other):return Vector(self.x * other.x, self.y * other.y)def __str__(self):return f"Vector ({self.x}, {self.y})"v1 = Vector(1, 2)v2 = Vector(3, 4)print(v1 + v2) # Vector (4, 6)print(v1 * v2) # Vector (3, 8)Copied
Vector (4, 6)Vector (3, 8)
All possible operator overloads are:
__add__(self, other): overloads the addition operator (+).__sub__(self, other): overloads the subtraction operator (-).__mul__(self, other): overloads the multiplication operator (*).__truediv__(self, other): overloads the division operator (/).__floordiv__(self, other): overloads the floor division operator (//).__mod__(self, other): overloads the modulo operator (%).__divmod__(self, other): overloads thedivmod()function.__pow__(self, other): overloads the power operator (**).__lshift__(self, other): overloads the left shift operator (<<).__rshift__(self, other): overloads the right shift operator (>>).__and__(self, other): overloads theandoperator (&).__or__(self, other): overloads theoroperator (|).__xor__(self, other): overloads the xor operator (^).*__lt__(self, other): overloads the less-than comparison operator (<).__le__(self, other): overloads the less than or equal to comparison operator (<=).__eq__(self, other): overloads the equality comparison operator (==).__ne__(self, other): overloads the not-equal comparison operator (!=).__gt__(self, other): overloads the greater-than comparison operator (>).__ge__(self, other): overloads the greater than or equal to comparison operator (>=).__neg__(self): overloads the negation operator (-).__pos__(self): overloads the unary plus operator (+).__abs__(self): overloads theabs()function.__invert__(self): overloads the inversion operator (~).__complex__(self): overloads thecomplex()function.__int__(self): overloads theint()function.__float__(self): overrides thefloat()function.
7.3. Custom iterators
As we have seen in the section 2 (Tipos de datos de Python), there are some data types that can be iterated over. But we can create our own iterable class, as long as it has the functions __len__ and __getitem__
InputPythonclass custonIterator:def __init__(self, n):self.items = [i for i in range(n)]def __len__(self):return len(self.items)def __getitem__(self, index):return self.items[index]iterator = custonIterator(10)print(len(iterator)) # 10print(iterator[0]) # 0print(iterator[1]) # 1Copied
1001
Now we can iterate over the object of our class with for loops, for example
InputPythonfor i in iterator:print(i, end=" ") # 0 1 2 3 4 5 6 7 8 9Copied
0 1 2 3 4 5 6 7 8 9
7.4. Calling objects as functions
It may be useful to invoke an object from a function as if it were a class. This can be achieved by adding the __call__ function to the class
InputPythonclass potencia:def __init__(self, base):self.base = basedef __call__(self, potencia):return self.base ** potenciapotencia_cuadrado = potencia(2)print(potencia_cuadrado(3)) # 8Copied
8
7.5. Private attributes and functions
When we create a class, we can make some attributes or functions private so that they cannot be accessed from outside the class; to do this, we need to add __ before the attribute or method
InputPythonclass Privados:def __init__(self):self.publico = "Soy público"self.__privado = "Soy privado"def getPrivado(self):return self.__privadodef setPrivado(self, valor):self.__privado = valordef __funcion_privada(self):return "Soy una función privada"def funcion_publica(self):return self.__funcion_privada()privados = Privados()print("Acceso al atributo publico: ", end="")try:print(f"{privados.publico}")except:print(" No se puede acceder al atributo privado")print("Acceso al atributo privado: ", end="")try:print(f"{privados.__privado}")except:print(" No se puede acceder al atributo privado")print("Acceso al atributo privado mediante el accesor: ", end="")try:print(f"{privados.getPrivado()}")except:print(" No se puede acceder al atributo privado mediante el accesor")print("Llamada a la función privada: ", end="")try:print(f"{privados.__funcion_privada()}")except:print(" No se puede llamar a la función privada")print("Llamada a la función pública: ", end="")try:print(f"{privados.funcion_publica()}")except:print(" No se puede llamar a la función pública")Copied
Acceso al atributo publico: Soy públicoAcceso al atributo privado: No se puede acceder al atributo privadoAcceso al atributo privado mediante el accesor: Soy privadoLlamada a la función privada: No se puede llamar a la función privadaLlamada a la función pública: Soy una función privada
8. Iterators
An iterator is an object that contains a countable number of values.
An iterator is an object that can be iterated over, which means it can go through all the elements.
Technically, in Python, an iterator is an object that implements the iterator protocol, which consists of the __iter__() and __next__() methods.
Lists, tuples, dictionaries and sets are all iterable objects. They are iterable containers from which an iterator can be obtained.
All these objects have an iter() method that is used to obtain an iterator:
InputPythontupla = ("manzana", "plátano", "cereza")iterable = iter(tupla)print(next(iterable))print(next(iterable))print(next(iterable))Copied
manzanaplátanocereza
InputPythonstring = "plátano"iterable = iter(string)print(next(iterable), end=' ')print(next(iterable), end=' ')print(next(iterable), end=' ')print(next(iterable), end=' ')print(next(iterable), end=' ')print(next(iterable), end=' ')print(next(iterable), end=' ')Copied
p l á t a n o
The for loop actually creates an iterator object and executes the next() method on each iteration.
InputPythontupla = ("manzana", "plátano", "cereza")for x in tupla:print(x)Copied
manzanaplátanocereza
InputPythonstring = "plátano"for x in string:print(x, end=' ')Copied
p l á t a n o
8.1. Create an iterator object
To create an object/class as an iterator, you need to implement the __iter__() and __next__() methods.
InputPythonclass Numeros:def __iter__(self):self.a = 1return selfdef __next__(self):x = self.aself.a += 1return xobjeto_iterador = Numeros()iterador = iter(objeto_iterador)print(next(iterador), end=' ')print(next(iterador), end=' ')print(next(iterador), end=' ')print(next(iterador), end=' ')print(next(iterador), end=' ')Copied
1 2 3 4 5
The previous example would continue forever if it had enough next() calls, or if it were used in a for loop.
To prevent the iteration from continuing forever, we can use the StopIteration statement.
In the __next__() method, we can add a termination condition to generate an error if the iteration is performed a specific number of times:
InputPythonclass Numeros:def __iter__(self):self.a = 1return selfdef __next__(self):if self.a <= 20:x = self.aself.a += 1return xelse:raise StopIterationobjeto_iterador = Numeros()iterador = iter(objeto_iterador)for x in iterador:print(x, end=' ')Copied
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
8.2. Iterate by obtaining the index and the value
We can iterate over an iterable object, obtaining its index and value in each iteration using the enumerate() method
InputPythonstring = "MaximoFN"for index, valor in enumerate(string):print(f"En la posición {index}, está el caracter {valor}")Copied
En la posición 0, está el caracter MEn la posición 1, está el caracter aEn la posición 2, está el caracter xEn la posición 3, está el caracter iEn la posición 4, está el caracter mEn la posición 5, está el caracter oEn la posición 6, está el caracter FEn la posición 7, está el caracter N
8.3. Iterate simultaneously over two iterable objects
If we have two iterable objects of the same length, we can iterate over both at the same time using the zip() method
InputPythonstring1 = 'MaximoFN__'string2 = 'PythonPost'if len(string1) == len(string2):for valor1, valor2 in zip(string1, string2):print(f"En el primer string hay {valor1}, en el segundo string hay {valor2}")Copied
En el primer string hay M, en el segundo string hay PEn el primer string hay a, en el segundo string hay yEn el primer string hay x, en el segundo string hay tEn el primer string hay i, en el segundo string hay hEn el primer string hay m, en el segundo string hay oEn el primer string hay o, en el segundo string hay nEn el primer string hay F, en el segundo string hay PEn el primer string hay N, en el segundo string hay oEn el primer string hay _, en el segundo string hay sEn el primer string hay _, en el segundo string hay t
9. Scope of variables
A variable is only available within the region in which it is created. This is called *scope*
9.1. Local scope
A variable created inside a function belongs to that function’s local scope and can only be used within that function.
InputPythondef funcion():x = 300print(x)funcion()Copied
300
The variable x is not available outside the function, but it is available to any function inside it
InputPythondef funcion():x = 300def funcion_interna():print(x)funcion_interna()funcion()Copied
300
9.2. Global Scope
A variable created in the main body of Python code is a global variable and belongs to the global scope.
Global variables are available from any scope, global and local.
InputPythonx = 300def funcion():print(f'Ámbito local: {x}')funcion()print(f'Ámbito global: {x}')Copied
Ámbito local: 300Ámbito global: 300
If two variables are created, one global and one local, both with the same name, Python will create them as two different variables
InputPythonx = 300def funcion():x = 200print(f'Variable local: {x}')funcion()print(f'Variable global: {x}')Copied
Variable local: 200Variable global: 300
If a global variable needs to be created, but it is declared in the local scope, the global keyword can be used.
The global keyword makes the variable global.
InputPythondef funcion():global xx = 300funcion()print(f'Variable global: {x}')Copied
Variable global: 300
In addition, the use of the global keyword allows a change to be made to a global variable within a function.
InputPythonx = 300def funcion():global xx = 200funcion()print(f'Variable global: {x}')Copied
Variable global: 200
10. Modules
A module is a file that contains a set of functions that you want to include in your application.
To create a module, simply save the code you want in a file with the .py file extension
Tip: In Jupyter notebooks (Colab is an online Jupyter notebook), if we type the character
!before a command, we can execute terminal commands
First, let's see which directory we're in; for that, we use the pwd command (*print working directory*)
InputPython!pwdCopied
/home/wallabot/Documentos/web/portafolio/posts
Let's create a folder to create our modules with the mkdir (make directory) command
InputPython!mkdir introduccion_pythonCopied
Next, let's see which files are in our folder. We will do this using the ls (*list*) command
InputPython!ls introduccion_pythonCopied
We see that it is empty, we create a new .py file in which we are going to create our module
InputPython%%writefile introduccion_python/modulo1.pydef funcion_del_modulo(nombre):print("Hola, " + nombre)Copied
Writing introduccion_python/modulo1.py
Let's take another look at which files are in our folder
InputPython!ls introduccion_pythonCopied
modulo1.py __pycache__
We see that a file módulo1.py has been created. We can already use it
To use an external module, you have to use the word import. To use the module's functions, you must first write the name of the module, a ., and then the name of the function you want to use.
InputPythonimport introduccion_python.modulo1introduccion_python.modulo1.funcion_del_modulo('MaximoFN')Copied
Hola, MaximoFN
If we want the module to have a specific name within our code, we can use the as keyword.
InputPythonimport introduccion_python.modulo1 as mod1mod1.funcion_del_modulo('MaximoFN')Copied
Hola, MaximoFN
If the module has several functions, but we only want to import one, we can do so by using the from and import keywords. The syntax would be
from <module> import <function>In this case, it is not necessary to indicate the module name when calling the function
InputPython%%writefile introduccion_python/modulo2.pydef funcion1_del_modulo(nombre):print("Hola, " + nombre + ", funcion 1")def funcion2_del_modulo(nombre):print("Hola, " + nombre + ", funcion 2")def funcion3_del_modulo(nombre):print("Hola, " + nombre + ", funcion 3")Copied
Writing introduccion_python/modulo2.py
InputPythonfrom introduccion_python.modulo2 import funcion2_del_modulofuncion2_del_modulo('MaximoFN')Copied
Hola, MaximoFN, funcion 2
Not only can we use modules created by us, but also modules already installed (built-in modules)
For example, we can use the platform module
InputPythonimport platformx = platform.system()xCopied
'Linux'
10.1. Entry points: files as modules and not as scripts
Let's now create a file called módulo3.py
InputPython%%writefile introduccion_python/modulo3.pyprint("Hola desde modulo3")def funcion_del_modulo():return "Hola desde la función del modulo3"Copied
Overwriting introduccion_python/modulo3.py
If we now import modulo3.py to use the function funcion_del_modulo, let's see what happens
InputPythonimport introduccion_python.modulo3 as mod3print(mod3.funcion_del_modulo())Copied
Hola desde modulo3Hola desde la función del modulo3
We see that the print in modulo3.py has been executed, but that is not what we wanted; this is because when the file modulo3.py is called, Python executes it as a script
But what happens if we want to run introduccion_python/main.py as a script?
InputPython!python introduccion_python/modulo3.pyCopied
Hola desde modulo3
We see that only the print is executed, but not the funcion_del_modulo function. If we want to have the dual functionality of the modulo3.py file, that is, to be able to import it from another module without it being executed as a script and to run it on its own, and to have the function we want executed, an entry point is used. That is, use the condition if __name__ == '__main__': and then indicate what we want to be executed. Let’s see it with an example, I’m going to rewrite the modulo3.py file
InputPython%%writefile introduccion_python/modulo3.pyprint("Hola desde modulo3")def funcion_del_modulo():return "Hola desde la función del modulo3"if __name__ == "__main__":funcion_del_modulo()Copied
Overwriting introduccion_python/modulo3.py
If I now call main.py from another module, the print will no longer be executed
InputPythonimport introduccion_python.modulo3 as mod3print(mod3.funcion_del_modulo())Copied
Hola desde la función del modulo3
And if I run it as a standalone script, the module_function function will be executed
InputPython!python introduccion_python/modulo3.pyCopied
Hola desde modulo3
11. Packages
In Python, we can create our own packages; to do this, we create a folder with the package name
InputPython!mkdir mi_paquete_de_pythonCopied
We now create two files inside
InputPython!touch mi_paquete_de_python/modulo1.py mi_paquete_de_python/modulo2.pyCopied
And we write in them
InputPython%%writefile mi_paquete_de_python/modulo1.pydef funcion1():print("Hola desde la función 1 del módulo 1")def funcion2():print("Hola desde la función 2 del módulo 1")Copied
Overwriting mi_paquete_de_python/modulo1.py
InputPython%%writefile mi_paquete_de_python/modulo2.pydef funcion1():print("Hola desde la función 1 del módulo 2")def funcion2():print("Hola desde la función 2 del módulo 2")Copied
Overwriting mi_paquete_de_python/modulo2.py
Now we can call the functions of our package
InputPythonfrom mi_paquete_de_python import modulo1 as mod1from mi_paquete_de_python import modulo2 as mod2mod1.funcion1()mod1.funcion2()mod2.funcion1()mod2.funcion2()Copied
Hola desde la función 1 del módulo 1Hola desde la función 2 del módulo 1Hola desde la función 1 del módulo 2Hola desde la función 2 del módulo 2
But what happens if our package has dozens of files with functions that we want to use? We would have to import all the files one by one. To avoid this, you can create an __init__.py file inside the package where all this file importing is done
InputPython!touch mi_paquete_de_python/__init__.pyCopied
InputPython%%writefile mi_paquete_de_python/__init__.pyimport modulo1import modulo2Copied
Overwriting mi_paquete_de_python/__init__.py
Now we can simply import our package, since all modules have already been imported internally.
InputPythonimport mi_paquete_de_python as mi_paquetemi_paquete.modulo1.funcion1()mi_paquete.modulo1.funcion2()mi_paquete.modulo2.funcion1()mi_paquete.modulo2.funcion2()Copied
Hola desde la función 1 del módulo 1Hola desde la función 2 del módulo 1Hola desde la función 1 del módulo 2Hola desde la función 2 del módulo 2
In this way we only have to do an import
12. Try... except
When an error occurs, or an exception as it is really called, Python will normally catch it and generate an error message.
These exceptions can be handled using the try and except statements
InputPythontry:print(variable_no_declarada)except:print("Ha ocurrido una excepción")Copied
Ha ocurrido una excepción
Given that the try block generates an error, then the except block will be executed
Without the try block, the program would crash and generate an error
You can define as many exception blocks as you want, for example, if you want to execute a special code block for a special type of error
InputPythontry:print(variable_no_declarada)except NameError:print("La variable 'variable_no_declarada' no está definida")except:print("Algo inesperado ha ocurrido")Copied
La variable 'variable_no_declarada' no está definida
The word else can be used to indicate the case in which no error has occurred.
InputPythontry:print('MaximoFN')except NameError:print("Ha ocurrido una excepción")else:print('Todo OK')Copied
MaximoFNTodo OK
with the word finally a piece of code will be executed at the end whether an exception occurred or not
InputPythontry:print(variable_no_declarada)except:print("Ha ocurrido una excepción")finally:print("'try except' finallizado")Copied
Ha ocurrido una excepción'try except' finallizado
This can be useful for closing objects and cleaning up resources
InputPythonclass Clase:variable = 'MaximoFN'objeto = Clase()try:print(Clase.mi_variable)except:print("Ha ocurrido una excepción")finally:del objetoCopied
Ha ocurrido una excepción
12.1. Create an exception
As a Python developer, you can choose to raise an exception if a condition occurs.
To throw (or generate) an exception, you have to use the raise keyword
InputPythondef division(numerador, denominador):if denominador == 0:raise Exception("El denominador no puede ser 0")return numerador/denominadorprint(division(10, 0))Copied
---------------------------------------------------------------------------Exception Traceback (most recent call last)<ipython-input-16-33fb6066fa78> in <module>5 return numerador/denominador6----> 7 print(division(10, 0))<ipython-input-16-33fb6066fa78> in division(numerador, denominador)1 def division(numerador, denominador):2 if denominador == 0:----> 3 raise Exception("El denominador no puede ser 0")45 return numerador/denominadorException: El denominador no puede ser 0
You can define what type of error to generate and the text that will be shown to the user.
InputPythondef division(numerador, denominador):if denominador == 0:raise TypeError("El denominador no puede ser 0")return numerador/denominadorprint(division(10, 0))Copied
---------------------------------------------------------------------------TypeError Traceback (most recent call last)<ipython-input-17-26bfa63ae44c> in <module>5 return numerador/denominador6----> 7 print(division(10, 0))<ipython-input-17-26bfa63ae44c> in division(numerador, denominador)1 def division(numerador, denominador):2 if denominador == 0:----> 3 raise TypeError("El denominador no puede ser 0")45 return numerador/denominadorTypeError: El denominador no puede ser 0
13. Keywords or reserved words
During this post, several Python reserved words or keywords have appeared; these are a series of words reserved by Python.
Below is a list of the keywords
InputPythonimport keywordkeyword.kwlistCopied
['False','None','True','and','as','assert','async','await','break','class','continue','def','del','elif','else','except','finally','for','from','global','if','import','in','is','lambda','nonlocal','not','or','pass','raise','return','try','while','with','yield']
14. The Zen of Python
By importing the this module, we can read Python's zen, that is, its philosophy or principles
InputPythonimport thisCopied
The Zen of Python, by Tim PetersBeautiful is better than ugly.Explicit is better than implicit.Simple is better than complex.Complex is better than complicated.Flat is better than nested.Sparse is better than dense.Readability counts.Special cases aren't special enough to break the rules.Although practicality beats purity.Errors should never pass silently.Unless explicitly silenced.In the face of ambiguity, refuse the temptation to guess.There should be one-- and preferably only one --obvious way to do it.Although that way may not be obvious at first unless you're Dutch.Now is better than never.Although never is often better than *right* now.If the implementation is hard to explain, it's a bad idea.If the implementation is easy to explain, it may be a good idea.Namespaces are one honking great idea -- let's do more of those!