So what does this mean when someone says that foo looks like pythonic? What does it mean when someone looks into our code and says that it is unpythonic? Let's try to figure it out.
In the Python community, there is a
pythonic neologism, which can be interpreted differently, but in general it characterizes the style of the code. Therefore, the statement that any code is pythonic is equivalent to the statement that it is written in accordance with the Python idiom. Similarly, such a statement with respect to an interface, or some kind of functionality, means that it (she) agrees with Python's idioms and fits well with the ecosystem.
In contrast, the
unpythonic label means that the code is a crude attempt to write the code of some other programming language in Python syntax, and not an idiomatic transformation.
')
The concept of Pythonicity is closely related to the minimalist concept of Python and its departure from the principle “there are many ways to do this.” Unreadable code, or incomprehensible idioms - everything is unpythonic.
When moving from one language to another, some things need to be “unlearned”. What do we know from other programming languages ​​that will not be in place in Python?
Use the standard library
The standard library is our friend. Let's use her.
>>> foo = "/home/sa" >>> baz = "somefile" >>> foo + "/" + baz # unpythonic '/home/sa/somefile' >>> import os.path >>> os.path.join(foo, baz) # pythonic '/home/sa/somefile' >>>
Other useful functions in
os.path are
basename () ,
dirname (), and
splitext () .
>>> somefoo = list(range(9)) >>> somefoo [0, 1, 2, 3, 4, 5, 6, 7, 8] >>> import random >>> random.shuffle(somefoo) # pythonic >>> somefoo [8, 4, 5, 0, 7, 2, 6, 3, 1] >>> max(somefoo) # pythonic 8 >>> min(somefoo) # pythonic 0 >>>
There are many useful built-in functions that many people are unaware of for some reason. For example,
min () and
max () . The standard library includes many useful modules. For example,
random , which contains a bunch of functionality that people unknowingly implement independently.
Create empty lists, tuples, dictionaries, etc.
>>> bar = list() # unpythonic >>> type(bar) <class 'list'> >>> del bar >>> bar = [] # pythonic >>> type(bar) <class 'list'> >>> foo = {} >>> type(foo) <class 'dict'> >>> baz= set() # {} is a dictionary so we need to use set() >>> type(baz) <class 'set'> >>>
Using backslash
Because Python treats line breaks as an expression delimiter, and since Expressions very often do not fit into one line, many people do this:
if foo.bar()['first'][0] == baz.ham(1, 2)[5:9] and \ # unpythonic verify(34, 20) != skip(500, 360): pass
Using "\" is not a good idea. Such an approach can cause an unpleasant bug: a random space after a slash will make the string wrong. At best, we get a syntax error, but if the code presents something like this:
value = foo.bar()['first'][0]*baz.ham(1, 2)[5:9] \ # unpythonic + verify(34, 20)*skip(500, 360)
Then it will be just non-working. It is better to use the implicit continuation of the string in brackets. Such code is bulletproof:
value = (foo.bar()['first'][0]*baz.ham(1, 2)[5:9] # pythonic + verify(34, 20)*skip(500, 360))
Import
Do not use "
from foo import * ".
Here and
here you can find more detailed information.
General exceptions
Python has an “
except ”
clause that catches all exceptions. Because any error throws an exception, such code can make many programming errors look like runtime errors, and make it difficult to debug the program. The following example is exhaustive:
try: foo = opne("somefile") # misspelled "open" except: sys.exit("could not open file!")
The second line generates “
NameError ”, which will be caught, which is semantically incorrect, since “except” is written to catch “
IOError ”. It is better to write such code:
try: foo = opne("somefile") except IOError: sys.exit("could not open file")
When you run this code, Python will generate a “NameError”, and you will instantly see and correct the error.
Since "except" catches all exceptions, including "
SystemExit ", "
KeyboardInterrupt ", and "
GeneratorExit " (which are not in fact errors and should not be caught by user code), using the bare "except" is in any case a bad idea. In situations where we still need to cover all possible exceptions, we can use the base class for all exceptions - “
Exception ”.
We rarely need counters.
>>> counter = 0 # unpythonic >>> while counter < 10: ... # do some stuff ... counter += 1 ... ... >>> counter 10 >>> for counter in range(10): # pythonic ... # do some stuff ... pass ... ... >>>
Another example:
>>> food = ['donkey', 'orange', 'fish'] >>> for i in range(len(food)): # unpythonic ... print(food[i]) ... ... donkey orange fish >>> for item in food: # pythonic ... print(item) ... ... donkey orange fish >>>
Explicit iterators
Inside Python uses many iterators ... there should be no exceptions for loops:
>>> counter = 0 # unpythonic >>> while counter < len(somecontainer): ... callable_consuming_container_elements(somecontainer[counter]) ... counter += 1 ... ... >>> for item in somecontainer: # pythonic ... callable_consuming_container_elements(item) ... ... >>>
We can say that for simple things, we should not explicitly create iterators. There are a number of cases where explicit iterators will be useful. For example, when we process something, stop, do something else, then go back and continue. The iterator remembers our position, and that's fine:
>>> somecontainer = list(range(7)) >>> type(somecontainer) <class 'list'> >>> somecontainer [0, 1, 2, 3, 4, 5, 6] >>> somecontaineriterator = iter(somecontainer) >>> type(somecontaineriterator) <class 'list_iterator'>
Now we can start using our iterator:
>>> for item in somecontaineriterator: # start consuming the iterable somecontainer ... if item < 4: ... print(item) ... ... else: ... break # breaks out of the nearest enclosing for/while loop ... ... ... 0 1 2 3
Don't be fooled, the iterator stopped at “
somecontaineriterator [5] ”, which is 4, not 3. Let's see what happens next:
>>> print("Something unrelated to somecontaineriterator.") Something unrelated to somecontaineriterator. >>> next(somecontaineriterator) # continues where previous for/while loop left off 5 >>> next(somecontaineriterator) 6 >>> next(somecontaineriterator) Traceback (most recent call last): # we have exhausted the iterator File "<input>", line 1, in <module> StopIteration >>>
It may seem to some that this example is ambiguous, in fact it is not. The iterator in the loop passes an array, exits the loop by
break at index 5 (the value inside is 4). Then we perform some actions (we display the text in the console), and after that we continue to iterate through the iterator. That's all.
Assignment
Here you can read in detail.
Cycles only when really necessary
There are many cases for which we would use loop expressions in other programming languages, but in the case of Python, this is not necessary.
Python provides a lot of high-level functionality for operating with any objects. For sequences, these can be
zip () ,
min () ,
max () functions. Then, these are things like “list comprehensions,” generators, “set comprehensions,” etc.
The fact is that if we store our data in basic Python structures, such as lists, tuples, dictionaries, sets, etc., we get a bunch of functionality to work with them out of the box. Even if we need a specific structure, it is most likely not difficult to create it using the basic data structure. So what are the benefits. How can we get a list of some people's names stored in a disk file.
sa@wks:/tmp$ cat people.txt Dora John Dora Mike Dora Alex Alex sa@wks:/tmp$ python >>> with open('people.txt', encoding='utf-8') as a_file: # context manager ... { line.strip() for line in a_file } # set comprehension ... ... {'Alex', 'Mike', 'John', 'Dora'} >>>
No loops, user data structures, extra spaces and duplicates, all pythonic; -]
Tuples are not just read-only lists
This is a common misconception. Very often lists and tuples are used for the same purpose. Lists are designed to store data of the same type. While tuples are used to combine different types of data into a set. In other words
The whole is more than the sum of its parts.
- Aristotle (384 BC - 322 BC)
>>> person = ("Steve", 23, "male", "London") >>> print("{} is {}, {} and lives in {}.".format(person[0], person[1], person[2], person[3])) Steve is 23, male and lives in London. >>> person = ("male", "Steve", 23, "London") #different tuple, same code >>> print("{} is {}, {} and lives in {}.".format(person[0], person[1], person[2], person[3])) male is Steve, 23 and lives in London. >>>
The index in the tuple is meaningful. Let's compare these structures:
>>> foo = 2011, 11, 3, 15, 23, 59 >>> foo (2011, 11, 3, 15, 23, 59)
- The first of these, a tuple, is a structure where the position of an element has a certain meaning (the first element is a year).
- The second, a list, is a sequence in which the values ​​are functionally equivalent, the index has no meaning.
A great example of using both structures is the fetchmany () method from the Python DB API, which returns the result as a list of tuples.
Classes are not intended to group functionality.
C # and Java only contain code inside classes. As a result, there are utilitarian classes that contain some static methods. For example, the mathematical function
sin () . In Python, we simply use the top-level module:
sa@wks:/tmp$ echo -e 'def sin():\n pass' > foo.py; cat foo.py def sin(): pass sa@wks:/tmp$ python >>> import foo >>> foo.sin() >>>
Say no to getters and setters
The way to achieve encapsulation in Python is to use properties, not getters and setters. Using properties, we can change the attributes of the object and fix the implementation without affecting the code being called (read, stable API).
Functions are objects
In Python, everything is objects. Functions are also objects. Functions are objects that can be called.
>>> somefoo = [{'price': 9.99}, {'price': 4.99}, {'price': 10}] >>> somefoo [{'price': 9.99}, {'price': 4.99}, {'price': 10}] >>> def lookup_price(someobject): ... return someobject['price'] ... ... >>> somefoo.sort(key=lookup_price) # pass function object lookup_price >>> somefoo [{'price': 4.99}, {'price': 9.99}, {'price': 10}] # in-place sort of somefoo took place >>> type(somefoo) <class 'list'> >>> type(somefoo[0]) <class 'dict'> >>>
There is a
difference between
lookup_price and
lookup_price () - the latter calls the function, and the first is looking at the
binding named
lookup_price . This allows us to use functions as ordinary objects.