类最终解释了面向对象编程思想(OOP),Python中有关派生或子类化及继承机理都比较重要。Python也可以在特定功能方面定制类,例如重载操作符、模拟Python类型等。
(1)类与实例
类与实例相互关联着:类是对象的定义;而实例是"真正的实物",它存放了类中所定义的对象的具体信息。
定义类:关键字是class,紧接着是一个类名,随后是定义类的类体代码,可以单继承或多继承。object是“所有类之母”,如果类没有继承任何其他父类,object 将作为默认的父类,它位于所有类继承结构的最上层,如果没有直接或间接的子类化一个对象,那就定义了一个经典类。
创建一个实例的过程称作实例化,过程:myFirstObject = MyNewObjectType(),注意没有使用new关键字,以“函数调用”的形式出现,然后把这个新建的实例赋给一个变量(如果没有把这个实例保存到一个变量中,它就没用了,会被自动垃圾收集器回收,因为任何引用指向这个实例)。尽管每个实例都有一个基本的结构,但各自的属性像颜色或尺寸可以改变,这就好比实例的属性。
>>> class MyData(object):
... pass
...
>>> obj=MyData()
>>> obj.x=4
>>> obj.y=5
>>> obj.x*obj.y
20
定义的类没有任何方法或属性,创建了一个实例。它只使用类作为名称空间容器,实例名字obj将obj.x和obj.y
关联起来,obj.x和obj.y是实例属性是实例对象obj的独有属性,而不是类MyData的属性。
实例属性实质上是动态的,不需要在构造器中或其它任何地方为它们预先声明或者赋值。
>>> class AddrBookEntry(object): #类定义
... 'address book entry class'
... def __init__(self, nm, ph): #定义构造器
... self.name = nm #设置 name
... self.phone = ph #设置phone
... print 'Created instance for:', self.name
... def updatePhone(self, newph): #定义方法
... self.phone = newph
... print 'Updated phone# for:', self.name
...
>>>
在AddrBookEntry类的定义中,定义了两个方法:__init__()和updatePhone()__init__()在实例化时被调用,即在 AddrBookEntry()被调用时。可以认为实例化是对__init__()的一种隐式的调用,因为传给AddrBookEntry()的参数完全与__init__()接收到的参数是一样的(除了self是自动传递的)。
>>> john = AddrBookEntry('John Doe', '408-555-1212') #为 John Doe 创建实例
Created instance for: John Doe
>>> jane = AddrBookEntry('Jane Doe', '650-555-1212') #为 Jane Doe 创建实例
Created instance for: Jane Doe
实例化调用,它会自动调用__init__(),self 把实例对象自动传入__init__()。可以把方法中的self用实例名替换掉。当对象 john 被实例化后,它的john.name 就被设置了。另外,如果不存在默认的参数,那么传给__init__()的两个参数在实例化时是必须的。
>>> john.updatePhone('415-555-1212') #更新John Doe的电话
Updated phone# for: John Doe
>>> john.phone
'415-555-1212'
(7)创建子类
>>> class EmplAddrBookEntry(AddrBookEntry):
... 'Employee Address Book Entry class'#员工地址本类
... def __init__(self, nm, ph, id, em):
... AddrBookEntry.__init__(self, nm, ph)
... self.empid = id
... self.email = em
... def updateEmail(self, newem):
... self.email = newem
... print 'Updated e-mail address for:', self.name
...
创建子类EmplAddrBookEntry,当一个类被派生出来,子类继承了基类的属性,子类中不仅定义了__init__()、updatEmail()方法,而且还从AddrBookEntry中继承了updatePhone()方法。
>>> john = EmplAddrBookEntry('John Doe', '408-555-1212',42, '[email protected]')
Created instance for: John Doe
>>> john.email
'[email protected]'
>>> john.updatePhone('415-555-1212')
Updated phone# for: John Doe
抽象指对现实世界问题和实体的本质表现、行为和特征建模,建立一个相关的子集,可以用于描绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
合成
合成扩充了对类的描述,使得多个不同的类合成为一个大的类,来解决现实问题。合成描述了一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为,所有这些合在一起彼此是“有一个”的关系。组件要么通过联合关系组在一块,对子组件的访问是允许的;要么是聚合在一起,封装的组件仅能通过定义好的接口来访问,对于客户程序来说是透明的。Python支持上述两种形式的合成。
派生/继承/继承结构
派生描述了子类的创建,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。继承描述了子类属性从祖先类继承这样一种方式。继承结构表示多“代”派生,可以描述成一个“族谱”,连续的子类与祖先类都有关系。
泛化/特化
泛化表示所有子类与其父类及祖先类有一样的特点,所以子类可以认为同祖先类是“是一个”的关系,因为一个派生对象(实例)是祖先类的一个“例子”。特化描述所有子类的自定义,也就是什么属性让它与其祖先类不同。
多态
多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。多态表明了动态(又名运行时)绑定的存在,允计重载及运行时类型确定和验证。
自省/反射
自省表示给予程序员某种能力来进行像“手工类型检查”的工作,它也被称为反射。这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir()和 type()内建函数,将很难正常工作。还有那些特殊属性,像__dict__、__name__及__doc__。
>>> class C(object):
... foo = 100
...
>>> print C.foo
100
>>> C.foo = C.foo + 1
>>> print C.foo
101
>>>
(2)方法
>>> class MyClass(object):
... 'MyClass class definition' #MyClass类定义
... myVersion = '1.1' #static data静态数据
... def showMyVersion(self): #method方法
... print MyClass.myVersion
...
>>> dir(MyClass)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', \
'__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', \
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'myVersion', 'showMyVersion']
>>> MyClass.__dict__
>>> print MyClass.__dict__
{'__module__': '__main__', 'showMyVersion': , \
'__dict__': , 'myVersion': '1.1', \
'__weakref__': , '__doc__': 'MyClass class definition'}
dir()返回的仅是对象的属性的一个名字列表,而__dict__返回的是一个字典,它的键(keys)是属性名,键值(values)是相应的属性对象的数据值。MyClass中两个熟悉的属性:showMyVersion和myVersion,以及一些新的属性。这些属性__doc__和__module__是所有类都具备的特殊类属性(另外还有__dict__)。内建的vars()函数接受类对象作为参数,返回类的__dict__属性的内容。
>>> MyClass.__name__
'MyClass'
>>> MyClass.__doc__
'MyClass class definition'
>>> MyClass.__bases__
(,)
>>> MyClass.__module__
'__main__'
>>> MyClass.__class__
__name__是给定类的字符名字,它适用于那种只需要字符串(类对象的名字),而非类对象本身的情况。type()是一个内建类型,它返回被调用对象的类型,可以使用类型对象的__name__属性来取得相应的字符串名。_doc__是类的文档字符串,与函数及模块的文档字符串相似,必须紧随头行(header line)后的字符串,文档字符串不能被派生类继承,也就是说派生类必须含有它们自己的文档字符串。_bases__用来处理继承,它包含了一个由所有父类组成的元组。__dict__属性包含一个字典,由类的数据属性组成。Python支持模块间的类继承,__module__这样类名就完全由模块名所限定,类C的全名是“__main__.C”,如果类C位于一个导入的模块中,如from mymod import C,C.__module__为'mymod'
>>> class Singleton(object):
... __instance = None
... def __init__(self):
... pass
... def __new__(cls, *args, **kwd):
... if Singleton.__instance is None:
... Singleton.__instance = object.__new__(cls, *args, **kwd)
... print "create instance"
... return Singleton.__instance
...
>>> first = Singleton()
create instance
>>> second = Singleton()
>>> first
<__main__.Singleton object at 0x7fb470f76850>
>>> second
<__main__.Singleton object at 0x7fb470f76850>
(4)__del__()"解构器"方法>>> class InstCt(object):
... count = 0 # count is class attr count 是一个类属性
... def __init__(self): # increment count 增加 count
... InstCt.count += 1
... def __del__(self): # decrement count 减少 count
... InstCt.count -= 1
... def howMany(self): # return count 返回 count
... return InstCt.count
...
>>> a = InstCt()
>>> b = InstCt()
>>> b.howMany()
2
>>> del b
>>> a.howMany
>
>>> a.howMany()
1
>>> del a
>>> InstCt.count
0
>>> x = 3+0.14j
>>> x.__class__
>>> x.imag
0.14000000000000001
>>> x.real
3.0
>>> x.conjugate()
(3-0.14000000000000001j)
(4)实例属性 vs 类属性
>>> class C(object): #define class
... version = 1.2 #static member
...
>>> c = C() #instantiation实例化
>>> C.version #access via class
1.2
>>> c.version
1.2
>>> C.version += 0.1
>>> C.version
1.3
>>> c.version
1.3
当实例c被创建后,访问c.version, Python首先会在实例中搜索名字version,然后是类,再就是继承树中的基类,都没找到报出AttributeError异常。然而只有当使用类引用version时,才能更新它的值,像C.version递增语句。如果在实例中设定或更新类属性会创建一个实例属性c.version,后者会阻止对类属性C.versioin的访问,因为第一个访问的就是c.version,这样可以对实例有效地“遮蔽”类属性C.version,直到c.version被清除掉。
>>> c.version += 0.1
>>> c.version
1.4000000000000001
>>> C.version
1.3
从实例中访问类属性须谨慎,任何对实例属性的赋值都会创建一个实例属性(如果不存在的话)并且对其赋值。如果类属性中存在同名的属性,有趣的副作用即产生(经典类和新式类都存在)。
>>> class Foo(object):
... x = 1.5
...
>>> foo = Foo()
>>> foo.x
1.5
>>> foo.x=1.7
>>> foo.x
1.7
>>> Foo.x
1.5
>>> del foo.x # delete instance attribute
>>> foo.x # can now access class attr again
1.5
实例属性foo.x覆盖了对类属性Foo.x的引用,而类属性本身并没有受到伤害,仍然存在于类域中,还可以通过类属性来访问它。使用del语句删除
实例属性foo.x,那通过foo.x又可以访问到类属性了。所以给一个与类属性同名的实例属性赋值,会有效地“隐藏”类属性,但一旦删除了这个实例属性,类属性又重见天日。>>> class Foo(object):
... x = {2003: 'poe2'}
...
>>> foo = Foo()
>>> foo.x
{2003: 'poe2'}
>>> foo.x[2004] = 'valid path'
>>> foo.x
{2003: 'poe2', 2004: 'valid path'}
>>> Foo.x # it works
{2003: 'poe2', 2004: 'valid path'}
>>> del foo.x # no shadow so cannot delete
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'Foo' object attribute 'x' is read-only
>>>
类属性(
静态成员)持久性,任凭整个实例(及其属性)的如何进展,它都独立于实例。同时,当一个实例在类属性被修改后才创建,那么更新的值就将生效。类属性的修改会影响到所有的实例。
使用实例属性来试着修改类属性是很危险的,
原因在于实例拥有它们自已的属性集,在Python中没有明确的方法来指示想要修改同名的类属性(无global关键字可以用来在一个函数中设置一个全局变),因此修改类属性需要使用类名而不使用实例名。
>>> class AddrBookEntry(object):
... def __init__(self, nm, ph):
... self.name = nm
... self.phone = ph
... print 'Created instance for:', self.name
...
>>> class EmplAddrBookEntry(AddrBookEntry):
... def __init__(self, nm, ph, em):
... AddrBookEntry.__init__(self, nm, ph)
... self.empid = id
... self.email = em
...
>>>
EmplAddrBookEntry中重载了构造器__init__()。尽可能多地重用代码,而不是去从父类构造器中剪切粘贴代码,这样做还可以避免BUG传播,只需要能够调用父类的构造器即可。当一个EmplAddrBookEntry被实例化,并且调用__init__()时,其父类只有很少的差别,主要是在子类中自定义一些特性。这是调用非绑定方法的最佳地方了,在子类构造器中调用父类的构造器并且明确地传递
>>> class TestStaticMethod:
... def foo():
... print 'calling static method foo()'
... foo = staticmethod(foo)
...
>>> class TestClassMethod:
... def foo(cls):
... print 'calling class method foo()'
... print 'foo() is part of class:', cls.__name__
... foo = classmethod(foo)
...
>>> tsm = TestStaticMethod()
>>> TestStaticMethod.foo()
calling static method foo()
>>> tsm.foo()
calling static method foo()
>>> tcm = TestClassMethod()
>>> tcm.foo()
calling class method foo()
foo() is part of class: TestClassMethod
>>> TestClassMethod.foo()
calling class method foo()
foo() is part of class: TestClassMethod
>>>
(2)使用函数修饰符实现
静态方法和类方法
>>> class TestStaticMethod:
... @staticmethod
... def foo():
... print 'calling static method foo()'
...
>>> class TestClassMethod:
... @classmethod
... def foo(cls):
... print 'calling class method foo()'
... print 'foo() is part of class:', cls.__name__
...
>>>
>>> class NewAddrBookEntry(object):
... def __init__(self, nm, ph): # define constructor
... self.name = Name(nm) # create Name instance
... self.phone = Phone(ph)
... print 'Created instance for:', self.name
...
如果在设计的过程中,为names、addresses等等创建了单独的类。那么最后可能想把这些工作组合到AddrBookEntry类中去,而不是重新设计每一个需要的类。这样就节省了时间和精力,而且最后的结果是容易维护的代码。NewAddrBookEntry类由它自身和其它类组合而成,这就在一个类和其它组成类之间定义了一种组合关系。
创建复合对象就可以实现这些附加的功能,并且很有意义,因为这些类都不相同。每一个类管理它们自己的名字空间和行为。不过当对象之间有更接近的关系时,派生的概念可能更有意义,特别是当你需要一些相似的对象,但却有少许不同功能的时候。
>>> class P: # parent class
... 'P class'
... def __init__(self):
... print 'created an instance of', self.__class__.__name__
...
>>> class C(P): # child class
... pass
...
>>> p = P() # parent instance
created an instance of P
>>> p.__class__ # class that created us
>>> P.__doc__ # parent's doc string
'P class'
>>> c = C() # child instance
created an instance of C
>>> c.__class__ # class that created us
>>> P.__bases__ # parent's parent class(es)
()
>>> C.__bases__ # child's parent class(es)
(,)
>>> C.__doc__
>>>
C没有声明__init__()方法,然而在类 C 的实例c被创建时,还是会有输出信息,原因在于C继承了P的__init__(),若C中重写
__init__()方法则不会执行父类的
__init__()。__bases__元组列出了其父类 P。需要注意的是文档字符串对类、函数/方法、还有模块来说都是唯一的,,所以特殊属性__doc__不会从基类中继承过来。
>>> class A(object): pass
...
>>> class B: pass
...
>>> class C(B): pass
...
>>> class D(A, C): pass
...
>>> A.__bases__
(,)
>>> B.__bases__
()
>>> C.__bases__
(,)
>>> D.__bases__
(, )
>>>
(2)通过继承覆盖方法
>>> class P(object):
... def foo(self):
... print 'Hi, I am P-foo()'
...
>>> class C(P):
... def foo(self):
... print 'Hi, I am C-foo()'
...
>>> p = P()
>>> c = C()
>>> p.foo()
Hi, I am P-foo()
>>> c.foo()
Hi, I am C-foo()
>>> P.foo(c)
Hi, I am P-foo()
>>>
尽管C继承了P的 foo()方法,但因为C定义了它自已的 foo()方法,所以P中的foo()方法被覆盖。覆盖方法的原因之一是,子类可能需要这个方法具有特定或不同的功能。若需要去调用一个未绑定的基类方法,明确给出子类的实例:P.foo(c)。实际情况下,不需要P的实例调用P的方法,因为已经有一个P的子类的实例c可用,可以在子类的重写方法里显式地调用基类方法。
>>> class C(P):
... def foo(self):
... P.foo(self)
... print 'Hi, I am C-foo()'
...
>>>
在这个(未绑定)方法调用中我们显式地传递了self,一个更好的办法是使用super()内建方法,super()不但能找到基类方法,而且还为我们传进self,这样我们就不需要做这些事了。
>>> class C(P):
... def foo(self):
... super(C, self).foo()
... print 'Hi, I am C-foo()'
...
>>>
注意:子类重写__init__不会自动调用基类的__init__,即当从一个带构造器 __init()__的类派生,如果不去覆盖__init__(),它将会被继承并自动调用;但如果在子类中覆盖了__init__(),子类被实例化时,基类的__init__()就不会被自动调用。如果还想调用基类的__init__(),使用一个子类的实例去调用基类(未绑定)方法,如P.__init__(self)。子类的__init__()方法首先调用了基类的的__init__()方法,这是相当普遍的做法,用来设置初始化基类,然后可以执行子类内部的设置。这个规则之所以有意义的原因是:希望被继承的类的对象在子类构造器运行前能够很好地被初始化或作好准备工作,因为子类可能需要或设置继承属性。也可以使用super(P, self).__init__()调用基类的__init__,使用super()的重点是不需要明确提供父类,这意味着如果改变了类继承关系,只需要改一行代码(class 语句本身)而不必在大量代码中去查找所有被修改的那个类的名字。
>>> class RoundFloat(float):
... def __new__(cls, val):
... return super(RoundFloat, cls).__new__(cls, round(val, 2))
...
覆盖了__new__()特殊方法来定制自己的对象,使之和标准Python浮点数(float)有一些区别,round()内建函数对原浮点数进行舍入操作。然后实例化float、RoundFloat。通过调用父类的构造器来创建真实的对象float.__new__()。使用 super()内建函数去捕获对应的父类以调用它的__new()__方法。>>> class SortedKeyDict(dict):
... def keys(self):
... return sorted(super( SortedKeyDict, self).keys())
...
>>> d = SortedKeyDict((('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2)))
>>> print 'By iterator:'.ljust(12), [key for key in d]
By iterator: ['zheng-cai', 'xin-yi', 'hui-jun']
>>> print 'By keys():'.ljust(12), d.keys()
By keys(): ['hui-jun', 'xin-yi', 'zheng-cai']
通过keys迭代过程是以散列顺序的形式,而使用我们(重写的)keys()方法则将keys变为字母排序方式了。>>> class P1(object):
... def foo(self):
... print 'called P1-foo()'
...
>>> class P2(object):
... def foo(self):
... print 'called P2-foo()'
...
>>> class P2(object):
... def foo(self):
... print 'called P2-foo()'
... def bar(self):
... print 'called P2-bar()'
...
>>> class C1(P1, P2):
... pass
...
>>> class C2(P1, P2):
... def bar(self):
... print 'called C2-bar()'
...
>>> class GC(C1, C2):
... pass
上述由一组父类、一组子类、还有一个子孙类组成,
P1中定义了foo(),
P2定义了foo()和bar(),C2 定义了 bar()。父类、子类及子孙类的关系图如下:
>>> gc = GC()
>>> gc.foo() # GC ==> C1 ==> P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> P1 ==> P2
called P2-bar()
当调用 foo()时,它首先在当前类(GC)中查找。如果没找到,就向上查找最亲的父类C1。查找未遂,就继续沿树上访到父类P1,foo()被找到。同样,
对bar()来说,它通过搜索GC、C1、P1,然后在P2中找到。因为使用这种解释顺序的缘故,C2.bar()根本就不会被搜索了。调用C2的 bar()方法,必须调用它的合法的全名,采用典型的非绑定方式去调用,如C2.bar(gc)。
>>> gc = GC()
>>> gc.foo() # GC ==> C1 ==> C2 ==> P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> C2
called C2-bar()
与沿着继承树一步一步上溯不同,它首先查找同胞兄弟,采用一种广度优先的方式。当查找foo(),它检查 GC,然后是C1和C2,然后在P1中找到。如果P1中没有,查找将会到达P2。foo()的底线是包括经典类和新式类都会在P1中找到它,然而它们虽然是同归,但殊途。然而,bar()的结果是不同的,它搜索GC和C1,紧接着在C2中找到了。这样,就不会再继续搜索到祖父P1和P2。这种情况下,新的解释方式更适合需要查找GC更亲近的bar()的方案。还需要调用上一级的话使用非绑定的方式去做:P2.bar(gc)。
>>> GC.__mro__
(, , , , , )
(5)内部类的使用
>>> class Car:
... class Door:
... def open(self):
... print "open door"
... class Wheel:
... def run(self):
... print "car run"
...
>>> car = Car()
>>> backDoor = Car.Door() #外部类调用内部类,生成内部类的实例
>>> backDoor.open()
open door
>>> frontDoor = car.Door() #外部类进行实例化,然后再实例化内部类
>>> frontDoor.open()
open door
(6)抽象类的模拟
>>> def abstract():
... raise NotImplementedError("abstract")
...
>>> class Fruit:
... def __init__(self):
... if self.__class__ is Fruit:
... abstract()
... print "Fruit"
...
>>> class Apple(Fruit):
... def __init__(self):
... Fruit.__init__(self)
... print "Apple"
...
>>> apple = Apple()
Fruit
Apple
>>> fruit = Fruit()
Traceback (most recent call last):
File "", line 1, in
File "", line 4, in __init__
File "", line 2, in abstract
NotImplementedError: abstract
同样,Python也没有提供对接口的支持,接口是特殊的抽象类,接口没有数据成员,而是一组未实现的方法的集合。Python去掉了面向对象中很对复杂的特性,例如类的属性和方法的限制符号(public、protected、private)。
>>> class Fruit:
... def __init__(self, color = None):
... self.color = color
...
>>> class Apple(Fruit):
... def __init__(self, color = "red"):
... Fruit.__init__(self, color)
...
>>> class Banana(Fruit):
... def __init__(self, color = "yellow"):
... Fruit.__init__(self, color)
...
>>> class FruitShop:
... def sellFruit(self, fruit):
... if isinstance(fruit, Apple):
... print "sell apple"
... if isinstance(fruit, Banana):
... print "sell banana"
... if isinstance(fruit, Fruit):
... print "sell fruit"
...
>>> shop = FruitShop()
>>> apple = Apple()
>>> banana = Banana()
>>> shop.sellFruit(apple)
sell apple
sell fruit
>>> shop.sellFruit(banana)
sell banana
sell fruit
sellFruit()根据不同的水果类型返回不同的结果,即sellFruit()根据多态性的特性,作出不同的处理。
>>> isinstance(4, int)
True
>>> isinstance('4', str)
True
同issubclass()一样,isinstance()也可以使用一个元组(tuple)作为第二个参数,如果第一个参数是第二个参数中给定元组的任何一个候选类型或类的实例时,就会返回 True。
>>> class myClass(object):
... def __init__(self):
... self.foo = 100
...
>>> myInst = myClass()
>>> hasattr(myInst, 'foo')
True
>>> getattr(myInst, 'foo')
100
>>> getattr(myInst, 'bar', 'not have!')
'not have!'
>>> setattr(myInst, 'bar', 'my attr')
>>> dir(myInst)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', \
'__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', \
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo']
>>> getattr(myInst, 'bar')
'my attr'
>>> delattr(myInst, 'foo')
>>> hasattr(myInst, 'foo')
False
(4)dir()
>>> class C(object):
... pass
...
>>> c = C()
>>> c.foo = 100
>>> c.bar = 'Python'
>>> c.__dict__
{'foo': 100, 'bar': 'Python'}
>>> vars(c)
{'foo': 100, 'bar': 'Python'}
双下划线(__),是Python为类元素(属性和方法)的私有性提供初步的形式。由双下划线开始的属性在运行时被“混淆”,所以直接访问是不允许的。实际上,会在名字前面加上下划线和类名。比如以例self.__num属性为例,被“混淆”后,用于访问这个数据值的标识就变成了self._NumStr__num。把类名加上后形成的新的“混淆”结果将可以防止在祖先类或子孙类中的同名冲突。尽管这样做提供了某种层次上的私有化,但算法处于公共域中并且很容易被“击败”。这种名字混淆的另一个目的,是为了保护__XXX 变量不与父类名字空间相冲突。如果在类中有一个__XXX 属性,它将不会被其子类中的__XXX 属性覆盖。如果父类仅有一个 XXX 属性,子类也定义了这个,这时子类的XXX就覆盖了父类的XXX。而使用__XXX,子类的代码就可以安全地使用__XXX,而不必担心它会影响到父类中的__XXX。
>>> class Secretive:
... def __inaccessible(self):
... print "Bet you can't see me..."
...
>>> s = Secretive()
>>> s.__inaccessible()
Traceback (most recent call last):
File "", line 1, in
AttributeError: Secretive instance has no attribute '__inaccessible'
>>> s._Secretive__inaccessible()
Bet you can't see me...
单下划线(_),简单的模块级私有化只需要在属性名前使用一个单下划线字符。这就防止模块的属性用“from mymodule import *来加载。这是严格基于作用域的,所以这同样适合于函数。尽管Python没有在语法上把private、protected、friend或protected等特征内建于语言中,但是可以按需要严格地定制访问权。
运算符用于表达式的计算,但对于自定义的对象并不能进行计算。运算符的重载可以实现对象之间的运算,Python把运算符和类的内置方法关联起来,每个运算符对应一个函数。
实现对运算符"+"(__add__()表示运算符加)和运算符">"(__gt__()表示运算符大于)的重载。
>>> class Fruit:
... def __init__(self, price = 0):
... self.price = price
... def __add__(self, other): #重载+运算符
... return self.price + other.price
... def __gt__(self, other):
... if self.price > other.price #重载>运算符
File "", line 7
if self.price > other.price #重载>运算符
^
SyntaxError: invalid syntax
>>> class Fruit:
... def __init__(self, price = 0):
... self.price = price
... def __add__(self, other): #重载+运算符
... return self.price + other.price
... def __gt__(self, other): #重载>运算符
... if self.price > other.price:
... flag = True
... else:
... flag = False
... return flag
...
>>> class Apple(Fruit):pass
...
>>> class Banana(Fruit):pass
...
>>> apple = Apple(3)
>>> print "the price of apple:", apple.price
the price of apple: 3
>>> banana = Banana(4)
>>> print "the price of banana:", banana.price
the price of banana: 4
>>> total = apple + banana
>>> print "total:", total
total: 7
>>> print apple > banana
False
C++支持<<、>>等流操作符,Python可以对流操作符进行重载,实现类似C++的流操作。如下实现对运算符<< 的重载:
>>> import sys
>>> class Stream:
... def __init__(self, file):
... self.file = file
... def __lshift__(self, obj):
... self.file.write(str(obj))
... return self
...
>>> class Fruit(Stream):
... def __init__(self, price = 0, file = None):
... Stream.__init__(self, file)
... self.price = price
...
>>> class Apple(Fruit):pass
...
>>> class Banana(Fruit):pass
...
>>> apple = Apple(3, sys.stdout)
>>> banana = Banana(4, sys.stdout)
>>> endl = "\n"
>>> apple << apple.price <
>>> banana << banana.price <
>>> class Factory:
... def createFruit(self, fruit):
... if fruit == "apple":
... return Apple()
... if fruit == "banana":
... return Banana()
...
>>> class Fruit:
... def __str__(self):
... return "fruit"
...
>>> class Apple(Fruit):
... def __str(self):
... return "apple"
...
>>> class Banana(Fruit):
... def __str__(self):
... return "banana"
...
>>> factory = Factory()
>>> print factory.createFruit("apple")
fruit
>>> print factory.createFruit("banana")
banana