|
|
(20 intermediate revisions by one other user not shown) |
Line 1: |
Line 1: |
| = Python =
| | {{admon/important|This page is deprecated| All Fedora Modularity Documentation has moved to the new [https://docs.pagure.org/modularity/ Fedora Modularity Documentation website] with source hosted along side the code in the [https://pagure.io/modularity Fedora Modularity website git repository]}} |
| | |
| Most of our code is written in Python, so this document will concentrate on it.
| |
| | |
| == PEP 8: Official Python Style Guide ==
| |
| | |
| Fortunately, with PEP 8 there's an official [https://www.python.org/dev/peps/pep-0008/ Style Guide for Python Code]. All new Python code you submit should conform to it, unless you have good reasons to deviate from it, [https://www.python.org/dev/peps/pep-0008/#id15 for instance readability].
| |
| | |
| == Keep It Simple ==
| |
| | |
| The code you write now probably needs to be touched by someone else down the road, and that someone else might be less experienced than you, or have a terrible headache and be under pressure of time. So while a particular construct may be a clever way of doing something, a simple way of doing the same thing can be and often is preferrable.
| |
| | |
| == New-style classes ==
| |
| | |
| Python 2 and earlier knows two types of classes, old-style which have no base class, and new-style which have <code>object</code> as the base class. Because their behavior is slightly different in some places, and some things can't be done with old-style classes, we want to stick to new-style classes wherever possible.
| |
| | |
| The syntactical difference is that new-style classes have to explicitly be derived from <code>object</code> or another new-style class.
| |
| | |
| <pre>
| |
| # old-style classes
| |
| class OldFoo:
| |
| pass
| |
| | |
| class OldBar(OldFoo):
| |
| pass
| |
| | |
| # new-style classes
| |
| class NewFoo(object):
| |
| pass
| |
| | |
| class NewBar(NewFoo):
| |
| pass
| |
| </pre>
| |
| | |
| Python 3 only knows new-style classes and the requirement to explicitly derive from <code>object</code> was dropped. In projects that will only ever run on Python 3, it's acceptable not to explicitly derive classes without parents from <code>object</code>, but if in doubt, do it just the same.
| |
| | |
| == Idiomatic code ==
| |
| | |
| In Python, it's easy to inadvertently emulate idiomatic styles of other languages like C/C++ or Java. In cases where there are constructs "native" to the language, it's preferrable to use them.
| |
| | |
| Here are some examples:
| |
| | |
| === Looping ===
| |
| | |
| Languages like C normally use incremented indices to loop over arrays:
| |
| | |
| <pre>
| |
| float pixels[NUMBER_OF_PIXELS] = [...];
| |
| | |
| for (int i = 0; i < NUMBER_OF_PIXELS; i++)
| |
| {
| |
| do_something_with_a_pixel(pixels[i]);
| |
| }
| |
| </pre>
| |
| | |
| {{admon/warning|Looping C-style in Python|Please avoid looping over indices of sequences, rather than the sequences themselves in Python.}} | |
| | |
| Implementing the loop like this would give away that you've programmed in C or a similar language before:
| |
| | |
| <pre>
| |
| pixels = [...]
| |
| | |
| for i in range(len(pixels)):
| |
| do_something_with_a_pixel(pixels[i])
| |
| </pre>
| |
| | |
| {{admon/note|Looping over iterables in Python|In Python, you can simply iterate over many non-scalar data types.}}
| |
| | |
| Here's the "native" way to implement the above loop:
| |
| | |
| <pre>
| |
| pixels = [...]
| |
| | |
| for p in pixels:
| |
| do_something_with_a_pixel(p)
| |
| </pre>
| |
| | |
| {{admon/tip|Using <code>enumerate()</code>|If you need to keep track of the current count of looped-over items, use the <code>enumerate()</code> built-in.}}
| |
| | |
| It yields pairs of count and the current value like this:
| |
| | |
| <pre>
| |
| pixels = [...]
| |
| | |
| for i, p in enumerate(pixels):
| |
| print("Working on pixel no. {}".format(i + 1))
| |
| do_something_with_a_pixel(p)
| |
| </pre>
| |
| | |
| === Properties rather than explicit accessor methods ===
| |
| | |
| In order to allow future changes in how object attributes (member variables) are set, some languages encourage always using getter and/or setter methods. This is unnecessary in Python, as you can intercept access to an attribute by wrapping it into a [https://docs.python.org/2/howto/descriptor.html#properties property]. These allow having accessor methods without making the user of the class have to use them explicitly. This way you can validate values when an attribute is set, or translate back and forth between the interface used on the attribute and an internal representation.
| |
| | |
| ==== Validating a value when setting an attribute ====
| |
| | |
| To ensure that an <code>Employee</code> object only has positive values for its <code>salary</code> attribute, you'd put a property in its place which checks values before storing them in an attribute called e.g. <code>_salary</code>:
| |
| | |
| <pre>
| |
| class Employee(object):
| |
| | |
| @property
| |
| def salary(self):
| |
| return self._salary
| |
| | |
| @salary.setter
| |
| def salary(self, salary):
| |
| if salary <= 0:
| |
| raise ValueError("Salary must be positive.")
| |
| self._salary = salary
| |
| </pre>
| |
| | |
| {{admon/caution|Avoid recursion|In order to avoid endless recursion, you must use a different attribute than the one using the property to store actual values.}}
| |
| | |
| ==== Translating between attribute interface and internal representation ====
| |
| | |
| Take these classes of geometric primitives, <code>Point</code> and <code>Circle</code>:
| |
| | |
| <pre>
| |
| class Point(object):
| |
| def __init__(self, x, y):
| |
| self.x = x
| |
| self.y = y
| |
| | |
| class Circle(object):
| |
| def __init__(self, point, radius):
| |
| self.point = point
| |
| self.radius = radius
| |
| </pre>
| |
| | |
| If you wanted to add a <code>diameter</code> attribute, you can do so as a property which translates back and forth between it and the existing <code>radius</code> attribute:
| |
| | |
| <pre>
| |
| ...
| |
| class Circle(object):
| |
| def __init__(self, point, radius=None, diameter=None):
| |
| self.point = point
| |
| if (radius is None) == (diameter is None):
| |
| raise ValueError("Exactly one of radius or diameter must be set")
| |
| if radius is not None:
| |
| self.radius = radius
| |
| else:
| |
| self.diameter = diameter
| |
| | |
| @property
| |
| def diameter(self):
| |
| return self.radius * 2
| |
| | |
| @diameter.setter
| |
| def diameter(self, diameter):
| |
| self.radius = diameter / 2.0
| |
| ...
| |
| </pre>
| |
| | |
| Even setting <code>self.diameter</code> in the constructor goes by way of the property and therefore the setter method.
| |
| | |
| [[Category:Modularity]]
| |