Effective Python was, as the name implies, Effective

Summary

I took a quick look at Effective Python. I think it's a very good book. I think I'm a beginner in Python. , I think that it plays a role of bridging beginners and intermediates more than enough because it contains things that I think about to some extent and fall into my intuition. I am a large-scale on Google. As a developer, there are some explanations that aren't intuitively useful to me as I'm writing a lot of small code, but it's not Brett Slatkin's fault, because I'm not at my own.

It's easy to forget just by reading it. I'd like to summarize some of the points that impressed me. The code used here is Github [ ^ 1].

Impression point summary (promotion)

Since some excerpts are posted, it may be difficult to grasp the context, but please buy it and read it.

Use the generator formula!

You can use generator expressions to process large numbers of inputs sequentially:

import random

with open('my_file.txt', 'w') as f:
    for _ in range(10):
        f.write('a' * random.randint(0, 100))
        f.write('\n')

it = (len(x) for x in open('my_file.txt'))
print(next(it))
print(next(it))

Doing this all at once with a list or tuple consumes a lot of memory. I think it's a fairly well-known method, but Expert Python Programming /books/books/detail/978-4-04-868629-7.shtml) was also impressive that the generator formula was strongly recommended.

Don't use for ... else, while ... else

It's a for ... else that doesn't seem to be useful, but it seems to be a best practice not to use it after all. The reason is that the context is different from the general ʻelse. If the previous block fails It's normal to jump to else, but for ... else executes a ʻelse block when the for statement completes successfully (without break). This impairs readability. That is.

When I first read it, I thought, "What's that? ...", but ** "Effective ..." consistently emphasizes "genericity." ** Programming The claim that usage that undermines the meaning of ʻelse` in general language should be avoided seems to be taken for granted now that we have read through it. I think it makes sense to read.

What is a closure?

Closures are a state-holding mechanism, so I'm not sure, so for example:

def closure():
    flag = False

    def getter():
        print(flag)
    return getter


func = closure()
func() # False

func () holds a variable called flag. It is convenient to use this technique to reduce global variables.

One thing to keep in mind is that sad things happen when assignments are involved:

def closure():
    flag = False

    def setter():
        flag = True
    setter()
    return flag


print(closure()) # False

I'm trying flag = True withsetter (), but the return value ofclosure ()is False, because the scope inside setter () does not have flag. Assignments have been treated as variable definitions in. If you want to take advantage of closures, you have to be very careful with assignments. To solve this problem, use nonlocal:

def closure():
    flag = False

    def setter():
        nonlocal flag
        flag = True
    setter()
    return flag


print(closure()) # True

This will refer to the scope one level higher. The root of this problem is that variable definitions and assignments have the same grammar in Python. To solve this, variable definitions are like Javascript. It seems that it was suggested to set var flag = False, but it seems that nonlocal was adopted in consideration of compatibility.

Closures are convenient, but using nonlocal in large functions makes it difficult to grasp, so it seems better to use classes in such cases.

Pay attention to the position of the keyword argument

In detail, keyword arguments must be specified after positional arguments:

def remainder(number, divisor):
    return number % divisor


remainder(20, 7)                # OK
remainder(20, divisor=7)        # OK
remainder(number=20, divisor=7) # OK
remainder(divisor=7, number=20) # OK
remainder(number=20, 7)         # NG

Don't nest dictionaries! Use class

Dictionaries are very good containers, but nesting them like dictionaries can be very difficult to maintain. Before that, split them into classes:

import collections
Grade = collections.namedtuple('Grade', ('score', 'weight'))


class Subject(object):
    def __init__(self):
        self._grades = []

    def report_grade(self, score, weight):
        self._grades.append(Grade(score, weight))

    def average_grade(self):
        total, total_weight = 0, 0
        for grade in self._grades:
            total += grade.score * grade.weight
            total_weight += grade.weight
        return total / total_weight


class Student(object):
    def __init__(self):
        self._subjects = {}

    def subject(self, name):
        if name not in self._subjects:
            self._subjects[name] = Subject()
        return self._subjects[name]

    def average_grade(self):
        total, count = 0, 0
        for subject in self._subjects.values():
            total += subject.average_grade()
            count += 1
        return total / count


class Gradebook(object):
    def __init__(self):
        self._students = {}

    def student(self, name):
        if name not in self._students:
            self._students[name] = Student()
        return self._students[name]


book = Gradebook()
albert = book.student('Albert Einstein')
math = albert.subject('Math')
math.report_grade(80, 0.10)
math.report_grade(80, 0.10)
math.report_grade(70, 0.80)
gym = albert.subject('Gym')
gym.report_grade(100, 0.40)
gym.report_grade(85, 0.60)
print(albert.average_grade())

It's a bit long, but let's read it hard. ** It seems to be one answer to the question "What is a class design that is easy to maintain and extend?" **

The layered structure of "1. Gradebook → 2. Students → 3. Subjects → 4. Scores" is realized by the instance variables (dictionaries) of each object holding the objects in the lower hierarchy. Each class It has a method to set an instance of a lower class to an instance variable, and there is also a method to calculate the average score. If you take a closer look, you can see that this code is highly extensible. If this is a single It is dangerous to try to realize it with a dictionary.

I'm not sure if the above code is intuitive and straightforward. I didn't immediately understand it. But once I knew it, I was convinced that it was a very simple idea. On the other hand, I could understand it. Some code still feels like "Why do you have such a messy implementation !?" ** Easy-to-understand code is not always good code. Easy-to-explain code is good code (probably). ** Zen of Python also says that:

Although that way may not be obvious at first unless you're Dutch. The method may be difficult to understand at first glance. It may be easy for only Dutch people to understand.

If the implementation is hard to explain, it's a bad idea. If it's difficult to explain what the code is, it's a bad implementation.

If the implementation is easy to explain, it may be a good idea. If you can easily explain the content of the code, it's probably a good implementation.

from The Zen of Python

Where do you use @ classmethod?

It seems to be good to use it for the method that creates its own object. Consider the following parent class / child class:

from tempfile import TemporaryDirectory

class GenericInputData(object):
    def read(self):
        raise NotImplementedError

    @classmethod
    def generate_inputs(cls, config):
        raise NotImplementedError


class PathInputData(GenericInputData):
    def __init__(self, path):
        super().__init__()
        self.path = path

    def read(self):
        return open(self.path).read()

    @classmethod
    def generate_inputs(cls, config):
        data_dir = config['data_dir']
        for name in os.listdir(data_dir):
            yield cls(os.path.join(data_dir, name))


class GenericWorker(object):
    def __init__(self, input_data):
        self.input_data = input_data
        self.result = None

    def map(self):
        raise NotImplementedError

    def reduce(self, other):
        raise NotImplementedError

    @classmethod
    def create_workers(cls, input_class, config):
        workers = []
        for input_data in input_class.generate_inputs(config): #Create an instance of the class taken as an argument
            workers.append(cls(input_data)) #Create your own instance
        return workers #Returns its own instance


class LineCountWorker(GenericWorker):
    def map(self):
        data = self.input_data.read()
        self.result = data.count('\n')

    def reduce(self, other):
        self.result += other.result  


with TemporaryDirectory() as tmpdir:
    config = {'data_dir': tmpdir}
    workers = LineCountWorker.create_workers(PathInputData, config)

This is also long, but do your best! There are two important points.

The first is that the class method of the class itself is responsible for creating the instance. ** When new child classes are added, it is the management of "where and who creates the instance of the class". In the above example, LineCountWorker.create_workers () creates its own instance under with, and also creates an instance of PathInputData in it [^ 2]. In other words, "where" and "who makes it" are clear.

The second is closely related to the first. It means that " cls is replaced by the called class. "It sounds obvious, but it's very important. In the above code, * * cls (input_data) defined inGenericWorker.create_workers ()is garbled asLineCountWorker (input_data)when called through its child class LineCountWorker. ** Thanks to this, Even if you create a new derived classHogeCountWorker whose parent class is GenericWorker, it is stillHogeCountWorker.create_workers ()that creates an instance of it, and a new create_workers method is created in HogeCountWorker. It is not necessary. Even though it is a method inherited from the parent class, when it is called from the derived class, it quickly changes to a method dedicated to that child! This is the "generic" that is often talked about in "Effective ...".

Promotion, again.

There are many other useful things such as tips related to iterators and generators and how to use @property. Please buy it and read it [^ 3].

Not so good

Effective Python is a great book, but there are some things I'm curious about. ** Translation quality. ** For example ...

Example 1
(original) You'd want a similar abstract interface for the MapReduce worker that consumes the input data in a standard way.
(Translation) Let's say you want a similar abstract interface for MapReduce Workers that consume input data as standard.
Example 2
(original) Here, I extend the InputData class with a generic class method that's responsible for creating new InputData instances using a common interface.
(Translation) Extend the InputData class and add a generic class method that is responsible for creating a new InputData instance with a common interface.

Hmm ... it's subtle. I felt a lot of doubts about whether I really understood and wrote the content. Especially, "Chapter 3: Class and Inheritance" was painful. I blamed my lack of understanding for translation. I wanted to ... no, well, I'm sorry that I'm not smart enough.

However, the content itself is still wonderful. Those who are good at English are recommended to read the original text.

in conclusion

I think that the most important thing in study books, not just programming, is an "excellent introductory book." There is a high barrier between beginners and beginners to see high school students who give up math and college students who drop out in C language. Is obvious.

Thankfully, Python is full of introductory books. If you have a large number of people, there will be excellent books among them. On the other hand, what about Japanese books for beginners and intermediates? There are many Python books from O'Reilly, but I think the only beginner's books that cover a wide range of people are "Effective Python" and "Practical Python3", and "Expert Python Programming" except for O'Reilly [^ 4]. I'm really happy that one of them, "Effective Python," was released with this perfection.

Reference books

--Brett Slatkin, Effective Python (O'Reilly Japan, 2016)

Recommended Posts

Effective Python was, as the name implies, Effective
Get the host name in Python
[Python] Get the variable name with str
Set the process name of the Python program
The file name was bad in Python and I was addicted to import
[Python] I tried substituting the function name for the function name
What! The unzipped file name was SJIS! command?
[Python] I tried to get the type name as a string from the type function
How to get the variable name itself in python
Output the time from the time the program was started in python
Python program that looks for the same file name
Specify MinGW as the compiler to use with Python
Find the maximum Python
[Python] Split the date
Name identification using python
Effective Python Memo Item 3
I was hooked for 2 minutes with the Python debugger pdb
[python] Change the image file name to a serial number
Get only the Python version (such as 2.7.5) on the CentOS 7 shell
Sort and output the elements in the list as elements and multiples in Python.
'De' =='De' is False! Actually, the second "de" was two letters. [Python]
Try to decipher the garbled attachment file name with Python