可变数据

引入(Intro)

The behavior of a changing object may be influenced by its history, just like an entity in the world. Adding state to data is a central ingredient of a paradigm called object-oriented programming.
可变数据是一种数据状态可以不断改变的数据类型,它独立与项目的其他部分。它的行为会受到数据变化历史的影响(即前面的操作会影响数据后面的状态)。面对对象编程核心的范式就是给数据增加新的状态。
----John DeNero, UCB

对象的隐喻(The Object Metaphor)

  • 对象(Object):对象是数据和行为的组合,不仅可以表示信息,还可以表示行为。
from datetime import date

date 作为类的名称,绑定到一个类(class),可以通过调用这个类来创建实例(Instance)

today = date(2021,2,16)

对象还有自己的属性(attribute),用 ”·“ 来指定对象的属性。

<expression>.<attribute_name>
# expression :赋予对象的名称
# attribute_name: 属性的名称
>>>today.year
2021

对象的属性还可以是函数,称为对象的方法(Method),例如”strftime“方法可以用字符串格式表示时间。

>>>today.strftime('%A, %B %d')
‘Monday,Feburary 16’

计算strftime的返回值需要两个输入值:

  1. 输出日期的字符串格式
  2. 绑定到today的日期信息
    除了日期,数字、字符串、列表、范围都是对象,都有自己的属性和方法。

数组型对象(Sequence Objects)

初始内置数据的实例是不可变的(immutable),他们的值不会随之程序的执行随之变化。相反,数组就是一种可变数据类型(mutable data)。可变数据类型用来表示会随时间变化的变量。例如一个人,尽管会生老病死,但这个人还是同一个人。

>>> chinese = ['coin', 'string', 'myriad']  # A list literal
>>> suits = chinese                         # Two names refer to the same list
>>> suits.pop()             # Remove and return the final element
'myriad'
>>> suits.remove('string')  # Remove the first element that equals the argument
>>> suits.append('cup')              # Add an element to the end
>>> suits.extend(['sword', 'club'])  # Add all elements of a sequence to the end

我们可以对数组进行一系列方法的调用,来改变这个数组的状态

元组
元组是python的内置实例,是一种不可变类型的序列。元组通过逗号将各元素分隔开,可以加括号也可以不加括号

>>> 1, 2 + 3
(1, 5)
>>> ("the", 1, ("and", "only"))
('the', 1, ('and', 'only'))
>>> type( (10, 20) )
<class 'tuple'>

空元组和单元素元组

>>> ()    # 0 elements
()
>>> (10,) # 1 element
(10,)

元组是不可变数据类型,所以不能该表元组内部的元素值。如果元组内部含有数组,则可以改变内部的数组。

>>> nest = (10, 20, [30, 40])
>>> nest[2].pop()
(10, 20, [30])

元组一般在多重隐式赋值中被使用。将两个值赋给两个变量名可以创建一个包含两个元素的元组。

字典(Dictionaries)

字典是包含(key:value)数据对的一种数据类型,key和value都是对象。有了字典就不用去按索引查找元素了,可以直接用描述性的key来查找元素。例如在超市报一个菜名,熟练的收银员能立马知道菜的价格。

>>> numerals = {'I': 1.0, 'V': 5, 'X': 10}
>>>> numerals['X']
10
>>> numerals['I'] = 1
#新增元素
>>> numerals['L'] = 50
>>> numerals
{'I': 1, 'X': 10, 'L': 50, 'V': 5}

字典也支持一系列方法:

  • .keys: 返回字典的key
  • .values:返回字典的元素值
  • .items:
  • .get:返回元素值,或者是key。输入的参数是key和默认元素值
>>> sum(numerals.values())
66
>>> numerals.get('A', 0)
0
>>> numerals.get('V', 0)
5

字典的限制:

  • 字典的key不能包含可变数据类型
  • 一个key只能对应一个值

字典的理解型语法(comprehension syntax)
用冒号将key和value隔开,创建一个新的字典。

>>> {x: x*x for x in range(3,6)}
{3: 9, 4: 16, 5: 25}

局部状态(local state)

例如在取款机取钱

>>> def make_withdraw(balance):
        """Return a withdraw function that draws down balance with each call."""
        def withdraw(amount):
            nonlocal balance                 # 声明"balance" nonlocal
            if amount > balance:
                return 'Insufficient funds'
            balance = balance - amount       # 重新绑定balance
            return balance
        return withdraw
>>> withdraw = make_withdraw(100)
>>> withdraw(25)
75
>>> withdraw(25)
50
>>> withdraw(60)
'Insufficient funds'
>>> withdraw(15)
35

使用了nonlocal声明后,之后的每一次调用,把balance绑定到初始的余额。Nonlocal表示每当程序要把balance绑定给一个新值的时候,这个变化都会在最初balance被绑定的框架进行。即初始余额是100,取出25后,把剩余的75重新绑定给最初的balance。每次调用withdraw函数,都会创建一个新的框架。每个withdraw框架都有一个共同的母框架。

非本地分配的好处(The Benefits of Non-Local Assignment)

非本地分配可以让程序变得独立自治。例如上面的程序可以建立两个账户,账户之间的的取款在不同的框架内,互不干扰。

def make_withdraw(balance):
	def withdraw(amount):
		nonlocal balance
	    if amount > balance:
	    	return 'Insufficient funds'
	    balance = balance - amount
        return balance
	return withdraw	
wd = make_withdraw(20)
wd2 = make_withdraw(7)
wd2(6)
wd(8)

非本地分配的代价

当把wd和wd2弄混淆的时候,会很容易出错。避免这种错误的关键是记住:只有调用函数才会引入新的框架,只有make_withdraw被调用两次,才会有两个框架。

上一篇:多线程同步机制练习之银行存钱


下一篇:Mysql:事务