Body:
This article shows some of the advanced Python design structures and how they are used. In your daily work, you can choose the right data structure, such as the requirements for fast lookup, the requirement for data consistency, or the requirements for indexing, as well as the appropriate combination of data structures to generate logical and Easy-to-understand data models. Python's data structure clause is intuitive and provides a wide range of optional operations. This guide attempts to put together most commonly used data structure knowledge and provides an exploration of its best usage.
deduced formula (comprehensions)
If you've been using Python for a long time, you should at least have heard of list derivation (listing comprehensions). This is a way to place a for loop, an if expression, and an assignment statement in a single statement. In other words, you can map or filter a list through an expression.
A list derivation consists of the following sections:
- An input sequence
- A variable that represents the input sequence member
- An optional assertion expression
- An output expression that transforms a member in an input sequence that satisfies an assertion expression into an output list member
For example, we need to generate a new sequence of all the squares of integers greater than 0 from an input list, which you might write:
num = [1, 4, -5, ten,-7, 2, 3,-1]
filtered_and_squared = [] for number in
num:
If number > 0:
filtered _and_squared.append (Number * * 2)
print filtered_and_squared
# [1, 16, 100, 4, 9]
It's simple, isn't it? But there are 4 lines of code, two layers of nesting plus a completely unnecessary append operation. And if you use the filter, lambda, and map functions, you can greatly simplify your code:
num = [1, 4, -5, ten,-7, 2, 3,-1]
filtered_and_squared = map (Lambda x:x * * 2, filter (lambda x:x > 0, num))
Print filtered_and_squared
# [1, 16, 100, 4, 9]
Well, this way the code will unfold horizontally. So is it possible to continue simplifying the code? List deduction can give us the answer:
num = [1, 4, -5, ten,-7, 2, 3,-1]
filtered_and_squared = [x**2 for x in num if x > 0]
print filtered_and_sq uared
# [1, 16, 100, 4, 9]
- The iterator (iterator) traverses each member x of the input sequence num
- An assertion determines whether each member is greater than 0
- If the member is greater than 0, it is sent to the output expression, after which the square becomes a member of the output list.
The list derivation is encapsulated in a list, so it is obvious that it can generate a new list immediately. There is only one type function call without an implicit invocation of the lambda function, and the list derivation uses a regular iterator, an expression, and an if expression to control optional arguments.
On the other hand, list derivation may also have some negative effects, that is, the entire list must be loaded into memory at once, which is not a problem for the example mentioned above, or even a few times after it is enlarged. But always reach the limit, memory will always run out.
For the above problem, the generator (generator) can be well resolved. Instead of loading the entire list into memory at once, the builder expression generates a Generator object (generator objector), so only one list element is loaded at a time.
A builder expression has almost the same syntactic structure as a list derivation, except that the builder expression is surrounded by parentheses, not square brackets:
num = [1, 4, -5, ten,-7, 2, 3,-1]
filtered_and_squared = (x**2 for x in num if x > 0)
print filtered_and_squ Ared
# <generator Object <genexpr> at 0x00583e18> for
item in filtered_and_squared:
print item< c7/># 1, 16, 100 4,9
This is a little bit more efficient than list derivation, so let's change the code again:
num = [1, 4, -5, ten,-7, 2, 3,-1]
def square_generator (optional_parameter): Return
(x * * * 2 for x in num if x > Optional_parameter)
print square_generator (0)
# <generator Object <genexpr> at 0x004e6418>
# Option I for
K in Square_generator (0):
print K
# 1, +, 4, 9
# option II
g = List (Square_generat or (0))
print G
# [1, 16, 100, 4, 9]
You should often use generator expressions in your code unless you have special reasons. But unless you're facing a very large list, you don't see the obvious difference.
The following example uses the Zip () function to work with elements in two or more lists at once:
alist = [' A1 ', ' A2 ', ' A3 ']
blist = [' 1 ', ' 2 ', ' 3 '] for
A, b in zip (Alist, blist):
print A, B
# A1 1
# A2 2
# A3 3
Let's take a look at an example of traversing a table of contents through a two-order list:
Import OS
def tree (top): For
path, names, Fnames in Os.walk (top): For
fname in fnames:
yield Os.path.join (Path, fname)
For name in tree (' C:\Users\XXX\Downloads\Test '):
print Name
Adorners (Decorators)
Adorners provide us with an efficient way to increase the functionality of existing functions or classes. Does that sound like a aspect-oriented programming concept in Java? Both are simple, and the adorner has a more powerful function. For example, if you want to do something special (such as security, tracking, and locking operations) at the entry and exit points of a function, you can use adorners.
An adorner is a special function that wraps another function: The main function is invoked, and its return value is passed to the adorner, and the adorner returns an alternate function that wraps the main function, and the other part of the program sees the wrapper function.
def timethis (func):
' decorator that
reports ' execution time.
'
Pass
@timethis
def countdown (n): while
n > 0:
N-= 1
The grammar sugar @ identifies the adorner.
Well, let's go back to the example just now. We'll do some more typical things with adorners:
Import time from
functools import wraps
def timethis (func):
"decorator that
reports the execution Time.
"
@wraps (func)
def wrapper (*args, **kwargs):
start = time.time () result
= Func (*args, **kwargs) End
= Time.time ()
print (func.__name__, end-start) return result return
wrapper
@timethis
def Countdown (N):
while n > 0:
N-= 1
countdown (100000)
# (' Countdown ', 0.006999969482421875)
When you write the following code:
@timethis
def Countdown (n):
means that you perform the following steps separately:
def countdown (n):
...
Countdown = Timethis (Countdown)
The code in the adorner function creates a new function (as in this example, the wrapper function), which receives arbitrary input parameters with *args and **kwargs, and calls the original function within this function and returns its result. You can place any additional code (such as the timing operation in this example) according to your needs, and the newly created wrapper function will return as the result and replace the original function.
@decorator
def function ():
print ("Inside function")
When the compiler looks at the above code, the function () functions will be compiled, and the function return object will be passed to the adorner code, and the adorner will replace the original function with a new function object after the relevant operation is done.
What is the adorner code like? Most of the examples are to define adorners as functions, and I find it easier to understand the features of adorners by defining them as classes, and to give more power to the adorner mechanism.
The only requirement for the class implementation of the adorner is that it must be able to be used as a function, that is, it must be callable. So, if you want to do this, the class must implement the __call__ method.
What should this adorner be used for? It can do anything, but usually it is used when you want to use the original function in some special place, but this is not necessary, for example:
Class Decorator (object):
def __init__ (self, f):
print ("Inside decorator.__init__ ()")
F () # Prove that function definition has completed
def __call__ (self):
print ("Inside decorator.__call__ ()")
@decorator
def function ():
print ("Inside function ()")
print ("Finished decorating function ()")
function ()
# inside Decorator.__init__ () #
inside function () #
finished decorating function ()
# inside Decorator.__call__ ()
Translator Note:
1. The syntactic sugar @decorator is equivalent to Function=decorator (function), where decorator __init__ print "Inside decorator.__init__ ()" is called
2. Then execute f () print "Inside function ()"
3. Then execute "print" ("finished decorating function ()") "
4. Finally, when calling function functions, the __call__ print "Inside decorator.__call__ ()" of decorator is performed because of the adorner wrapper.
A more practical example:
def decorator (func):
def modify (*args, **kwargs):
variable = kwargs.pop (' variable ', None)
print variable
X,y=func (*args, **kwargs) return
x,y return
Modify
@decorator
def func (a,b):
print a**2,b* *2 return
a**2,b**2
func (a=4, b=5, variable= "HI")
func (a=4, b=5)
# hi #
None
# 16 25
Context Management Library (CONTEXTLIB)
The Contextlib module contains tools related to the context manager and the with Declaration. Usually if you want to write a context manager, you need to define a class that contains __enter__ methods and __exit__ methods, such as:
Import Time
class Demo:
def __init__ (self, label):
self.label = label
def __enter__ (self):
Self.start = Time.time ()
def __exit__ (self, exc_ty, Exc_val, EXC_TB): End
= Time.time ()
print (' {}: {} '. Format (Self.label, End-self.start))
Complete examples of this:
Import Time
class Demo:
def __init__ (self, label):
self.label = label
def __enter__ (self):
Self.start = Time.time ()
def __exit__ (self, exc_ty, Exc_val, EXC_TB): End
= Time.time ()
print (' {}: {} '. Format (Self.label, End-self.start)) with the
demo (' Counting '):
n = 10000000 while
n > 0:
N-= 1
# counting:1.36000013351
The context manager is activated with a declaration, which involves two methods.
1. The __enter__ method, when the execution stream enters the with code block, the __enter__ method executes. And it returns an object that can be used by the context.
2. When the execution stream leaves the with code block, the __exit__ method is invoked and it cleans up the resource being used.
Rewrite the above example with the @contextmanager adorner:
From contextlib import contextmanager
import time
@contextmanager
def demo (label):
start = Time.time (
try:
yield
finally: End
= Time.time ()
print (' {}: {} '. Format (label, End-start)) with
demo (' Counting '):
n = 10000000 while
n > 0:
N-= 1
# counting:1.32399988174
Looking at the example above, all the code before yield in the function is similar to the content of the __enter__ method in the context manager. And all the code after yield is like the content of the __exit__ method. If an exception occurs during execution, it is triggered in the yield statement.
Descriptor (descriptors)
The descriptor determines how the object properties are accessed. The role of the descriptor is to customize what happens when you want to reference an attribute.
The way to build a descriptor is to define at least one of the following three methods. Note that the instance in the following section is an object instance that contains the accessed attribute, while owner is the class that is represented by the descriptor.
__get__ (self, instance, owner) – This method is invoked when a property is obtained by means of (value = obj.attr), and the return value of this method is assigned to the part of the code that requests this property value.
__set__ (self, instance, value) – This method is called when you want to set the value of the property (obj.attr = ' value '), and the method does not return any values.
__delete__ (self, instance) – This method is called when a property is deleted from an object (Del obj.attr).
Translator Note: For instance and owner's understanding, consider the following code:
class Celsius (object):
def __init__ (self, value=0.0):
self.value = float (value)
def __get__ (self, instance, owner): Return
self.value
def __set__ (self, instance, value):
Self.value = float (value)
Class temperature (object):
Celsius = Celsius ()
temp=temperature ()
Temp.celsius #calls celsius.__get__
In the example above, instance refers to temp, while owner is temperature.
Lazyloading Properties Example:
Import Weakref
class Lazyattribute (object):
def __init__ (self, f):
Self.data = weakref. Weakkeydictionary ()
self.f = f
def __get__ (self, obj, CLS):
if obj not in Self.data:
Self.data[obj] = SELF.F (obj) return
Self.data[obj]
class Foo (object):
@lazyattribute
def Bar (self):
Print "Being lazy" return
f = Foo ()
print F.bar
# being lazy #
print F.bar
# 42
The descriptor is a good summary of the concept of binding methods (bound method) in Python, which is the core of the implementation of classical classes (classic classes). In a classic class, when a property is not found in the dictionary of an object instance, it continues to look in the dictionary of the class, then into the dictionary of the base class, where it is recursively searched. If this property is found in the class dictionary, the interpreter checks to see if the object being found is not a Python function object. If it does, it returns not the object itself, but rather the wrapper object that returns a currying function. When this wrapper is invoked, it first inserts an instance before the argument list and then calls the original function.
Translator Note:
1. Corrie –http://zh.wikipedia.org/wiki/%e6%9f%af%e9%87%8c%e5%8c%96
2. The difference between Function,method,bound method and unbound method. First, a function is created by a def or a lambda. When a function is defined in a class statement block or created by type, it is converted to an unbound method (unbound), and when this method is accessed through a class instance (instance), it is converted to the binding method (bound methods). The binding method automatically passes the instance as the first argument to the method. In summary, the method is the function that appears in the class, the binding method is a method that binds the concrete instance, and the other is the unbound method.
In summary, the descriptor is assigned to the class, and these special methods are automatically invoked when the property is accessed according to the specific access type.
Meta Class (metaclasses)
The Meta class provides an effective way to change the behavior of a Python class.
The definition of a class is "class of a class". Any instance of its own class is a meta class.
Class Demo (object):
pass
obj = demo ()
print ' class of obj is {0} '. Format (obj.__class__)
print "Class of O BJ is {0} '. Format (demo.__class__)
# Class of ' obj is ' <class ' __main__.demo ' >
# Class of ' obj is <type ' type ' >
In the example above, we defined a class demo and generated an object obj for that class. First, you can see that obj's __class__ is demo. Interesting to come, so what is the class of demo? You can see that the __class__ of the demo is type.
So the type is the class of the Python class, in other words, the obj in the example above is a demo object, and the demo itself is an object of type.
So the type is a meta class and is the most common tuple in Python because it makes the default meta class for all classes in Python.
Because the meta class is the class of the class, it is used to create the class (just as the class is used to create the object). But aren't we creating classes from a standard class definition? That's true, but the internal workings of Python are as follows:
- When you see a class definition, Python collects all the attributes into a dictionary.
- When the class definition ends, Python determines the class's meta class, and we call it Meta bar.
- Finally, Python executes Meta (name, bases, DCT), Where:
A. Meta is a meta class, so this call is instantiated.
B. Name is the class name of the new class.
C. Bases is the base class tuple of the new class
D. DCT maps a property name to an object, listing all the class properties.
So how do you determine the meta class of a class (a)? In simple terms, if a class (a) itself or one of its base classes (BASE_A) has a __metaclass__ attribute, the Class (A/base_a) is the class (a). Otherwise, type will be the Meta class of Class (A).
mode (Patterns)
"It's easier to ask for forgiveness than to ask for permission (EFAP)"
The Python design principle says, "It's easier to ask for forgiveness than to ask for permission" (EFAP). Do not advocate thoughtful design ideas, the principle is that should try as far as possible, if encountered errors, then give proper treatment. Python has a powerful exception-handling mechanism to support this attempt, which helps programmers develop more stable, fault-tolerant programs.
Single case
A single example is an instance object that can only exist at the same time. Python provides a number of ways to implement a single example.
Null object
Null objects can be used in place of the none type to avoid testing for none.
Observer
The Observer mode allows multiple objects to access the same data.
Constructors
The parameters of the constructor are often assigned to the variables of the instance. This pattern can replace multiple manual assignment statements with one line of code.
Summary
Thank you for reading, if you have questions, please leave a message for discussion.