码迷,mamicode.com
首页 > 编程语言 > 详细

python13:类

时间:2015-05-05 12:32:17      阅读:196      评论:0      收藏:0      [点我收藏+]

标签:python

Python是面向对象的语言,Python中的类提供了面向对象的所有特征:多态、封装和继承。
    1)多态:可以对不同的对象使用同样的操作,它会根据对象(或类)类型的不同而表现出不同的行为;
    2)封装:对外部世界隐藏对象的工作细节;
    3)继承:以普通的类为基础建立特定的类对象。

更多面向对象的概念请参考专门的面向对象的书籍,下面将介绍Python中类的定义和使用方法。

定义类

下面是定义一个类的最简单的形式:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>
类就像函数一样,在使用之前必须先定义(支持将类的定义放在if语句的分支,或者函数内部)。
实践中,类中通常是函数定义,但是其它的语句也是允许的,这在有些时候是有用的,这点将在后面详述。类中的函数定义通常有一个特定形式的参数列表,这也将在后面详述。
类的内部有自己的作用域和命名空间,所有的本地变量都进入类的命名空间。尤其是,函数定义绑定新的函数名称。
类定义正常结束后,一个类对象被创建,这基本上是对类定义创建的命名空间的内容的封装,下面我们将对类对象做详细介绍。

类的属性引用和实例化

类对象支持两种操作:属性引用和实例化。
属性引用使用那个标准语法:obj.name。当类被创建时,在类的命名空间中的所有名称都是有效的属性名。例如,如果类定义如下:

class MyClass:
    """A simple example class"""
    i = 12345
    def f(self):
        return 'hello world'
那么MyClass.i和MyClass.f是有效的属性引用,分别返回一个整数和一个函数对象。类属性也可以分配,因此你能修改被分配的MyClass.i的值。__doc__也是一个有效的属性,返回类的文档字符串,即:"A simple example class"。
类实例化使用那个函数符号,就像类对象是一个无参的函数,返回类的一个新的实例。例如:

x = MyClass()
这里创建一个类的新实例并分配这个对象给本地变量x。
实例化操作(“调用”一个类对象)创建一个空对象,一些类可能希望创建对象时设置对象的初始状态,可以定义类的特定方法__init__(),例如:

def __init__(self):
    self.data = []
当类定义了__init__()方法,类实例化会自动调用__init__()。在这个例子中,新实例能通过下面的方法获取,并拥有了一个data属性:

x = MyClass()
__init__()也可以传入参数,参数通过类的实例化操作传给__init__(),例如:

>>> class Complex:
        def __init__(self, realpart, imagpart):
            self.r = realpart
            self.i = imagpart
   
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

实例对象的属性和方法

接下来我们能对实例对象做什么操作呢?实例对象仅支持属性引用,有两种有效的属性名:数据属性和方法。
数据属性对应Smalltalk中的“实例变量”和C++中的“数据成员”。数据属性不需要被定义,同本地变量,当第一次被使用时被自动定义。例如,假定x是上面定义的MyClass的实例,下面的代码将打印16:

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter)
del x.counter
实例对象的另一种属性引用是方法,一个方法是一个属于对象的函数。
实例对象的有效的方法名依赖它在类中的定义,在上面的例子中,x.f值一个有效的方法引用,因为MyClass.f是一个函数,但是x.i不是。但注意x.f和MyClass.f不同,x.f是方法对象,而MyClass.f不是。

方法调用

通常,一个方法使用下面的方法调用:

x.f()

在MyClass的例子中,这个调用将返回‘hello world‘。也可以将方法付给另一个变量,在后面调用:

xf = x.f
while True:
    print(xf())
当一个方法被调用时将发生什么呢?你可能已经注意到,虽然f()在定义时指定了一个参数,但调用时却不需要传入参数,什么原因呢?当一个函数需要一个参数,调用时却没有传入,Python将抛出一个异常,这一点是确定的。你也许已经猜到,方法的第一个参数就是对象本身,在我们的例子中,调用x.f()等价于调用MyClass.f(x),通常,调用一个n个参数的方法等价于调用一个带有同样n个参数的函数,并在第一个参数前插入方法所属的对象。
如果你任然不理解方法怎么运作,那么这是方法调用的具体实现:当一个实例属性被引用时(不是一个数据属性),它的类将被查找到;如果属性名称是一个有效的类属性,并且是一个函数对象,一个方法对象将被创建并指向该实例对象;当这个方法对象被使用参数列表调用时,一个带有实例对象和调用的参数列表的新的参数列表被构建,并用于调用函数对象。

类和实例变量

通常来讲,实例变量对应每个特定实例,而类变量对应所有类的实例:

class Dog:
    kind = 'canine'         # class variable shared by all instances

    def __init__(self, name):
        self.name = name    # instance variable unique to each instance

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind                  # shared by all dogs
'canine'
>>> e.kind                  # shared by all dogs
'canine'
>>> d.name                  # unique to d
'Fido'
>>> e.name                  # unique to e
'Buddy'
这里Dog.kind是类变量,而name则是实例变量,类变量可以在类的不同实例间共享,而实例变量则对应到类的每一个实例。
需要注意的是,共享数据可能引发一些令人惊讶的行为,例如,下面的代码中有一个list在不同的对象间共享:

class Dog:

    tricks = []             # mistaken use of a class variable

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # unexpectedly shared by all dogs
['roll over', 'play dead']
由于类变量tricks可以被类的所有实例共享,所以list中的数据将是不可预测的,导致可能引发一些奇怪的行为,正确的做法应该是为每一个实例对象提供一个tricks变量:

class Dog:

    def __init__(self, name):
        self.name = name
        self.tricks = []    # creates a new empty list for each dog

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']

类的数据属性和方法属性

如果类的数据属性和方法属性具有相同的名称,在大的程序中可能引发一些很难发现的bug。因此,为了避免名字冲突,使用一些命名的约定是有必要的,可以尽量的减少冲突。可能的约定包括大写方法名称、 使用某个小的唯一字符串(也许只是一条下划线)作为数据属性名称的前缀、或使用动词名词对于数据的属性和方法。
数据属性可以被对象的使用者引用,也可以被方法引用。换句话说,类无法实现纯粹的抽象数据类型,实际上,Python无法实现数据隐藏,所有的都是基于约定。
对数据属性的使用应该非常小心,如果你修改了变量的值,可能会影响依赖这个变量的方法的执行。用户还可以为实例对象添加自己的数据属性,但需要避免名称冲突,因此,命名约定可以减少大量令人头痛的问题。
通过方法来引用数据属性往往可以增加可读性,用户不容易混淆本地变量和实例变量。
方法的第一参数通常是self,但这仅仅是一个惯例,但是,遵守这个惯例往往会让其它Python编程人员更容易阅读你的代码,也容易通过编辑器自动生成。
函数对象并不一定要定义在类定义中,先定义一个函数,然后将其分配给类的本地变量也是可以的,例如:

def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1
    def g(self):
        return 'hello world'
    h = g
现在f、g和h都是类C的属性了,执行一个函数对象,并且都将成为C的实例的方法(h和g是完全等同的)。但是,这个在实践中会导致代码的可读性差,不会带来实际好处。
方法可以通过self参数来调用其它方法,例如:

class Bag:
    def __init__(self):
        self.data = []
    def add(self, x):
        self.data.append(x)
    def addtwice(self, x):
        self.add(x)
        self.add(x)
方法也可以引用全局名称(变量或者函数),需要在引用前引入包含该名称定义的模块。
每个值都有一个对象,因此有一个类(也叫做类型),存储在object.__class__中:

>>> a = 1
>>> a.__class__
<class 'int'>
>>> a = 1.1
>>> a.__class__
<class 'float'>
>>> class Bag:
    def __init__(self):
        self.data = []
    def add(self, x):
        self.data.append(x)
    def addtwice(self, x):
        self.add(x)
        self.add(x)
>>> Bag.__class__
<class 'type'>

继承

Python中派生类的定义语法如下:

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>
aseClassName是基类的名称,也支持在基类的名称前加模块前缀,例如:

class DerivedClassName(modname.BaseClassName):
派生类定义的执行处理和基类是一样的,当类对象被构建,基类被保存。当类中请求的属性没有找到时,则在基类中搜索,一直向上,直到到达最上层的基类。
派生类的实例化也和基类一样:DerivedClassName()创建一个类的新的实例。方法引用采用下面的原则:先搜索类属性,在搜索基类,直到最终的基类。
派生类可以重载基类的方法,一个基类的方法如果被重载,如果基类中的另一个方法调用了该方法,则实际上是调用了子类中重载过的该方法。
如果在派生类中想要调用基类的方法,有一个直接的方法:调用BaseClassName.methodname(self, arguments)。
Python中有两个方法可以用于调查继承关系:
    1)isinstance()
    检查一个实例的类型:如果isinstance(obj, int)返回true,则obj.__class__必定为int或者int的子类。
    2)issubclass()
    用于检查类的继承关系:issubclass(bool, int)将返回True,由于bool是int的子类。然而,issubclass(float, int)返回False,由于float不是int的子类。
另外,类还有一个属性__bases__可以查看类的所有基类,大家可能注意到,这里bases是一个复数,也就是它暗示基类可能会有多个。

多重继承

Python支持多重继承,一个类可以定义多个基类:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>
简单来讲,在多重继承下,类的属性的搜索采用深度遍历、从左到右的顺序。因此,如果一个属性在DerivedClassName中没有,则会搜索Base1,再搜索Base1的基类,如果没有发现,则搜索Base2,等等。
因此,当一个方法从多个超类继承,则必须要注意一下超类的顺序:先继承的类中的方法会重写后继承的类中的方法。如果超类们共享一个超类,那么在查找给定方法或者特性时访问超类的顺序成为MRO(Method Resolution Order,方法判定顺序)。
介于多重继承的复杂性,建议应该尽量避免使用,因为有些时候会出现不可预见的麻烦。

私有变量

在Python中不存在私有变量,但是,存在一个被大多数Python代码遵守的惯例:以‘_‘为前缀的命名应该被作为API的非公共部分(无论是函数、方法或者数据成员),它应该在不需要通知的情况下就可以被修改。
Python为类元素的私有性提供初步的形式,由双下划线开始的属性(实际上是至少两个下划线开头,之多一个下划线结尾)在运行时被混淆,不能够直接访问,例如:

>>> class Test:
	__a_ = "This is a test."

>>> t = Test()
>>> t.__a_
Traceback (most recent call last):
  File "<pyshell#71>", line 1, in <module>
    t.__a_
AttributeError: 'Test' object has no attribute '__a_'
混淆后的名称前会加上下划线和类名,即_classname__。因此,类Test中的变量__a_被混淆后变为_Test__a_:

>>> t._Test__a_
'This is a test.'
这样做提供了某种层次上的私有化,名字混淆的另一个目的,是为了保护__XXX变量不与父类名字空间相冲突,并且父类的__XXX变量不会被子类覆盖。例如:

>>> class Test:
	def test(self):
		print("this is base.")
	__test = test
	def doTest(self):
		self.__test()

>>> class TestSubclass(Test):
	def test(self):
		print("this is subclass.")

>>> a = Test()
>>> b = TestSubclass()
>>> a.test()
this is base.
>>> a.doTest()
this is base.
>>> b.test()
this is subclass.
>>> b.doTest()
this is base.

python13:类

标签:python

原文地址:http://blog.csdn.net/tomato__/article/details/45500833

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!