zz Python学习备忘录
Python学习备忘录
类中的特殊方法
一般说来,特殊的方法都被用来模仿某个行为。例如,如果你想要为你的类使用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 <name>
__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。
repr函数用来取得对象的规范字符串表示。反引号(也称转换符)可以完成相同的功能。注意,在大多数时候有eval(repr(object)) == object。>>> mylist = [ ' item ' ]
>>> assert len(mylist) >= 1
>>> mylist.pop()
' item '
>>> assert len(mylist) >= 1
Traceback (most recent call last):
File " <stdin> " , line 1 , in ?
AssertionError repr函数
>>> 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类被称为导出类或子类 。
现在,我们将学习一个例子程序。
使用继承
# 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 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/