Contents of this chapter:
Object reference:
Referential variable Object identifier,value,alias
variability
Tuple properties Latent replication and deep replication References and function parameters
garbage collection
delete object Weak reference
Tip: the contents of this chapter are not understood clearly, and errors that are not easy to find may occur
8.1 variables are not boxes
#a is the identification of object [1,2,3], or referential variable a=[1,2,3] #b is also the label or alias of the object [1,2,3] b=a a.append(4) print(b)
For referential variables, it is more reasonable to assign variables to objects
8.2 identification, equality and alias
charles={"name":"Charles L","born":1832} #lewis is charles' pseudonym lewis=charles lewis is charles id(charles),id(lewis) #At this time, there is an impostor: alex={"name":"Charles L","born":1832} #==Compare values alex==charles #is compares the id value of the object alex is not charles
==Operator compares the values of two objects (the data stored in the object), and is compares the memory address of the object.
8.2.1 select between = = and is
Usually we use = = more because we usually care about the value of the object
Is is is most often used to check whether the value of variable binding is None. The following is the recommended wording:
x is None
This is a faster call to the is operator. Because the is operator cannot be overloaded, Python does not need to find and call special methods
8.2.2 relative immutability of tuples
Tuples save references to objects. The immutability of tuples actually means that the physical content of tuple data structures (i.e. saved references) is immutable, independent of the referenced objects
That is, the immutable tuple is only the identification of the element, and the object pointed to is variable
t1=(1,2,[30,40]) t2=(1,2,[30,40]) t1==t2 id(t1[-1]) t1[-1].append(99) id(t1) t1==t2
Therefore, tuples only have relative immutability, which is the reason why tuples are not hashable
8.3 shallow copy by default
Shallow replication
l1=[3,[55,44],[7,8,9]] #[:] the constructor does shallow copy by default l2=l1[:] l2 l1 l2==l1 l2 is l1 l1[1] is l2[1] l3=la.copy() #This method is also latent replication l4=list(l1) l5=list(l1) #It is also a latent copy
Deep copy (that is, the copy does not share references to internal objects)
import copy l5=copy.deepcopy(l1)
Use the following example to see the difference between latent replication and deep replication
class Bus: def __init__(self,passengers=None): #Create a passenger list if passengers is None: self.passengers=[] else: self.passengers=list(passengers) def pick(self,name): self.passengers.append(name) def drop(self,name): self.passengers.remove(name) import copy bus1=Bus(["Alice","bill","Charles","David"]) bus2=copy.copy(bus1) #Use the copy module to copy variables directly. Here is latent copy bus3=copy.deepcopy(bus1) #Use the deep copy method in the copy module for deep copy bus1.drop("Alice") print(bus2.passengers) print(bus3.passengers) print(id(bus1.passengers)==id(bus2.passengers)) #bus2 is a shallow copy of bus1 and shares the same passenger list with bus print(id(bus1.passengers)==id(bus3.passengers))
8.4 when the parameters of the function are used as references
The only parameter passing mode supported by Python is shared parameter passing
Shared parameters
: a formal parameter inside a function is an alias for an argument
The result of this parameter passing method: the function may modify the variable object passed in as a parameter
When the incoming parameters are immutable objects, such as integers and tuples, changing the formal parameters does not change the actual parameters.
def f(a,b): a+=b return a x=1 y=2 f(x,y) print(x,y) #tuple t=(10,20) u=(30,40) f(t,u) print(t,u)
Pay special attention when the parameter is a variable object, such as a list
x=[1,2] y=[3,4] f(x,y) print(x,y)
8.4.1 do not use variable types as default values for parameters
In python functions, optional parameters can have default values.
This enables backward compatibility.
(backward compatibility: for example, when a program written on Python 2.0 can run perfectly on Python 3.0, we call this phenomenon backward compatibility or historical compatibility)
Using a variable object as the default value of a parameter may cause an error that is not easy to find.
See the following example:
class funnyBus: """People will disappear on the magic school bus""" def __init__(self,passengers=[]): #This sets a list (mutable objects) as the default parameter self.passengers=passengers def pick(self,name): self.passengers.append(name) def drop(self,name): self.passengers.remove(name) bus1=funnyBus(["uni","xiaoma","leo"]) #A list parameter is passed in. At this time, there is no problem bus1.passengers bus1.pick("alice") bus2=funnyBus() bus3=funnyBus()
At this time, the problem arises. Bus2 and bus2 instances will share a default list []
Let's look at the properties of this class.
bus2=funnyBus() bus2.pick("hello") bus3=funnyBus() bus2.pick("hello ni mei") print(dir(funnyBus.__init__)) print(funnyBus.__init__.__defaults__)
output:
(['hello', 'hello ni mei'],)
Run the following code:
print(funnyBus.__init__.__defaults__[0] is bus2.passengers) #The passengers attribute of instance bus2 is set to the first attribute of the class.
How to correctly handle default parameters:
class Bus: def __init__(self,passengers=None): #Create a passenger list if passengers is None: #If there is no default parameter, the instance creates one of its own. self.passengers=[] else: self.passengers=list(passengers)
8.4.2 defense variable parameters
If a defined function receives variable parameters, you should carefully consider whether the caller expects to modify the incoming parameters.
The following example illustrates the risk of variable parameters:
class Bus: def __init__(self,passengers=None): #Create a passenger list if passengers is None: self.passengers=[] else: self.passengers=passengers #(1) def pick(self,name): self.passengers.append(name) def drop(self,name): self.passengers.remove(name)
At this time, the variable parameter is passed into the object. When the object changes the parameter, the external variable parameter will also change. Because a reference to a parameter was passed in.
So if we don't want to change the passed in parameters,
Part (1) of the above code should be written like this
self.passengers=list(passengers) #Create a copy of the parameter
Another advantage of this is that tuples and other parameters can also be passed in.
When we are not sure whether we need to change the parameter object, we will use this method by default.
8.5 del and waste recycling
Objects will never be destroyed by themselves; However, when an object cannot be obtained, it may be garbage collected.
Del statementdeletes a reference, not an object. When the reference deleted by del is the only reference of the object, the object will be garbage collected.
python garbage collection mechanism:
Reference counting method
When the reference count returns to zero, the object is destroyed immediately.
The following example demonstrates the recycling of objects.
import weakref s1={1,2,3} s2=s1 def bye(): print("Gone....") ender=weakref.finalize(s1,bye) ender.alive def sl ender.alive s2="spam" ender.alive
8.6 weak references
In actual use, it is sometimes necessary to refer to objects, but do not let the objects exist longer than required.
For example, we save the data object in the cache for use. When the reference of the object does not exist, we want to release this part of the data in the cache. At this point, we can use weak references.
Explanation of the principle of weak reference:
Weak reference underlying implementation principle
A usage scenario for weak applications.
Suppose we have a multithreaded program that processes application data concurrently:
class Date: def __init__(self,key): pass
key is the unique identifier of the data. The data may be accessed by multiple threads at the same time.
When there is a large amount of Data, the creation cost is very high. We want to maintain only one copy of Data in the program,
Even if it is accessed by multiple threads at the same time, it is not created repeatedly.
You can design a caching middleware cache
import threading #Data cache class Cacher: def __init__(self): self.pool={} self.lock=threading.Lock() def get(self,key): with self.lock: #Lock data=self.pool.get(key) if data: return data self.pool[key]=data=Data(key) return data
There is a serious problem with this code,
Once the Data is worn, it will be saved in the Data dictionary. Even if no thread needs the Data, because the Data is referenced by a key in the dictionary, the object will not be recycled.
At this time, the weak reference is used. The value in the dictionary key value pair is used to save the weak reference of the data object. When the reference of the data object is 0, the object will be automatically released.
import threading import weakref # Data cache class Cacher: def __init__(self): self.pool=weakref.WeakValueDictionary() self.lock = threading.Lock() def get(self, key): with self.lock: data = self.pool.get(key) if data: return data self.pool[key] = data = Data(key) return data
Weak reference classes are in the breaker module,
Common are:
weakValueDictinary
weakKEYDictinary
weakSet
Finally, look at a small program and guess the result:
t1=(1,2,3)
t2=tuple(t1)
t2 is t1
?
t3=t1[:]
t3 is t1
?