类中的特殊方法
一般说来,特殊的方法都被用来模仿某个行为。例如,如果你想要为你的类使用x[key]这样的索引操作(就像列表和元组一样),那么你只需要实现__getitem__()方法就可以了。想一下,Python就是对list类这样做的!
下面这个表中列出了一些有用的特殊方法。如果你想要知道所有的特殊方法,你可以在《Python参考手册》中找到一个庞大的列表。
名称 说明
---------------------------------------------------------
__init__
(self,...) 这个方法在新建对象恰好要被返回使用之前被调用。
__del__
(self) 恰好在对象要被删除之前调用。
__str__
(self) 在我们对对象使用print语句或是使用str()的时候调用。
__lt__
(self,other) 当使用 小于 运算符(
<
)的时候调用。类似地,对于所有的运算符(
+
,
>
等等)都有特殊的方法。
__getitem__
(self,key) 使用x[key]索引操作符的时候调用。
__len__
(self) 对序列对象使用内建的len()函数的时候调用。
__repr__
(s) repr()
and
`...` conversions
__cmp__
(s, o) Compares s to o
and
returns
<
0, 0,
or
>
0.
Implements
>
,
<
,
==
etc...
__hash__
(s) Compute a
32
bit hash code; hash()
and
dictionary ops
__nonzero__
(s) Returns 0
or
1
for
truth value testing
__getattr__
(s, name) called when attr lookup doesn
'
t find
__setattr__
(s, name, val) called when setting an attr
(inside, don
'
t use "self.name = value"
use
"
self.__dict__[name] = val
"
)
__delattr__
(s, name) called to delete attr
<
name
>
__call__
(self,
*
args) called when an instance
is
called as function.
exec和eval语句
exec语句用来执行储存在字符串或文件中的Python语句。例如,我们可以在运行时生成一个包含Python代码的字符串,然后使用exec语句执行这些语句。
下面是一个简单的例子。
>>>
exec
'
print "Hello World"
'
Hello World
eval语句用来计算存储在字符串中的有效Python表达式。下面是一个简单的例子。
>>>
eval(
'
2*3
'
)
6
assert语句
assert语句用来声明某个条件是真的。例如,如果你非常确信某个你使用的列表中至少有一个元素,而你想要检验这一点,并且在它非真的时候引发一个错误,那么assert语句是应用在这种情形下的理想语句。当assert语句失败的时候,会引发一个AssertionError。
>>>
mylist
=
[
'
item
'
]
>>>
assert
len(mylist)
>=
1
>>>
mylist.pop()
'
item
'
>>>
assert
len(mylist)
>=
1
Traceback (most recent call last):
File
"
"
, line
1
,
in
?
AssertionError repr函数
repr函数用来取得对象的规范字符串表示。反引号(也称转换符)可以完成相同的功能。注意,在大多数时候有eval(repr(object)) == object。
>>>
i
=
[]
>>>
i.append(
'
item
'
)
>>>
`i`
"
['item']
"
>>>
repr(i)
"
['item']
"
基本上,repr函数和反引号用来获取对象的可打印的表示形式。你可以通过定义类的__repr__方法来控制你的对象在被repr函数调用的时候返回的内容。
类和实例变量
有两种类型的域 —— 类的变量和对象的变量,它们根据是类还是对象拥有这个变量而区分。
类的变量 由一个类的所有对象(实例)共享使用。只有一个类变量的拷贝,所以当某个对象对类的变量做了改动的时候,这个改动会反映到所有其他的实例上。
对象的变量 由类的每个对象/实例拥有。因此每个对象有自己对这个域的一份拷贝,即它们不是共享的,在同一个类的不同实例中,虽然对象的变量有相同的名称,但是是互不相关的。通过一个例子会使这个易于理解。
#
!/usr/bin/python
#
Filename: objvar.py
class
Person:
'''
Represents a person.
'''
population
=
0
def
__init__
(self, name):
'''
Initializes the person's data.
'''
self.name
=
name
print
'
(Initializing %s)
'
%
self.name
#
When this person is created, he/she
#
adds to the population
Person.population
+=
1
def
__del__
(self):
'''
I am dying.
'''
print
'
%s says bye.
'
%
self.name
Person.population
-=
1
if
Person.population
==
0:
print
'
I am the last one.
'
else
:
print
'
There are still %d people left.
'
%
Person.population
def
sayHi(self):
'''
Greeting by the person.
Really, that's all it does.
'''
print
'
Hi, my name is %s.
'
%
self.name
def
howMany(self):
'''
Prints the current population.
'''
if
Person.population
==
1
:
print
'
I am the only person here.
'
else
:
print
'
We have %d persons here.
'
%
Person.population
swaroop
=
Person(
'
Swaroop
'
)
swaroop.sayHi()
swaroop.howMany()
kalam
=
Person(
'
Abdul Kalam
'
)
kalam.sayHi()
kalam.howMany()
swaroop.sayHi()
swaroop.howMany()输出
___FCKpd___5nbsp;python objvar.py
(Initializing Swaroop)
Hi, my name
is
Swaroop.
I am the only person here.
(Initializing Abdul Kalam)
Hi, my name
is
Abdul Kalam.
We have
2
persons here.
Hi, my name
is
Swaroop.
We have
2
persons here.
Abdul Kalam says bye.
There are still
1
people left.
Swaroop says bye.
I am the last one.
这是一个很长的例子,但是它有助于说明类与对象的变量的本质。这里,population属于Person类,因此是一个类的变量。name变量属于对象(它使用self赋值)因此是对象的变量。
观察可以发现__init__方法用一个名字来初始化Person实例。在这个方法中,我们让population增加1,这是因为我们增加了一个人。同样可以发现,self.name的值根据每个对象指定,这表明了它作为对象的变量的本质。
记住,你只能使用self变量来参考同一个对象的变量和方法。这被称为 属性参考 。
在这个程序中,我们还看到docstring对于类和方法同样有用。我们可以在运行时使用Person.__doc__和Person.sayHi.__doc__来分别访问类与方法的文档字符串。
就如同__init__方法一样,还有一个特殊的方法__del__,它在对象消逝的时候被调用。对象消逝即对象不再被使用,它所占用的内存将返回给系统作它用。在这个方法里面,我们只是简单地把Person.population减1。
当对象不再被使用时,__del__方法运行,但是很难保证这个方法究竟在什么时候运行。如果你想要指明它的运行,你就得使用del语句,就如同我们在以前的例子中使用的那样。
给C++/Java/C#程序员的注释
Python中所有的类成员(包括数据成员)都是公共的 ,所有的方法都是有效的。
只有一个例外:如果你使用的数据成员名称以双下划线前缀 比如__privatevar,Python的名称管理体系会有效地把它作为私有变量。
这样就有一个惯例,如果某个变量只想在类或对象中使用,就应该以单下划线前缀。而其他的名称都将作为公共的,可以被其他类/对象使用。记住这只是一个惯例,并不是Python所要求的(与双下划线前缀不同)。
同样,注意__del__方法与destructor的概念类似。
继承
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。继承完全可以理解成类之间的类型和子类型关系。
假设你想要写一个程序来记录学校之中的教师和学生情况。他们有一些共同属性,比如姓名、年龄和地址。他们也有专有的属性,比如教师的薪水、课程和假期,学生的成绩和学费。
你可以为教师和学生建立两个独立的类来处理它们,但是这样做的话,如果要增加一个新的共有属性,就意味着要在这两个独立的类中都增加这个属性。这很快就会显得不实用。
一个比较好的方法是创建一个共同的类称为SchoolMember然后让教师和学生的类继承 这个共同的类。即它们都是这个类型(类)的子类型,然后我们再为这些子类型添加专有的属性。
使用这种方法有很多优点。如果我们增加/改变了SchoolMember中的任何功能,它会自动地反映到子类型之中。例如,你要为教师和学生都增加一个新的身份证域,那么你只需简单地把它加到SchoolMember类中。然而,在一个子类型之中做的改动不会影响到别的子类型。另外一个优点是你可以把教师和学生对象都作为SchoolMember对象来使用,这在某些场合特别有用,比如统计学校成员的人数。一个子类型在任何需要父类型的场合可以被替换成父类型,即对象可以被视作是父类的实例,这种现象被称为多态现象。
另外,我们会发现在重用父类的代码的时候,我们无需在不同的类中重复它。而如果我们使用独立的类的话,我们就不得不这么做了。
在上述的场合中,SchoolMember类被称为基本类或超类 。而Teacher和Student类被称为导出类或子类 。
现在,我们将学习一个例子程序。
使用继承
#
!/usr/bin/python
#
Filename: inherit.py
class
SchoolMember:
'''
Represents any school member.
'''
def
__init__
(self, name, age):
self.name
=
name
self.age
=
age
print
'
(Initialized SchoolMember: %s)
'
%
self.name
def
tell(self):
'''
Tell my details.
'''
print
'
Name:"%s" Age:"%s"
'
%
(self.name, self.age),
class
Teacher(SchoolMember):
'''
Represents a teacher.
'''
def
__init__
(self, name, age, salary):
SchoolMember.
__init__
(self, name, age)
self.salary
=
salary
print
'
(Initialized Teacher: %s)
'
%
self.name
def
tell(self):
SchoolMember.tell(self)
print
'
Salary: "%d"
'
%
self.salary
class
Student(SchoolMember):
'''
Represents a student.
'''
def
__init__
(self, name, age, marks):
SchoolMember.
__init__
(self, name, age)
self.marks
=
marks
print
'
(Initialized Student: %s)
'
%
self.name
def
tell(self):
SchoolMember.tell(self)
print
'
Marks: "%d"
'
%
self.marks
t
=
Teacher(
'
Mrs. Shrividya
'
,
40
,
30000
)
s
=
Student(
'
Swaroop
'
,
22
,
75
)
print
#
prints a blank line
members
=
[t, s]
for
member
in
members:
member.tell()
#
works for both Teachers and Students输出
$ python inherit.py
(Initialized SchoolMember: Mrs. Shrividya)
(Initialized Teacher: Mrs. Shrividya)
(Initialized SchoolMember: Swaroop)
(Initialized Student: Swaroop)
Name:
"
Mrs. Shrividya
"
Age:
"
40
"
Salary:
"
30000
"
Name:
"
Swaroop
"
Age:
"
22
"
Marks:
"
75
"
为了使用继承,我们把基本类的名称作为一个元组跟在定义类时的类名称之后。然后,我们注意到基本类的__init__方法专门使用self变量调用,这样我们就可以初始化对象的基本类部分。这一点十分重要——Python不会自动调用基本类的constructor,你得亲自专门调用它。
我们还观察到我们在方法调用之前加上类名称前缀,然后把self变量及其他参数传递给它。
注意,在我们使用SchoolMember类的tell方法的时候,我们把Teacher和Student的实例仅仅作为SchoolMember的实例。
另外,在这个例子中,我们调用了子类型的tell方法,而不是SchoolMember类的tell方法。可以这样来理解,Python总是首先查找对应类型的方法,在这个例子中就是如此。如果它不能在导出类中找到对应的方法,它才开始到基本类中逐个查找。基本类是在类定义的时候,在元组之中指明的。
一个术语的注释——如果在继承元组中列了一个以上的类,那么它就被称作多重继承 。
字符串
字符串是字符的序列。字符串基本上就是一组单词。
我几乎可以保证你在每个Python程序中都要用到字符串,所以请特别留心下面这部分的内容。下面告诉你如何在Python中使用字符串。
* 使用单引号(')
你可以用单引号指示字符串,就如同'Quote me on this'这样。所有的空白,即空格和制表符都照原样保留。
* 使用双引号(")
在双引号中的字符串与单引号中的字符串的使用完全相同,例如"What's your name?"。
* 使用三引号('''或""")
利用三引号,你可以指示一个多行的字符串。你可以在三引号中自由的使用单引号和双引号。例如:
'''
This is a multi-line string. This is the first line.
This is the second line.
"What's your name?," I asked.
He said "Bond, James Bond."
'''
* 转义符
假设你想要在一个字符串中包含一个单引号('),那么你该怎么指示这个字符串?例如,这个字符串是What's your name?。你肯定不会用'What's your name?'来指示它,因为Python会弄不明白这个字符串从何处开始,何处结束。所以,你需要指明单引号而不是字符串的结尾。可以通过 转义符 来完成这个任务。你用/'来指示单引号——注意这个反斜杠。现在你可以把字符串表示为'What/'s your name?'。
另一个表示这个特别的字符串的方法是"What's your name?",即用双引号。类似地,要在双引号字符串中使用双引号本身的时候,也可以借助于转义符。另外,你可以用转义符//来指示反斜杠本身。
值得注意的一件事是,在一个字符串中,行末的单独一个反斜杠表示字符串在下一行继续,而不是开始一个新的行。例如:
"This is the first sentence./
This is the second sentence."
等价于"This is the first sentence. This is the second sentence."
* 自然字符串
如果你想要指示某些不需要如转义符那样的特别处理的字符串,那么你需要指定一个自然字符串。自然字符串通过给字符串加上前缀r或R来指定。例如r"Newlines are indicated by /n"。
* Unicode字符串
Unicode是书写国际文本的标准方法。如果你想要用你的母语如北印度语或阿拉伯语写文本,那么你需要有一个支持Unicode的编辑器。类似地,Python允许你处理Unicode文本——你只需要在字符串前加上前缀u或U。例如,u"This is a Unicode string."。
记住,在你处理文本文件的时候使用Unicode字符串,特别是当你知道这个文件含有用非英语的语言写的文本。
* 字符串是不可变的
这意味着一旦你创造了一个字符串,你就不能再改变它了。虽然这看起来像是一件坏事,但实际上它不是。我们将会在后面的程序中看到为什么我们说它不是一个缺点。
* 按字面意义级连字符串
如果你把两个字符串按字面意义相邻放着,他们会被Python自动级连。例如,'What/'s' 'your name?'会被自动转为"What's your name?"。
给C/C++程序员的注释
在Python中没有专门的char数据类型。确实没有需要有这个类型,我相信你不会为此而烦恼。
给Perl/PHP程序员的注释
记住,单引号和双引号字符串是完全相同的——它们没有在任何方面有不同。
给正则表达式用户的注释
一定要用自然字符串处理正则表达式。否则会需要使用很多的反斜杠。例如,后向引用符可以写成'//1'或r'/1'。
私有变量
Python对私有类成员有部分支持。任何象__spam这样形式的标识符(至少有两个前导下划线,至多有一个结尾下划线)目前被替换成_classname__spam,其中classname是所属类名去掉前导下划线的结果。这种搅乱不管标识符的语法位置,所以可以用来定义类私有的实例、变量、方法,以及全局变量,甚至于保存对于此类是私有的其它类的实例。如果搅乱的名字超过255个字符可能会发生截断。在类外面或类名只有下划线时不进行搅乱。
名字搅乱的目的是给类一种定义“私有”实例变量和方法的简单方法,不需担心它的其它类会定义同名变量,也不怕类外的代码弄乱实例的变量。注意搅乱规则主要是为了避免偶然的错误,如果你一定想做的话仍然可以访问或修改私有变量。这甚至是有用的,比如调试程序要用到私有变量,这也是为什么这个漏洞没有堵上的一个原因。(小错误:导出类和基类取相同的名字就可以使用基类的私有变量)。
注意传递给exec,eval()或evalfile()的代码不会认为调用它们的类的类名是当前类,这与global语句的情况类似,global的作用局限于一起字节编译的代码。同样的限制也适用于getattr() ,setattr()和delattr(),以及直接访问__dict__的时候。
下面例子中的类实现了自己的__getattr__和__setattr__方法,把所有属性保存在一个私有变量中,这在Python的新旧版本中都是可行的:
class
VirtualAttributes:
__vdict
=
None
__vdict_name
=
locals().keys()[0]
def
__init__
(self):
self.
__dict__
[self.
__vdict_name
]
=
{}
def
__getattr__
(self, name):
return
self.
__vdict
[name]
def
__setattr__
(self, name, value):
self.
__vdict
[name]
=
value
参考资料:
http://www.e7blog.com/blog/user1/python/
http://www.czug.org/docs/python/TutorialCN/X_e7_ac_ac_e4_b9_9d_e7_ab_a0_e7_b1_bb/