• 8. Object References, Mutability, and Recycling


    1. Variables Are Not Boxes

    # Think variables as sticky notes
    a = [1, 2, 3]
    b = a
    a.append(4)
    print b  # [1, 2, 3, 4]
    
    # 1. The object is created before the assignment. So variable is
    # assigned to an object, not the other way around. 
    

     2. Identity, Equality, and Aliases

    charles = {'name': 'Charles', 'born': 1832}
    lewis = charles  # alias
    print lewis is charles  # True
    print id(lewis) == id(charles)  # True
    lewis['born'] = 1844
    print charles  # {'born': 1844, 'name': 'Charles'}
    
    alex = {'name': 'Charles', 'born': 1844}
    print alex == charles  # True (same value)
    print alex is charles  # False (different identities)
    
    
    # 1. In CPython, id() returns the memory address of the object, but
    # it may be something else in another Python interpreter. The key 
    # point is that the ID is guaranteed to be a unique numeric label, 
    # and it will never change during the life of the object.
    # 2. The is operator is faster than ==, because it cannot be
    # overloaded, so Python does not have to find and invoke special
    # methods to evaluate it, and computing is as simple as comparing
    # two integer IDs.
    # 3. a == b is syntactic sugar for a.__eq__(b). The __eq__ method
    # inherited from object compares object IDs, so it produces the
    # same result as is. But most built-in types override __eq__ with
    # more meaningful implementations that actually take into account
    # the values of the object attributes. Equality may involve a lot
    # of processing. (large collections / deeply nested structures)
    
    t1 = (1, 2, [30, 40])
    t2 = (1, 2, [30, 40])
    print t1 == t2  # True
    print id(t1[-1])  # 4302515784
    t1[-1].append(99)
    print t1  # (1, 2, [30, 40, 99])
    print id(t1[-1])  # 4302515784
    print t1 == t2  # False
    
    # 1. What can never change in a tuple is the identity of the
    # items it contains.
    # 2. Tuples, like most Python collections—lists, dicts, sets,
    # etc.--hold references to objects. On the other hand, single-type
    # sequences like str, bytes, and array.array are flat: they don’t
    # contain references but physically hold their data--characters,
    # bytes, and numbers--in contiguous memory.

     3. Copies Are Shallow by Default

    import copy
    l1 = [3, [55, 44], (7, 8, 9)]
    l2 = list(l1)  # l2 = l1[:] or l2 = copy.copy(l1)
    print l2  # [3, [55, 44], (7, 8, 9)]
    print l2 == l1  # True
    print l2 is l1  # False
    l1.append(100)
    l1[1].remove(55)
    print l1  # [3, [44], (7, 8, 9), 100]
    print l2  # [3, [44], (7, 8, 9)]
    l3 = copy.deepcopy(l2)
    l3[1].append(55)
    print l3  # [3, [44, 55], (7, 8, 9)]
    print l2  # [3, [44], (7, 8, 9)]
    
    # 1. Using the constructor or [:] or copy.copy() produces a shallow copy.

    # Cyclic references
    a = [10, 20]
    b = [a, 30]
    a.append(b)
    print a  # [10, 20, [[...], 30]]
    c = copy.deepcopy(a)
    print c  # [10, 20, [[...], 30]]

    [Notes]: You can control the behavior of both copy and deepcopy by implementing the __copy__() and __deepcopy__() special methods as described in the copy module documentation.

    4. Function Parameters as References

    def f(a, b):
    	a += b
    	return a
    
    x = 1
    y = 2
    print f(x, y)  # 3
    print x, y  # 1 2
    a = [1, 2]
    b = [3, 4]
    print f(a, b)  # [1, 2, 3, 4]
    print a, b  # [1, 2, 3, 4] [3, 4]
    t = (10, 20)
    u = (30, 40)
    print f(t, u)  # (10, 20, 30, 40)
    print t, u  # (10, 20) (30, 40)
    
    1. The only mode of parameter passing in Python is call by sharing.
    which means the parameters inside the function become aliases
    of the actual arguments.
    2. The result of this scheme is that a function may change any
    mutable object passed as a parameter, but it cannot change the
    identity of those objects
    
    
    class A:
    	def __init__(self, a_list=[]):
    		self.a_list = a_list
    	def add(self, name):
    		self.a_list.append(name)
    
    a1 = A()
    a1.add('A')
    print a1.a_list  # ['A']
    a2 = A()
    a2.add('B')
    print a2.a_list  # ['A', 'B']
    print a1.a_list  # ['A', 'B']
    print a1.a_list is a2.a_list  # True
    print A.__init__.__defaults__[0] is a1.a_list  # True
    
    # 1. Two objects don’t get an initial list end up sharing the same
    # list among themselves.
    # 2. When the module is loaded, and the default values become
    # attributes of the function object. So if a default value is a
    # mutable object, and you change it, the change will affect every
    # future call of the function.
    
    
    class B:
    	def __init__(self, a_list=None):
    		if a_list is None:
    			self.a_list = []
    		else:
    			self.a_list = a_list
    			# self.a_list = list(a_list)  # make a copy
    	def add(self, name):
    		self.a_list.append(name)
    
    l = [1, 2, 3]
    b1 = B(l)
    b1.add(5)
    print b1.a_list  # [1, 2, 3, 5]
    print l  # [1, 2, 3, 5]
    
    # 1. You should think twice before aliasing the argument object
    # by simply assigning it to an instance variable in your class.
    # If in doubt, make a copy.
    

     5. del and Garbage Collection

    • The del statement deletes names, not objects. An object may be garbage collected as result of a del command, but only if the variable deleted holds the last reference to the object, or if the object becomes unreachable. Rebinding a variable may also cause the number of references to an object to reach zero, causing its destruction.
    • unreachable: If two objects refer to each other, they may be destroyed if the garbage collector determines that they are otherwise unreachable because their only references are their mutual references.
    • There is a __del__ special method, but it does not cause the disposal of the instance, and should not be called by your code. __del__ is invoked by the Python interpreter when the instance is about to be destroyed to give it a chance to release external re‐sources. You will seldom need to implement __del__ in your own code

    • In CPython, the primary algorithm for garbage collection is reference counting. Es‐sentially, each object keeps count of how many references point to it. As soon as that refcount reaches zero, the object is immediately destroyed: CPython calls the __del__ method on the object (if defined) and then frees the memory allocated to the object. In CPython 2.0, a generational garbage collection algorithm was added to detect groups of objects involved in reference cycles—which may be unreachable even with outstanding references to them, when all the mutual references are contained within the group. Other implementations of Python have more sophisticated garbage collectors that do not rely on reference counting, which means the __del__ method may not be called immediately when there are no more references to the object.

    import weakref
    s1 = {1, 2, 3}
    s2 = s1
    def bye():
    	print('Gone with the wind...')
    ender = weakref.finalize(s1, bye)
    print(ender.alive)  # True
    del s1
    print(ender.alive)  # True
    s2 = 'spam'  # Gone with the wind...
    print(ender.alive)  # False
    
    # 1. del does not delete objects, but objects may be deleted
    # as a consequence of being unreachable after del is used.
    # 2. This works because final ize holds a weak reference to {1, 2, 3}.

    6. Weak References

    P236

    7. Tricks Python Plays with Immutables

    P240

  • 相关阅读:
    常用的正则表达式,字符串,地址操作
    倒计时工具
    Java—集合框架List
    Java—包装类、Date和SimpleDateFormat、Calendar类
    Java—字符串
    Java —异常
    Java—多态
    Java—继承
    Java—封装
    Java —类和对象
  • 原文地址:https://www.cnblogs.com/lb477/p/10929550.html
Copyright © 2020-2023  润新知