Python Is a powerful dynamic language , Where is the dynamic , Where is the strength ?
Except for the good ,Python Is there any use trap hidden in the dynamic of , Is there any way to identify and avoid ?
Along with its dynamic features topic , Several articles have explored it in turn : Change variables dynamically 、 Define functions dynamically 、 Dynamic execution of code and so on , However , When you mix variable assignments 、 Dynamic assignment 、 Namespace 、 Scope 、 Function compilation principle and so on , The problem can become very tricky .
therefore , This article merges some of the previous content , Do another extended discussion , Hope to be able to sort out some details of use , Explore more deeply Python The mystery of language .
(1) A confused example
Let's take a look at this example :
# example 0
def foo():
exec('y = 1 + 1')
z = locals()['y']
print(z)
foo()
# Output :2
exec() The variable is defined in the code block of the function y, This value can be used by the following locals() Fetch , It's also printed after the assignment . However , On the basis of this example , Just make small changes , The result may be quite different .
# example 1
def foo():
exec('y = 1 + 1')
y = locals()['y']
print(y)
foo()
# Report errors :KeyError: 'y'
Put the precedent z Change it to y , It's a mistake . among ,KeyError It means that there is no corresponding key . Why is this so , The newly assigned variable is y perhaps z, Why has such a different effect on the results ?
Try a exec Get rid of , Don't complain !
# example 2
def foo():
y = 1 + 1
y = locals()['y']
print(y)
foo()
# 2
problem : Direct pair y assignment , With the dynamic exec() Assignment in , Would be right locals() How does value affect ?
Try the right example again 1 Of locals() Assign first , Or an error :
# example 3
def foo():
exec('y = 1 + 1')
boc = locals()
y = boc['y']
print(y)
foo()
# KeyError: 'y'
Do a assignment first , It's no use ? Neither , If you put the order of the assignment before , I won't make a mistake :
# example 4
def foo():
boc = locals()
exec('y = 1 + 1')
y = boc['y']
print(y)
foo()
# 2
in other words ,locals() Is not fixed , Its value is context sensitive at call time , call locals() Timing matters .
However , If you want to verify , Add a... To the function locals() Printing of , This action will affect the final execution result .
# example 5
def foo():
boc = locals()
exec('y = 1 + 1')
print(locals())
y = boc['y']
print(y)
foo()
# {'boc': {...}} # KeyError: 'y'
What's going on here ?
(2) A reserve of multiple knowledge
The above examples are quite different in details , Mainly due to the following knowledge points :
1、 Declaration and assignment of variables
2、locals() Logic of value and modification
3、locals() The relationship between dictionary and local namespace
4、 Function compilation , Parsing of the abstract syntax tree
Be careful :exec() The function has two default parameters globals() And locals() ( Same name as built-in function ), It is used to limit variables in string parameters , If you add it , It will only increase the complexity of the above examples , therefore , We all do the default processing , What is discussed here is exec() If there is only one parameter .
In some programming languages , Variable declaration and assignment can be separated , For example, write... When declaring int a , When assignment is needed , To write a = 1 , Of course, it can't be separated , It is int a = 1 .
Corresponding to Python in , It's different , These two actions are combined in writing . First, it doesn't specify the type of variable , You don't need... At any time ( Also can not ) Type... Before variable ( Such as int), secondly , Declaration and assignment cannot be split into writing , It can only be written as a = 1 such . It looks like it's written in the same way as other languages , But actually , It has the effect of int a = 1 .
It's a convenience , But there is also a hidden trap ( Focus on ): When you see a = 1 when , You can't be sure a It's the first statement , Or has been declared .
About locals() The creation process of ,locals() Dictionaries are proxies for local namespaces , It will collect variables from the local scope , If the local variables are changed dynamically during the code running period , It only affects the dictionary , It doesn't affect the real local scope variables . therefore , When called again locals() when , Because of the recapture , The dynamically modified content will be discarded .
The local namespace of the runtime cannot be changed , It means exec() The assignment of a variable in a function does not affect it , but locals() The dictionary is changeable , Will receive exec() The effect of functions .
About function compilation ,Python At compile time, the valid variable names in the local scope are determined , Bind to content at run time . The resolution of a variable in scope is independent of its execution order , It has nothing to do with whether or not it will be executed .
(3) Schrodinger's cat
The above is the premise , Friendship tips , If you have a vague understanding , Please read the corresponding article first . Next is the analysis based on these contents .
I can't guarantee that every detail is accurate , But this analysis tries to be simple 、 ing 、 Logic is right , And funny by the way ……
example 0 in , Although there is no ‘y’, but exec() Function creates it dynamically , So it dynamically writes locals() In the dictionary , So it can be found without error .
example 1 in ,exec() Does not affect the local scope , Is this time y No declarations or assignments have been made in the local scope , The following sentence is the first time that the local scope is used for y Make a declaration and assignment !
y = locals()['y'] , The left side of the equal sign is making a statement , As long as the result to the right of the equal sign holds , The whole process of declaration and assignment is established . The right side needs to be locals() Look up in the dictionary y Corresponding value .
Creating locals() Dictionary time , Because there are variables in the local scope y Statement of , So we first collected y, Not necessarily in exec() Find... In the dynamic results of the function . There is a dictionary key, Then match this key Corresponding value , That is to say y Bound value .
however , I just said that this is y The first assignment of , It's not finished , therefore y There is no valid binding value .
There is a contradiction , It's a little winding here , Let's take care of it : Left side y Wait for the assignment to be completed , So you need the execution result on the right ; And the dictionary on the right needs to use y Value , So it depends on the y Assignment completion . The operation on both sides is not finished , But both sides need to rely on each other to finish first , It's a dead end that can't be solved .
so to speak ,y The value of is a mess , It must be equal to “locals()['y']” , However, only by uncovering the code can we get the exact result —— Only by opening the cage can we know the result , Did you think of Schrodinger's cat ?
locals() Although I have got the dictionary y The name of , But I can't get it , I'll have a good time , So the newspaper KeyError.
example 3 Empathy , Use... Without completing the assignment , So wrong reporting .
example 2 in ,y In the process of secondary assignment , A valid y be equal to 2, therefore locals() Find it for assignment , So no mistake .
As for example 4, It follows the example 3 There's only one execution sequence left , Why can't we report a mistake ? And more strange , In case 4 Plus a print ( example 5), Should not affect the result , But the truth is that it's wrong again , Why? ?
example 4 in ,boc = locals() This sentence also has the problem of circular quotation , So there is no y, next exec() This sentence has been revised dynamically locals(), After execution boc The result is {'y' : 2}, So in the next sentence boc['y'] Can find the result , Instead of reporting mistakes .
example 4 And example 3 Of ”y = boc['y']“ , Although it's the first time to declare and assign in a local scope y, But for example 4 Of boc Has been exec() A modified , So it can get real value , There will be no more circular references .
Let's take an example 5, first locals() There is still circular quotation , next exec() Write variables to the dictionary y, however , the second locals() It triggers a new dictionary creation process , Will be able to exec() The results of the implementation of , So go to the second round of circular reference , Result in an error .
example 5 And example 4 The difference is , It's a dictionary regenerated from a local scope , The effect is the same as example 3.
in addition , Please pay special attention to the printed results :{'boc': {…}} .
This result shows that , the second locals() It's a dictionary , And it has only one key yes ’boc‘, and ’boc‘ Mapping is the first locals() Dictionaries , That is to say {…} . This means that there is a circular reference inside it , Intuitively confirmed all the previous analyses .
There are circular references in the dictionary , This phenomenon is extremely rare ! Although the previous analysis , But when I saw this , I don't know if you think it's incredible ?
The reason why the first circular reference can be recorded , The reason is that we didn't try to take out ’y‘ Value , The second circular reference cannot be recorded due to the value error .
This example tells you : Schr? Dinger's cat mixed in Python In the dictionary , And the answer is , Open the cage , The cat will die .
The phenomenon of circular reference in dictionaries plays an extremely important role in several cases , But it's often overlooked . The reason why it's hard to be noticed , The reason or the important content in the front : When you see a = 1 when , You can't be sure a It's the first statement , Or has been declared .
Article KeyError It's actually “local variable 'y' referenced before assignment”,y has defined But not assigned, Lead to reference Times wrong .
Assigned or unassigned , This is a problem . It's also a cat .
Last , Even though the cat is making a mess in the dark , We still have to thank it : Thank it for connecting other knowledge by us “ One pot ”, Thank it for scratching out some lively interest for this abstract brain burning article ……( as well as , Thanks for the title inspiration , I don't know how many people read for the title ?)
The above is all the content shared this time , Want to know more python Welcome to official account :
Python Programming learning circle
, send out “J” Free access to , Daily dry goods sharing