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].
Since some excerpts are posted, it may be difficult to grasp the context, but please buy it and read it.
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.
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.
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.
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
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
@ 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 class
HogeCountWorker 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 ...".
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].
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.
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.
--Brett Slatkin, Effective Python (O'Reilly Japan, 2016)
Recommended Posts