Table of Contents generated with DocToc
- Effective Python
- Pythonic
- The list of cutting
- List derivation
- The iteration
- A reverse iterative
try/except/else/finally
- function
- Use decorators
- Using generators
- iterable
- Using positional parameters
- Use keyword arguments
- Default values for parameters
- class
__slots__
__call__
@classmethod
&@staticmethod
- Create a context manager
- Pythonic
Effective Python
Some of them are from books: Effective Python & Python3 Cookbook, but have also been modified and added with my own understanding and best practices in use
The list of cutting
list[start:end:step]
- If you are cutting from the beginning of the list, ignore the 0 of the start bit, for example
list[:4]
- If you cut all the way to the end of the list, the 0 of the end bit is ignored, for example
list[3:]
- When cutting a list, there is no problem even if the start or end index is crossed
- List slicing does not change the original list. When indexes are left blank, a copy of the original list is generated
b = a[:]
assert b = = a and b is not a # trueCopy the code
- Use list derivations instead
map
andfilter
a = [1.2.3.4.5.6.7.8.9.10]
# use map
squares = map(lambda x: x ** 2, a)
# use list comprehension
squares = [x ** 2 for x in a]
#One big benefit is that list comprehensions can make judgments about values, such as
squares = [x ** 2 for x in a if x % 2 = = 0]
#If this is done using the map or filter methods, you need to write more functionsCopy the code
- Do not use list comprehensions that contain more than two expressions
#You have a nested list, and now you want to flatten out all of its elements
list =[[[1.2.3],
[4.5.6]]]#Use list derivations
flat_list = [x for list0 in list for list1 in list0 for x in list1]
# [1, 2, 3, 4, 5, 6]
#Poor readability and error prone. In this case, a normal loop is recommended
flat_list = []
for list0 in list:
for list1 in list0:
flat_list.extend(list1)Copy the code
- When there is a lot of data, the list derivation may consume a lot of memory, so generator expressions are recommended
#In the derivation of a list derivation, it is possible to create a new list with only one element for each value of the input sequence. Therefore, large amounts of data consume performance.
#Using generator expressions
list = (x ** 2 for x in range(0.1000000000))
#Generator expressions return iterators that generate values only on each invocation, thus avoiding memory footprintCopy the code
The iteration
- Used when you need to get index
enumerate
enumerate
You can accept the second argument as added during iterationindex
In the numerical
list = ['a'.'b'.'c'.'d']
for index, value in enumerate(list) :print(index)
# 0
# 1
# 2
# 3
for index, value in enumerate(list.2) :print(index)
# 2
# 3
# 4
# 5Copy the code
- with
zip
Iterate over both iterators at the same time
list_a = ['a'.'b'.'c'.'d']
list_b = [1.2.3]
#The list length varies, but the iteration stops when one of the lists is exhausted
for letter, number in zip(list_a, list_b):
print(letter, number)
# a 1
# b 2
# c 3Copy the code
zip
Traversal returns a tuple
a = [1.2.3]
b = ['w'.'x'.'y'.'z']
for i in zip(a,b):
print(i)
# (1, 'w')
# (2, 'x')
# (3, 'y')Copy the code
-
When the list is not long, as long as there is a depletion, no more iteration.
-
Else blocks after for and while loops
- cycleThe end of the normalAnd then it calls
else
The code in - Loop through
break
Out of the loop, it will not be executedelse
- Execute immediately if the sequence to be traversed is empty
else
- cycleThe end of the normalAnd then it calls
for i in range(2) :print(i)
else:
print('loop finish')
# 0
# 1
# loop finish
for i in range(2) :print(i)
if i % 2 = = 0:
break
else:
print('loop finish')
# 0Copy the code
For ordinary sequences (lists), we can iterate backwards using the built-in reversed() function:
list_example = [i for i in range(5)]
iter_example = (i for i in range(5)) #The iterator
set_example = {i for i in range(5)} #A collection of
#Normal forward iteration
# for i in list_example
#Reverse iteration using Reversed
for i in reversed(list_example):
print(i)
# 4
# 3
# 2
# 1
# 0
#It does not apply to collections and iterators
reversed(iter_example) # TypeError: argument to reversed() must be a sequenceCopy the code
You can also reverse the class by implementing the __reversed__ method in the class:
class Countdown:
def __init__(self.start) :self.start = start
#Positive iteration
def __iter__(self):
n = self.start
while n > 0:
yield n
n - = 1
#A reverse iterative
def __reversed__(self):
n = 1
while n < = self.start:
yield n
n + = 1
for i in reversed(Countdown(4)) :print(i)
# 1
# 2
# 3
# 4
for i in Countdown(4) :print(i)
# 4
# 3
# 2
# 1Copy the code
try/except/else/finally
- if
try
If no exception occurs in theelse
The code in else
Will be infinally
Run before- It will be executed eventually
finally
And can be cleaned up in it
Use decorators
Decorators are used to modify an existing function without changing the original function code. A common scenario is to add a debug sentence, or to add log monitoring to an existing function
Here’s an example:
def decorator_fun(fun) :def new_fun(*args.**kwargs) :print('current fun:', fun.__name__)
print('position arguments:', args)
print('key arguments:'.**kwargs)
result = fun(*args, **kwargs)
print(result)
return result
return new_fun
@decorator_fun
def add(a.b) :return a + b
add(3.2)
# current fun: add
# position arguments: (3, 2)
# key arguments: {}
# 5Copy the code
In addition, we can write a decorator that receives arguments, in effect nesting a function on top of the original decorator:
def read_file(filename='results.txt') :def decorator_fun(fun) :def new_fun(*args.**kwargs):
result = fun(*args, **kwargs)
with open(filename, 'a') as f:
f.write(result + '\n')
return result
return new_fun
return decorator_fun
#Use decorator times to enter parameters
@read_file(filename='log.txt')
def add(a.b) :return a + bCopy the code
But there’s a problem with using decorators like the one above:
@decorator_fun
def add(a.b) :return a + b
print(add.__name__)
# new_funCopy the code
This means that the original function has been replaced by the new_fun function in the decorator. Calling a decorated function is equivalent to calling a new function. When you view the arguments, comments, and even the function name of the original function, you only see the information about the decorator. To solve this problem, use the functools.wraps method that comes with Python.
Functools. wraps is a hack method that acts as a decorator for the function that will be returned inside the decorator. In other words, it is a decorator of a decorator, and it takes the original function as an argument. Its purpose is to retain various information about the original function, so that when we view the decorated original function, it can be exactly the same as the original function.
from functools import wraps
def decorator_fun(fun) :@wraps(fun)
def new_fun(*args.**kwargs):
result = fun(*args, **kwargs)
print(result)
return result
return new_fun
@decorator_fun
def add(a.b) :return a + b
print(add.__name__)
# addCopy the code
In addition, sometimes our decorators may do more than one thing, and events should be separated out as additional functions. But because it might be related only to the decorator, you can construct a decorator class at this point. The principle is very simple, mainly is to write the __call__ method in the class, so that the class can be called like a function.
from functools import wraps
class logResult(object) :def __init__(self.filename='results.txt') :self.filename = filename
def __call__(self.fun) :@wraps(fun)
def new_fun(*args.**kwargs):
result = fun(*args, **kwargs)
with open(filename, 'a') as f:
f.write(result + '\n')
return result
self.send_notification()
return new_fun
def send_notification(self) :pass
@logResult('log.txt')
def add(a.b) :return a + bCopy the code
Using generators
Consider using generators to override functions that return lists directly
#Defines a function that checks the index positions of all a's in a string and returns an array of all indexes
def get_a_indexs(string):
result = []
for index, letter in enumerate(string):
if letter = = 'a':
result.append(index)
return resultCopy the code
There are a few minor problems with this approach:
- Called each time a qualified result is obtained
append
Methods. But we’re not really focused on the method at all, it’s just a means to an end, it’s really just a matter ofindex
It’s good - The returned
result
I can continue to optimize - The data is there
result
Inside, if the amount of data is very large, it will take up more memory
Therefore, it is better to use a generator. A generator is a function that uses yield expressions. When a generator is called, it does not actually execute. Instead, it returns an iterator that advances the generator to the next yield expression each time the built-in next function is called on the iterator:
def get_a_indexs(string) :for index, letter in enumerate(string):
if letter = = 'a':
yield indexCopy the code
Once you get a generator, you can iterate over it normally:
string = 'this is a test to find a\ ' index'
indexs = get_a_indexs(string)
#You can iterate like this
for i in indexs:
print(i)
#Or it
try:
while True:
print(next(indexs))
except StopIteration:
print('finish!')
#The generator raises a StopIteration error if it continues to pass the next() value after fetching it
#But traversing through the for loop automatically catches this errorCopy the code
If you still need a list, you can call the list method with the result of the function call as an argument
results = get_a_indexs('this is a test to check a')
results_list = list(results)Copy the code
Note that with normal iterators, the same instance can only be iterated once, and repeated calls after one iteration are invalid.
#Define a function that iterates through a list of values
def loop(items) :for i in items:
yield i
foo = loop([1.2.3.4])
print(list(foo)) #And it completes the iteration
# output: [1, 2, 3, 4]
print(list(foo)) #Again, an iterative
# output: []Copy the code
To solve this problem, you can define an iterable container class:
class LoopIter(object) :def __init__(self.data) :self.data = data
#Must yield results in __iter__
def __iter__(self) :for index, letter in enumerate(self.data):
if letter = = 'a':
yield indexCopy the code
In this case, the class instance is iterated as many times as possible:
string = 'this is a test to find a\ ' index'
indexs = LoopIter(string)
print('loop 1')
for _ in indexs:
print(_)
# loop 1
# 8
# 23
print('loop 2')
for _ in indexs:
print(_)
# loop 2
# 8
# 23Copy the code
Note, however, that iterators that simply implement __iter__ methods can only be iterated through the for loop; To iterate through the next method, use iter:
string = 'this is a test to find a\ ' index'
indexs = LoopIter(string)
next(indexs) # TypeError: 'LoopIter' object is not an iterator
iter_indexs = iter(indexs)
next(iter_indexs) # 8Copy the code
Using positional parameters
In some cases, the number of arguments a method takes may not be certain, such as a summation method that takes at least two arguments:
def sum(a.b) :return a + b
#Normal use
sum(1.2) # 3
#But if I wanted to sum up a lot of numbers, it would be wrong to plug in all the arguments, and it would be too much trouble to plug in all the arguments
sum(1.2.3.4.5) # sum() takes 2 positional arguments but 5 were givenCopy the code
For functions that receive a variable number of arguments and do not care about the order in which they are passed in, the positional argument *args should be used:
def sum(*args):
result = 0
for num in args:
result + = num
return result
sum(1.2) # 3
sum(1.2.3.4.5) # 15
#Alternatively, you can simply bring in an array, destructing it with *
sum(*[1.2.3.4.5]) # 15Copy the code
Note, however, that args of variable length is first automatically converted to a tuple when passed to a function. This means that if you pass a generator into a function as an argument, the generator will first iterate over it, converting it to a tuple. This can consume a lot of memory:
def get_nums() :for num in range(10) :yield num
nums = get_nums()
sum(*nums) # 45
#However, when the number of traversals is large, it takes up a lot of memoryCopy the code
- Keyword arguments improve code readability
- You can provide default values for functions through keyword arguments
- Easy to expand function parameters
Defines functions that use only keyword arguments
- In the normal way, keyword arguments are not mandatory during the call
#Defines a method that iterates through an array to find indexes equal to (or not equal to) the target element
def get_indexs(array.target=''.judge=True) :for index, item in enumerate(array):
if judge and item = = target:
yield index
elif not judge and item ! = target:
yield index
array = [1.2.3.4.1]
#All of the following are possible
result = get_indexs(array, target=1.judge=True)
print(list(result)) #[0, 4)
result = get_indexs(array, 1.True)
print(list(result)) #[0, 4)
result = get_indexs(array, 1)
print(list(result)) #[0, 4)Copy the code
- usePython3How to enforce keyword arguments in
#Defines a method that iterates through an array to find indexes equal to (or not equal to) the target element
def get_indexs(array.*.target=''.judge=True) :for index, item in enumerate(array):
if judge and item = = target:
yield index
elif not judge and item ! = target:
yield index
array = [1.2.3.4.1]
#This is feasible
result = get_indexs(array, target=1.judge=True)
print(list(result)) #[0, 4)
#You can also ignore parameters that have default values
result = get_indexs(array, target=1)
print(list(result)) #[0, 4)
#An error occurs if no keyword argument is specified
get_indexs(array, 1.True)
# TypeError: get_indexs() takes 1 positional argument but 3 were givenCopy the code
- usePython2How to enforce keyword arguments in
#Defines a method that iterates through an array to find indexes equal to (or not equal to) the target element
#Using **kwargs, which represents receiving keyword arguments, the kwargs inside the function is a dictionary, and the keyword arguments passed in exist as key-value pairs
def get_indexs(array.**kwargs):
target = kwargs.pop('target'.'')
judge = kwargs.pop('judge'.True)
for index, item in enumerate(array):
if judge and item = = target:
yield index
elif not judge and item ! = target:
yield index
array = [1.2.3.4.1]
#This is feasible
result = get_indexs(array, target=1.judge=True)
print(list(result)) #[0, 4)
#You can also ignore parameters that have default values
result = get_indexs(array, target=1)
print(list(result)) #[0, 4)
#An error occurs if no keyword argument is specified
get_indexs(array, 1.True)
# TypeError: get_indexs() takes 1 positional argument but 3 were givenCopy the code
Default values for parameters
It’s a cliche: the default value of a function is set only once when the module is loaded and the function definition is read
That is, if you assign a dynamic value to a parameter (such as [] or {}), then if you assign another parameter to the parameter when calling the function later, the default value defined previously will change to the value assigned last time:
def get_default(value=[]) :return value
result = get_default()
result.append(1)
result2 = get_default()
result2.append(2)
print(result) # [1, 2]
print(result2) # [1, 2]Copy the code
Therefore, it is more recommended to use None as the default argument and assign it after checking within the function:
def get_default(value=None) :if value is None:
return []
return value
result = get_default()
result.append(1)
result2 = get_default()
result2.append(2)
print(result) # [1]
print(result2) # [2]Copy the code
__slots__
By default, Python uses a dictionary to hold instance attributes of an object. This allows us to dynamically add new attributes to class instances at runtime:
test = Test()
test.new_key = 'new_value'Copy the code
However, this dictionary wastes extra space – many times we don’t create as many attributes. So __slots__ tells Python not to use dictionaries but fixed collections to allocate space.
class Test(object) :#List all attributes
__slots__ = ['name'.'value']
def __init__(self.name='test'.value='0') :self.name = name
self.value = value
test = Test()
#Adding new attributes will cause an error
test.new_key = 'new_value'
# AttributeError: 'Test' object has no attribute 'new_key'Copy the code
By defining a __call__ method in a class, instances of the class can be called like normal functions.
class AddNumber(object) :def __init__(self) :self.num = 0
def __call__(self.num=1) :self.num + = num
add_number = AddNumber()
print(add_number.num) # 0
add_number() #Call like a method
print(add_number.num) # 1
add_number(3)
print(add_number.num) # 4Copy the code
The advantage of this approach is that you can store state through class attributes without having to create a closure or global variable.
@classmethod
& @staticmethod
Information:
- Python @classmethod and @staticmethod for beginner
- Difference between staticmethod and classmethod in python
@classMethod is similar to @staticMethod, but they are used in different scenarios.
- All ordinary methods inside a class are defined as
self
As the first argument, the instance scope is passed into the method when called through the instance. @classmethod
In order tocls
As the first argument, represents passing in the scope of the class itself. Whether called from a class or an instance of a class, the first argument passed in by default will be the class itself@staticmethod
You don’t need to pass in default arguments, just like a normal function
To see their usage scenarios through examples:
Suppose we need to create a class named Date to store year/month/day data
class Date(object) :def __init__(self.year=0.month=0.day=0) :self.year = year
self.month = month
self.day = day
@property
def time(self) :return "{year}-{month}-{day}".format(
year=self.year,
month=self.month,
day=self.day
)Copy the code
This code creates the Date class, which sets the day/month/year property at initialization, and sets a getter for property, which gets the stored time through time after instantiation:
date = Date('2016'.'11'.'09')
date.time # 2016-11-09Copy the code
But what if we want to change the way attributes are passed in? After all, it’s annoying to pass in year/month/day at initialization. Can you find a way to create a Date instance by passing in a string like 2016-11-09 without changing existing interfaces and methods?
You might think of something like this:
date_string = '2016-11-09'
year, month, day = map(str, date_string.split('-'))
date = Date(year, month, day)Copy the code
But not good enough:
- An extra method is written outside the class, and each time you format it, you get the parameters
- This method also only follows
Date
Class about - Did not solve the problem of passing too many arguments
We can create a new format string inside the class using @classMethod and return the method of the instance of the class:
#Add a classMethod to Date
@classmethod
def from_string(cls.string):
year, month, day = map(str, string.split('-'))
#Within ClassMethod you can call a method of a class through CLS, or even create an instance of it
date = cls(year, month, day)
return dateCopy the code
This way, we can create an instance by calling the from_string method from the Date class without invading or modifying the old instantiation:
date = Date.from_string('2016-11-09')
#The old instantiation method can still be used
date_old = Date('2016'.'11'.'09')Copy the code
Benefits:
- in
@classmethod
Inside, can passcls
Argument, obtaining the same convenience as when calling classes externally - The method can be further encapsulated here to improve reusability
- More in line with object-oriented programming
@staticMethod, because it is similar to a normal function, can put the helper method associated with this class in the class as @StaticMethod and call the method directly from the class.
#Add a StaticMethod to Date
@staticmethod
def is_month_validate(month) :return int(month) < = 12 and int(month) > = 1Copy the code
After placing the date-related helper class functions inside the Date class as @StaticMethod methods, these methods can be called from the class:
month = '08'
if not Date.is_month_validate(month):
print('{} is a validate month number'.format(month))Copy the code
Create a context manager
Context manager, colloquial description is: before the code block is executed, the preparation work; After the execution of the code block is complete, do the finishing touches. The with statement often comes with a context manager. Classic scenarios include:
with open('test.txt'.'r') as file:
for line in file.readlines():
print(line)Copy the code
With the with statement, the code opens the file and automatically closes the file at the end of the call or when an exception occurs in the read, completing the processing of the file after it has been read or written. Without the context manager, it would look like this:
file = open('test.txt'.'r')
try:
for line in file.readlines():
print(line)
finally:
file.close()Copy the code
Is it tedious? So the advantage of using a context manager is that it automatically handles the work when a block of code starts and finishes by calling our preset callbacks. By customizing the class’s __enter__ and __exit__ methods, we can customize a context manager.
class ReadFile(object) :def __init__(self.filename) :self.file = open(filename, 'r')
def __enter__(self) :return self.file
def __exit__(self.type.value.traceback) :#Type, value, and traceback represent the error type, value, and trace stack, respectively
self.file.close()
#Returning True means no errors are thrown
#Otherwise the error will be thrown by the with statement
return TrueCopy the code
The call can then be made as follows:
with ReadFile('test.txt') as file_read:
for line in file_read.readlines():
print(line)Copy the code
When called:
with
The statement is temporarily savedReadFile
Of the class__exit__
methods- And then call
ReadFile
Of the class__enter__
methods __enter__
Method opens the file and returns the result towith
statements- The result of the previous step is passed to
file_read
parameter - in
with
Statement of thefile_read
Parameter to operate, read each line - After reading,
with
Temporary before the statement is called__exit__
methods __exit__
Method closes the file
Note that in the __exit__ method, we close the file but return True, so the error is not thrown by the with statement. Otherwise, the with statement throws a corresponding error.