【示例】
def inc(x):
return x + 1
foo = 10
foo = inc(foo)
print(foo)
#如果想真的改变参数,那么可以使用一点小技巧,即将值放在列表中def inc(x):
x[
0] = x[0] + 1
foo = [10] #把值放在列表中,相当于C语言当中的指针inc(foo)
print(foo)
#这样就会只返回新值,代码看起来也比较清晰。
【结果】
11
[11]
6.4.3 关键字参数和默认值
目前使用的参数都叫做位置参数,因为它们的位置很重要---事实上,比它们的名字更重要。本节引入的功能可以回避位置问题,慢慢习惯之后,会发现程序规模越大,它们的作用也越大。
【示例】
def hello_1(greeting,name):
print('%s,%s!' % (greeting,name))
def hello_2(name,greeting):
print('%s,%s!' % (name,greeting))
#两个代码所实现的完全一样的功能,只是参数名字反过来了hello_1('hello','world')
hello_2(
'hello','world')
#有时,参数的顺序是很难记住的。可以提供参数的名字:hello_1(greeting = 'hello',name = 'world')
hello_1(
name = 'world',greeting = 'hello')
#但参数名和值一定要对应hello_2(greeting = 'hello',name = 'world')
#这类使用参数名提供的参数叫做关键字参数。作用主要是可以明确每个参数的作用,也就避免了奇怪的函数调用。尽管这么做打的字多了些,但是显然,每个参数的意义变得更加清晰。而且弄乱了参数的顺序,对于程序的功能也没有任何影响。
【结果】
hello,world!
hello,world!
hello,world!
hello,world!
world,hello!
【示例】
#当参数有默认值的时候,调用的时候就不用提供参数了def hello_3(greeting = 'hello',name = 'world'):
print('%s,%s!' % (greeting,name))
hello_3()
hello_3(
'Greetings')
hello_3(
'Greetings','universe')
#位置和关键字参数是可以联合使用的。把位置参数放置在前面就可以了。如果不这样做,解释器不知道它们到底谁是谁
#
注:只有在强制要求的参数个数比可修改的具有默认值的参数个数少的时候,才使用上面提到的参数书写方法def hello_4(name,greeting = 'Hello',punctuation = '!'):
print('%s,%s%s' % (greeting, name,punctuation))
hello_4(
'Mars')
hello_4(
'Mars','Howdy')
hello_4(
'Mars','Howdy','...')
hello_4(
'Mars',punctuation = '.')
hello_4(
'Mars',greeting = 'Top of the moring to ya')
【结果】
hello,world!
Greetings,world!
Greetings,universe!
Hello,Mars!
Howdy,Mars!
Howdy,Mars...
Hello,Mars.
Top of the moring to ya,Mars!
6.4.4 收集参数
有时候让用户提供任意数量的参数是很有用的。比如在名字存储程序中,用户每次只能存一个名字。如果能像下面这样存储多个名字,就更好了:
store(data,name1,name2,name3)
用户可以给函数提供任意多的参数。
【示例】
def print_param(*params):
print(params)
#这里指定了一个参数,但是前面加了个星号,为什么?
#
可以看到,结果作为元组打印出来,因为里面有个逗号(长度为1的元组有些怪,难道不是吗?)。所以在参数前使用星号就能打印出元组,那么在Params中使用多个参数,看看会发生什么:print_param(1)
print_param(
1,2,3)
#参数前的星号将所有值放置在同一个元组中。可以说是将这些值收集起来,然后使用。def print_param_2(title,*params):
print (title)
print(params)
print_param_2(
'Params:',1,2,3)
#没问题,所以星号的意思就是“收集其余的位置参数”。如果不提供任何收集的元素,params就是个空元组print_param_2('Nothing:')
【结果】
(1,)
(1, 2, 3)
Params:
(1, 2, 3)
Nothing:
()
【示例】
#print_param_2('Hmm...',something= 42)
#看来不行,所以需要另外一个能处理关键字参数的“收集”操作。def print_param_3(**params):
print(params)
print_param_3(
x = 1, y = 2,z = 3)
#返回的是字典而不是元组。def print_param_4(x,y,z = 3,*pospar,**keypar):
print(x,y,z)
print(pospar)
print(keypar)
print_param_4(
1,2,3,5,6,7,foo = 1,bar = 2)
#和我们期望的结果别无二致print_param_4(1,2,3,1,23,4,5,6,6,e = 2)
【结果】
{'z': 3, 'x': 1, 'y': 2}
1 2 3
(5, 6, 7)
{'foo': 1, 'bar': 2}
1 2 3
(1, 23, 4, 5, 6, 6)
{'e': 2}
【示例】
def init(d):
d[
'first'] = {}
d[
'middle'] = {}
d[
'last'] = {}
def lookup(data,label,name):
return data[label].get(name)
def store(data,*full_names): #full_names 代表元组,可以存储多个全名
for full_name infull_names:
names = full_name.split(
' ') #这里names是列表(使用空格作为参数,或者不写参数,则默认是空格)
if len(names)
== 2:
names.insert(
1,'')
labels =
'first','middle','last'
for label,namein zip(labels,names):
people =lookup(data,label,name)
if people: #说明原先值(列表)中不空,则使用append追加
people.append(full_name)
else: #说明原先值(列表)中为空,则加入第一个
data[label][name] = [full_name] #(设定值为列表) key1是label,key2是name,value是列表,可以存放好多全名d = {}
init(d)
store(d,
'Han Solo')
store(d,
'Luke Skywalker','Anakin
Skywalker')
list1 = lookup(d,
'last','Skywalker')
list2 = lookup(d,
'first','Han')
list3 = lookup(d,
'first','Ha')
print(list1)
print(list2)
print(list3)
【结果】
['Luke Skywalker', 'Anakin Skywalker']
['Han Solo']
None
6.4.5 反转过程
如何将参数收集为元组和字典已经讨论过了,但是事实上,如果使用*和**的话,也可以执行相反的操作。即函数收集的逆过程。
【示例】
def add(x,y):
return x + y
#比如说有个包含由两个相加的数字组成的元组params = (1,2)
#这个过程多少有点像上一节中介绍的方法的逆过程。不是要收集参数,而是分配它们在“另一端”。使用*运算符就简单了,不过是在调用而不是在定义时使用:print(add(*params))
#对于参数列表来说工作正常,只要扩展的部分是最新的就可以。可以使用同样的技术来处理字典--使用双星号运算符。假设之前定义了hello_3,那么可以这样使用:def hello_3(greeting = 'hello',name = 'world'):
print('%s,%s!' % (greeting,name))
params = {
'name': 'Sir Robin','greeting':'well
met'}
hello_3(**params)
#在定义或者调用时使用星号(或者双兴号)仅传递元组或字典,所以可能没遇到什么麻烦:def with_start(**kwds):
print(kwds['name'],'is',kwds['age'],'years old.')
def without_start(kwds):
print(kwds['name'],'is',kwds['age'],'years old')
args = {
'name':'Mr.
Gumby','age':42}
with_start(**args)
without_start(args)
#可以看到,在with_starts中,在定义和调用函数时都使用了星号。而在whthout_starts中,两处都没用,但得到了相同的效果。所以星号只在定义函数(允许使用不定数目的参数)或者调用(“分割”字典或者序列)时才有用。
【结果】
3
well met,Sir Robin!
Mr. Gumby is 42 years old.
Mr. Gumby is 42 years old
使用拼接(Splicing)操作符“传递”参数很有用,因为这样一来就不用关心参数的个数之类的问题
【示例】
def foo(x,y,z,m = 0,n = 0):
print(x,y,z,m,n)
def call_foo(*args,**kwds):
print("Calling foo!")
foo(*args,**kwds)
在调用超类的构造函数时这个方法尤其有用。
6.4.6 练习使用参数
有了这么多提供和接受参数的方法,很容易犯浑吧!
【示例】
def story(**kwds):
return 'Once
upon a time. there was a %(job)s called %(name)s.' % kwds #%(job)s
job代表key
def power(x,y,*others):
if others:
print('Recieved redundant paramenters:',others)
return pow(x,y) #作用是求x的y次幂def interval(start,stop = None,step = 1):
'Imitates range() for step > 0'
if stop is None: #如果没有为stop提供值
start,stop = 0,start#指定参数
result = []
i = start
#计算start索引
while i < stop:
result.append(i)
i += step
return result
print('----------------------story----------------------------------')
print(story(job = 'king', name = 'Gumby'))
print(story(name= 'Sir Robin',job = 'brave knight'))
params = {
'job':'language','name':'Python'}
print(story(**params))
del params['job']
print(story(job = 'stroke of genius',**params)) #相当于补上了一个keyprint('----------------------power----------------------------------')
print(power(2,3))
print(power(x = 3,y =2))
params = (
5,) * 2 #相当于(5,5)print(power(*params)) #相当于求5的5次幂print(power(3,3,'Hello, world'))
print('----------------------interval----------------------------------')
print(interval(10))
print(interval(1,5))
print(interval(3,12,4))
print(power(*interval(3,7))) #相当于实参为3,4,(5,6) 因为剩下的放在元组中
【结果】
----------------------story----------------------------------
Once upon a time. there was a king called Gumby.
Once upon a time. there was a brave knight called Sir Robin.
Once upon a time. there was a language called Python.
Once upon a time. there was a stroke of genius called Python.
----------------------power----------------------------------
8
9
3125
Recieved redundant paramenters: ('Hello, world',)
27
----------------------interval----------------------------------
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4]
[3, 7, 11]
Recieved redundant paramenters: (5, 6)
81
6.5 作用域
到底什么是变量?可以把它们看成是值的名字。在执行x = 1赋值语句后,名称x引用到值1。这就像用字典一样。当然,变量和所对应的值用的是个“不可见”的字典。
【示例】
x = 1
scope = vars()
print(scope['x'])
scope[
'x'] += 1
print(x)
print(scope['x'])
#一般来说,vars所返回的字典是不能修改的,换句话说,可能得不到想要的结果。
【结果】
1
2
2
这类“不可见字典”叫做命名空间或者作用域。除了全局作用域外,每个函数调用都会创建一个新的作用域。
【示例】
def foo():
x = 42
x = 1
foo()
print(x)
【结果】
1
这里foo函数改变了(重绑定)了变量x,但是最后,x并没有变。这是因为调用foo的时候,新的命名空间被创建了,它作用于foo内的代码块。赋值语句x=42只在内部作用域(局部命名空间)起作用。参数的工作原理类似于局部变量,所以用全局变量的名字作为参数名并没有问题。
【示例】
def output(x):
print(x)
x =
1
y = 2
output(y)
【结果】
2
但是如果希望在函数的内部访问全局变量怎么办呢?而且只想读取变量的值:
【示例】
def combine(parameter):
print(parameter+ external)
external =
'berry'
combine('Shrub')
【结果】
Shrubberry
#像这样引用全局变量是很多错误的引发原因,慎重使用全局变量。
屏蔽(shadowing)的问题:
读取全局变量一般来说并不是问题,但是还是有个会出现问题的事情,如果局部变量或者参数的名字和想要访问的全局变量名相同的话,就不能直接访问了。全局变量会被局部变量屏蔽。
如果的确需要的话,可以使用globals函数获取全局变量值,该函数的近亲是vars,它可以返回全局变量的字典(locals返回局部变量的字典)。
【示例】
def combine(parameter):
print(parameter
+ globals()['parameter'])
parameter =
'berry'
combine('Shrub')
【结果】
Shrubberry
【示例】
#接下来讨论重绑定全局变量(使用变量引用其他新值)。如果在函数内部将值赋予一个变量,它会自动变成局部变量--除非告知Python将其声明为全局变量。x = 1
def change_global():
global x
x = x +
1
change_global()
print(x)
【结果】
2
嵌套作用域:
Python的函数是可以嵌套的,也就是说可以将一个函数放在另一个里面。
【示例】
def foo():
def bar():
print('Hello ,world!')
bar()
#嵌套一般并不那么有用,但它有一个很突出的应用,例如需要用一个函数“创建”另一个,也就意味着可以像下面这样使用:def multiplier(factor):
def multiplyByFactor(number):
return number* factor
return multiplyByFactor
#一个函数位于另外一个里面,外层函数返回里层函数。函数本身被返回了,但没有调用。重要的是返回的函数还可以访问它的定义所在的定义域。换句话说,它“带着”它的环境(和相关的局部变量)
#
每次调用外层函数,它内部的函数都被重新绑定,factor变量每次都有一个新的值。由于Python的嵌套作用域,来自(multiplier)外部作用域的这个变量,稍后会被内层函数访问。double = multiplier(2) #绑定print(double(5)) #利用绑定的函数计算triple = multiplier(3) #重新绑定print(triple(3)) #使用print(multiplier(5)(4)) #重新绑定加使用
【结果】
10
9
20
6.6 递归
前面已经学习了很多关于创建和调用函数的知识。函数可以调用其他函数。令人惊讶的是,函数可以调用自身。
递归:简单来说就是引用的意思。
递归的定义包括它们自身定义内容的引用。
使用“递归”的幽默定义来定义递归一般来说是不可行的,因为那样什么也做不了。一个类似的定义如下:
【示例】
def recursion():
return recursion()
recursion()
【结果】
RecursionError:maximum recursion depth exceeded while calling a Python object
理论上讲,它应该永远运行下去。然而每次调用函数都会用掉一点内存,进而程序会以一个“超过最大递归深度”的错误信息结束。
有用的递归函数包含以下几个部分:
[if !supportLists]① [endif]函数直接返回值时有基本实例(最小可能性问题)
[if !supportLists]② [endif]递归实例,包括一个或者多个问题最小部分的递归调用
这里的关键就是将问题分解为小部分,递归不能永远继续下去,因为它总是以最小可能性问题结束。
当函数调用自身时,实际上运行的是两个不同的函数(或者说是同一个函数有两个不同的命名空间)。
6.6.1 两个经典:阶乘和幂
【示例】
def factorial(n):
result = n
for i in range(1,n): #i的取值范围是:1~n-1
result *= i
return result
print(factorial(4))
【结果】
24
【示例】
def factorial(n):
if n == 1:
return 1
return n *
factorial(n - 1)
print(factorial(4))
【结果】
24
先看一个简单的例子:power(x,n)(x为n的幂次)是x自乘n-1次的结果。
【示例】
def power(x,n):
result =
1
for i in range(n):
result *= x
return result
print(power(2,3))
【结果】
8
程序很小巧,接下来把它改变为递归版本:
[if !supportLists]① [endif]对于任意数字x来说,power(x,0)是1
[if !supportLists]② [endif]对于任何大于0的数字来说,power(x,n)是x乘以power(x,n-1)的结果
【示例】
def power(x,n):
if n == 0:
return 1
return x *
power(x,n-1)
print(power(2,3))
【结果】
8
那么递归有什么用?就不能用循环代替吗?答案是肯定的,在大多数情况下可以使用循环,而且大多数情况下会更有效率。但是在多数情况下,递归更易读—有时会大大提高可读性—尤其当读程序的人懂得递归函数的定义的时候。
6.6.2 另外一个景点 二元查找
例如,当提问者可能在想一个1~100的数字,提问者需要猜中它。当然,提问者可以耐心地猜上100次,但是真正需要猜多少次呢?
[if !supportLists]① [endif]如果上下限相同,那么就是数字所在位置,返回。
[if !supportLists]② [endif]否则找到两者的中点,查找数字是在左侧还是在右侧。
这个递归例子的关键就是顺序。
【示例】
def search(sequence,number,lower = 0,upper = None):
if upper is None:
upper =
len(sequence)
- 1
if lower== upper:
assert number== sequence[upper]
return upper
else:
middle = (lower + upper) //
2
if number> sequence[middle]:
return search(sequence,number,middle
+ 1,upper)
else:
return search(sequence,number,lower,middle)
#完全符合定义。如果lower == upper ,那么返回upper,也就是上限。注意:程序假设(断言)所查找的数字一定会被找到。如果没有达到基本实例的话,先找到middle,检查数字是在左边还是在右边。seq = [34,67,8,123,4,100,95]
seq.sort()
print(seq)
print(search(seq,34))
print(search(seq,100))
#提示,标准库中的bisect模块可以非常有效地实现二元查找
【结果】
[4, 8, 34, 67, 95, 100, 123]
2
5
函数到处放
Python在应对这类“函数式编程”方面有一些有用的函数:map、filter和reduce函数(Python3.0中这些都被移至functools模块中)。可以使用map函数将序列中的元素全部传递给一个函数。
事实上,不是使用lambda函数,而是在operator模块引入对每个內建运算符的add函数。使用operator模块中的函数通常比用自己的函数更有效率。
第七章 更 加 抽 象
前面学习了Python主要的內建对象类型(数字,字符串、列表、元组和字典),以及內建函数和标准库用法,还有自定义函数的方式。
建立自己的对象类型可能很酷,但是做什么用呢?创建自己的对象(尤其是类型或者成为类的对象)是Python的核心概念—非常核心,事实上,Python被称为面向对象的语言(和SmallTalk、C++、Java以及其他语言一样。)本章将会介绍如何创建对象,以及多态、封装、特性、超类以及继承的概念。
7.1 对象的魔力
对象最重要的有点包括一下几个方面:
多态:意味着可以对不同类的对象使用同样的操作,它们会像被“施了魔法一般”工作。
封装:对外部世界隐藏对象的工作细节。
继承:以普通的类为基础建立专门的类对象。
封装和继承会首先被介绍,因为它们被用作现实世界中的对象的模型。面向对象设计很有趣的特性是多态。它也是让大多数人犯晕的特性。以多态开始。
7.1.1 多态
术语多态来自希腊语:意思是“有多种形式”。多态意味着计算不知道变量所引用的对象类型是什么,还是能对它进行操作,而它会根据对象或类型的不同而表现出不同的行为。
#不要这样做
def getPrice(object):
if isinstance(object,tuple):
return object[1]
else:
returnmagic_network_method(object)
#注:这里用isinstance函数查看对象是否为元组。如果是的话,就返回第2个元素,否则会调用一些“有魔力的”网络方法。
但是程序还不是很灵活。如果某些聪明的程序员决定用十六进制的字符串来表示价格,然后存储在字典中的键“price”下面呢?
#不要这样做
def getPrice(object):
if isinstance(object,tuple):
return object[1]
elif isinstance(object,dict):
return int(object[‘price’])
else:
returnmagic_network_method(object)
现在是不是已经考虑了所有的可能性?但是如果有人希望存储在其他键下面的价格增加新的字典类型呢?可以再次更新getPrice函数,但是这种工作还要做多长时间?每次有人要实现价格对象的不同功能时,都要再次实现你的模块。但是如果这个模块已经卖出去了或者转到了其他更酷的项目中—那么如何应付客户?显然这是个不灵活且不切实际的实现多种行为的代码编写方式。
那么应该怎么办?可以让对象自己进行操作。这样做会轻松很多。每个新的对象可以检索和计算自己的价格并且返回结果—只需向它询问价格即可。这时多态就要出场了。
[if !supportLists]1. [endif]多态和方法
object.getPrice()
绑定到对象特性上面的函数称为方法。
‘abc’.count(‘a’)
[1,2,’1’].count(‘a’)
【示例】
from random import choice
x = choice([
'Hello,world',[1,2,'e','e',4]])
print(x)
运行后,变量x可能包含字符串’Hello,world!’,也有可能包含列表[1,2,’e’,’e’,4]---不用关心是哪个类型。
[if !supportLists]2. [endif]多态的多种形式
任何不知道对象到底是什么类型,但是又要对对象“做点什么”的时候,都会用到多态。这不仅限于方法—很多內建运算符和函数都有多态的性质。
【示例】
print(1+2)
print(
'Fish' + 'license')
【结果】
3
Fishlicense
【示例】
def length_message(x):
print('The length of',repr(x),"is",len(x))
length_message(
'Fnord')
length_message([
1,2,3])
【结果】
The length of 'Fnord' is 5
The length of [1, 2, 3] is 3
很多函数和运算符都是多态的—你写的绝大多数程序可能都是。
如果可能的话,应该尽力避免使用这些销毁掉多态的方式。真正重要的是如何让对象按照咱们希望的方式工作,不管它是否是正确的类型(或者类)。
7.1.2 封装
封装是对全局作用域中其他区域隐藏多余信息的原则。使用对象而不用知道其内部细节,它们都会帮助处理程序组件而不用过多关心多余细节。
但是封装并不等同于多态。封装是可以不关心对象是如何构建的而直接进行使用。
假设有个名叫OpenObject的类:
o = OpenObject() #This is how we create objects…
o.setName(‘Sir Lancelot’)
print(o.getName())
创建了一个对象后,将变量o绑定到该对象上。可以使用setName和getName方法。一切看起来很完美。但是假设变量o将它的名字存储在全局变量globalName中,这意味着使用OpenObject类的实例时候,不得不关心globalName的内容。如果创建了多个OpenObject实例的话就会出现问题,因为变量相同,所以可能会混淆:
o1 = OpenObject()
o2 = OpenObject()
o1.setName(‘Robin Hood’)
o2.getName()
可以看到,设定了一个名字后,其他的名字也就自动设定了。这可不是想要的结果。基本上,需要将对象看做抽象,调用方法的时候不用关心其他的东西。可以将其作为特性存储。
正如方法一样,特性石对象内部的变量;事实上更像是绑定到函数的特性。
r = ClosedObject()
r.setName(‘sir robin’)
r.getName()
可以看到新的对象的名称已经正确设置。但是第一个对象怎么样了呢?
c.getName()
名字还在!这是因为对象有了自己的状态。对象的状态由它的特性来描述。对象的方法可以改变它的特性。所以就像是将一大堆函数捆在一起,并且给与它们访问变量的权利。它们可以在函数调用之间保存的值。
7.1.3 继承
继承是另外一个懒惰(褒义)的行为。程序员不想把同一段代码写好几次。之前使用的函数避免了这种情况,但是现在又有个更微妙的问题。如果已经有了一个类,而又想建立一个非常类似的呢?新的类可能只是添加几个方法。在编写新类时,又不想把旧类的代码全部复制过去。可以让Rectangle从Shape类继承方法。在Rectangle对象上调用draw方法时,程序会自动从shape类调用该方法。
7.2 类和类型
7.2.1 类到底是什么
可以将它或多或少地视为种类或者类型的同义词。从很多方面来说,这就是类—一种对象。所有的对象都属于某一个类,称为类的实例。
在面向对象程序设计中,子类的关系是隐式的,因为一个类的定义取决于它所支持的方法。
例如,Bird可能支持fly方法,而Penguin(Bird的子类)可能会增加个eatFish方法。当创建Penguin类时,可能会想要重写超类的fly方法,对于Penguin实例来说,这个方法要么什么也不做,要么就产生异常,因为Penguin(企鹅)不会fly(飞)
最近版本的Python中,基本类型和类之间的界限开始模糊了。可以创建内部类型的子类(或子类型),而这些类型的行为更类似于类。
7.2.2 创建自己的类
终于来了!可以创建自己的类了!先来看一个简单的类:
【示例】
__metaclass__ = type #确定使用新式类class Person:
def setName(self,name):
self.name= name
def getName(self):
return self.name
def greet(self):
print("Hello, world! I'm %s" % self.name)
#注:所谓的旧式类和新式类之前是由区别的。除非是Python3.0之前的版本中默认附带的代码,否则再继续使用旧式类已无必要。新式类的语法中,需要在模块或者脚本开始的地方放置赋值语句__metaclass__ = type(并不会在每个例子中显式地包含这些语句)。除此之外,也有其他的方法,例如继承新式类(比如object)。在Python 3.0中,旧式类的问题不用担心,因为它们根本就不存在了。
这个例子中,一切看起来都挺好,但是那个self参数看起来有点奇怪。它是对于对象自身的引用。那么它是什么对象,让我们创建一些实例来看看:
【实例】
foo= Person()
bar = Person()
foo.setName(
'Luke Skywalker')
bar.setName(
'Anakin Skywalker')
foo.greet()
bar.greet()
【结果】
Hello, world! I'm LukeSkywalker
Hello, world! I'm AnakinSkywalker
例子一目了然。在调用foo的setName和greet函数时,foo自动将自己作为第一个参数传入函数中----因此形象第命名为self。它总是对象自身。
显然这就是self的用处和存在的必要性。没有它的话,成员方法就没法访问他们要对其特性进行操作的对象本身了。
和之前一样,特性是可以在外部访问的。
【示例】
print(foo.name)
bar.name =
'Yoda'
bar.greet()
【结果】
Luke Skywalker
Hello, world! I'm Yoda
提示:如果知道foo是Person的实例的话,那么还可以把foo.greet()看作Person.greet(foo)方便的写法
7.2.3 特性、函数和方法
self参数事实上正是方法和函数的区别。方法(更专业一点可以称为绑定方法)将它们的第一个参数绑定到所属的实例上。因此这个参数可以不必提供。
【示例】
class Class:
def method(self):
print("I have a self!")
def function():
print("I don't ...")
instance = Class()
instance.method()
instance.method = function
instance.method()
【结果】
I have a self!
I don't ...
注意:self参数并不取决于调用方法的方式,目前使用的是实例调用方法,可以随意使用引用同一个方法的其他变量:
【示例】
class Bird:
song =
'Squaawk!'
def sing(self):
print(self.song)
bird = Bird()
bird.sing()
birdsong = bird.sing
birdsong()
【结果】
Squaawk!
Squaawk!
#尽管最后一个方法调用看起来与函数调用十分相似,但是变量birdsong引用绑定方法bird.sing上,也就意味着这还是对self参数的访问(也就是说,它仍旧绑定到类的相同实例上)。
再论私有化,默认情况下,程序可以从外部访问一个对象的特性。再次使用前面讨论过的有关封装的例子。有些程序员觉得这样做是可以的,但是有些人觉得这样就破坏了封装的原则。他们认为对象的状态对于外部应该是完全隐藏的。有人觉得为什么他们会站在如此极端的立场上。每个对象管理自己的特性还不够吗?为什么还要对外面世界隐藏呢?毕竟如果能直接使用CloseObject的name特性的话,就不用使用setName和getName方法了。
关键在于,其他程序员可能不知道(可能也不应该知道)你的对象内部的具体操作。例如,CloseObject可能会在其他对象更改自己名字的时候,给一些管理员发送邮件信息。但是如果直接用c.name设定名字会发生什么?什么都没发生,Email也没法出去。为了避免这类事情的发生,应该使用私有(private)特性,这是外部对象无法访问,但getName和setName等访问器能访问的特性。
Python并不直接支持私有方式,而要靠程序员自己把握在外部进行特性修改的时机。为了让方法或者特性变为私有(从外部无法访问),只要在它的名字前面加上双下划线即可:
class Secretive:
def __inaccessible(self):
print("Bet you can't see me...")
def accessible(self):
print("The secret message is:")
self.__inaccessible()
现在,__inaccessible从外界是无法访问的,而在类内部还能使用(比如从accessible)访问:������