Python的对象和类(6)

53 篇文章 1 订阅

什么是对象

对象既包含数据(变量,更习惯称之为特性attribute),也包含代码(函数,也成为方法)。

使用class定义类

之前,我们把对象比作塑料盒子。类(class)则像是制作盒子用的模具。例如,Python的内置类String可以创建像’cat’和’duck’这样的字符串对象。

# 创建Person类
class Person():
    # __init__是Python中一个特殊的函数名,用于根据类的定义创建实例对象
    # self参数指向了这个正在被创建的对象本身
    def __init__(self, name):
        self.name = name
    def print_name(self):
        print(self.name)

# Person('Elmer Fudd')创建了一个Person类的对象,并给它赋值someone这个名字
someone = Person('Elmer Fudd')
someone.print_name()

someone = Person(‘Elmer Fudd’)

上面这短短一行代码实际做了以下工作:
1. 查看Person类的定义
2. 在内存中实例化(创建一个新的对象)
3. 调用对象的_init_方法,将这个新创建的对象作为self传入,并将另一个参数(‘Elmer Fudd’)作为name传入
4. 将name的值存入对象
5. 返回这个新的对象
6. 将名字hunter与这个对象关联

someone.print_name()

self代表类的实例,而非类。在Python的解释器内部,当我们调用someone.print_name()时,实际上Python解释成Person.print_name(self)

self

  • self在定义时需要定义,但是在调用时会自动传入。
  • self的名字并不是规定死的,但是最好还是按照约定是用self
  • self总是指调用时的类的实例。

函数方法间的依赖关系

这里写图片描述

Python的函数方法并不想C++那样,能够重载

后面的haha函数的定义把前面haha函数的定义覆盖了(对象的名字才是唯一的标识,跟参数无关),就解释了下面的结果。
这里写图片描述

正确做法:
这里写图片描述

其他

class A:
    # 此处logo是类A的属性
    logo = 'Logo A'
class B:
    def __init__(self):
        # 此处logo是类A生成的对象的属性
        self.logo = 'Logo B'

a = A()
b = B()
print (a.logo)
print (b.logo)

print(A.__dict__)
print(B.__dict__)
print(a.__dict__)
print(b.__dict__)

结果:

Logo A
Logo B
{'__doc__': None, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__module__': '__main__', 'logo': 'Logo A', '__dict__': <attribute '__dict__' of 'A' objects>}
{'__doc__': None, '__init__': <function B.__init__ at 0x0037D348>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'B' objects>, '__dict__': <attribute '__dict__' of 'B' objects>}
{}
{'logo': 'Logo B'}

其中对比A._dict_、B._dict_、a._dict_和b._dict_、A._dict_中有个logo 属性,B.dict_中有个_init_属性,A没有_init_方法,故a没有任何属性,B有_init_方法,执行该_init_方法使b中有了一个logo属性

继承

从已有的类中衍生出新的类,添加或修改部分功能。这是代码复用的一个绝佳的例子。使用继承得到的新类会自动获得旧类中的所有方法,而不需要进行任何复制。
这里写图片描述

构造函数

python里一个class可以定义多个构造函数吗?

不行,一个class只能有一个用于构造对象的_init_函数
但python中的变量是无类型的,因此传给init的参数可以是任何类型

python中的函数参数在定义时可以有默认值,可以让init函数接受多个参数,在后面的一些参数给出默认值的方法让init接受不同个数的参数,并且执行类型检查执行不同的代码,用上述方法实现类的构造函数的多态性

这里写图片描述
这里写图片描述

多类继承中子类默认继承哪个父类的构造函数_init_

  1. Python中如果子类有自己的构造函数,会把父类的构造函数覆盖(与参数个数等无关),不会自动调用父类的构造函数,如果需要用到父类的构造函数,则需要在子类的构造函数中显式的调用
  2. 如果子类没有自己的构造函数,则会直接从父类继承构造函数,这在单继承(一个子类只从一个父类派生)中没有任何理解上的问题
  3. 子类从多个父类派生,而子类又没有自己的构造函数时,就按顺序继承,哪个父类在最前面且它又有自己的构造函数,就继承它的构造函数。即如果最前面第一个父类没有构造函数,则继承第2个的构造函数,第2个没有的话,再往后找,以此类推。若仍然没有就找超父类,直到起源。
class A:
    def __init__(self):
        print 'a'
class B:
    def __init__(self):
        print 'b'
class C1(B,A):
    pass
class C2(A,B):
    pass
class D1(C1):
    def __init__(self):
        C1.__init__(self)
class D2(C2):
    def __init__(self):
        C2.__init__(self)

if(__name__=='__main__'):
    print 'd1------->:'
    d1=D1()
    print 'd2------->:'
    d2=D2()

>>> 
d1------->:
b
d2------->:
a

这里写图片描述

覆盖方法

在Python中,子类的同名函数会把父类的同名函数完全覆盖掉。所以下面的例子中,doctor = MDPerson()会出错。如果想调用被覆盖的父类函数,需要super的帮忙。
这里写图片描述

使用super从父类得到帮助

Python中类的初始化方法是_init_(),因此父类、子类的初始化方法都是这个,如果子类不实现_init_()这个函数,初始化时调用父类的初始化函数.

如果子类实现了这个函数,则要在这个函数里显式调用一下父类的_init_(),这跟C++,Java不一样,他们是自动调用父类构造函数的

#初始化中调用父类初始化方法示例  
#B是A的子类  
class B(A):    
    def __init__(self):    
        super().__init__()  

调用父类其他成员函数的三种方法:
1. 直接写类名调用 (A._init_(self));
2. 用 super(type, obj).method(arg)方法调用 (super(B, obj)._init_());
3. 在子类的定义内,如果调用父类的成员,可以直接用 super().method(arg) (super()._init_())。

class A:    
    def method(self, arg):      
         return  

class B(A):    
    def method(self, arg):    
#        A.method(self,arg)                #1     
#        super(B, self).method(arg)        #2     
#        super().method(arg)               #3   

[注意] 如果在子类定义外(即在其他函数逻辑内,子类对象去调用父类成员时),则按照:

ob = B()    
super(B,ob).method(arg)    #调用class B的父类class A的method。   

理解python中super

在python中引入super()的目的是保证相同的基类只初始化一次

  1. super()机制是用来解决多重继承的,对于直接调用父类名是没有问题的,但在之后根据前人的经验就是:要么都用类名调用,要么就全部用super(),不要混合的用
  2. super()继承只能用于新式类,用于经典类时就会报错。
    • 新式类:必须有继承的类,如果无继承的,则继承object(即起源类仍要继承object对象 class A(object):)
    • 经典类:没有父类,如果此时调用super就会出现错误:(super() argument 1 must be type, not classobj)
利用类名进行调用
# parent1与parent2继承object
class parent1(object):  
    def __init__(self):  
        print 'is parent1'  
        print 'goes parent1'  

class parent2(object):  
    def __init__(self):  
        print 'is parent2'  
        print 'goes parent2'  

class child1(parent1):  
    def __init__(self):  
        print'is child1'  
        parent.__init__(self)  
        print 'goes child1'  

class child2 (parent1) :  
    def __init__(self):  
        print 'is child2'  
        parent.__init__(self)  
        print 'goes child2'  

class child3(parent2):  
    def __init__(self):  
        print 'is child3'  
        parent2.__init__(self)  
        print 'goes child3'  

class grandson(child3,child2,child1):  
    def __init__(self):  
        print 'is grandson'  
        child1.__init__(self)  
        child2.__init__(self)  
        child3.__init__(self)  
        print'goes grandson'  


if __name__=='__main__':  
    grandson()  

结果:

# 基类parent1被多次执行
is grandson  
is child1  
is parent1  
goes parent1  
goes child1  
is child2  
is parent1  
goes parent1  
goes child2  
is child3  
is parent2  
goes parent2  
goes child3  
goes grandson  
利用super()进行调用(更多)

有时我们只希望公共的类只被执行一次,那么此时我们引入super()机制查看效果:

class parent1(object):
    def __init__(self):  
        super(parent1, self).__init__()  
        print 'is parent1'  
        print 'goes parent1'  

class parent2(object):  
    def __init__(self):  
        super(parent2, self).__init__()  
        print 'is parent2'  
        print 'goes parent2'  

class child1(parent1):  
    def __init__(self):  
        print'is child1'  
        #parent1.__init__(self)  
        super(child1, self).__init__()  
        print 'goes child1'  

class child2 (parent1) :  
    def __init__(self):  
        print 'is child2'  
        #parent1.__init__(self)  
        super(child2, self).__init__()  
        print 'goes child2'  

class child3(parent2):  
    def __init__(self):  
        print 'is child3'  
        #parent2.__init__(self)  
        super(child3, self).__init__()  
        print 'goes child3'  

class grandson(child3,child2,child1):  
    def __init__(self):  
        print 'is grandson'  
        #child1.__init__(self)  
        #child2.__init__(self)  
        #child3.__init__(self)  
        super(grandson, self).__init__()  

        print'goes grandson'  


if __name__=='__main__':  
    grandson()  

结果:

is grandson  
is child3  
is child2  
is child1  
is parent1  
goes parent1  
goes child1  
goes child2  
is parent2  
goes parent2  
goes child3  
goes grandson  

分析:
1. grandson
2. grandson [child3]
3. grandson [child3, child2]
4. grandson [child3, child2, child1]
5. grandson [child3, child2] || child1[parent1]
6. grandson [child3, child2] || child1
7. grandson [child3] || child2[parent1] <==> child2
8. grandson || child3[parent2]
9. grandson || child3
10. grandson
这里写图片描述

使用属性对特性进行访问和设置

有一些面向对象的语言支持私有特性(如C++)。这些特性无法从对象外部直接访问,我们需要编写getter和setter方法对这些私有特性进行读写操作。

Python不需要getter和setter方法,因为Python里所有特性都是公开的,使用时全凭自觉。

如果你不放心直接访问对象的特性,可以为对象编写setter和getter方法。但更具Python风格的解决方案是使用属性(property)

定义属性方法1:通过property方法把getter方法和setter方法设置为name属性:

这里写图片描述

定义属性方法2:使用装饰器

  • @property,用于指示getter方法
  • @name.setter,用于指示setter方法

这里写图片描述

属性还可以指向一个计算结果值

class Circle():
    def __init__(self, radius):
        self.radius = radius
    @property
    def diameter(self):
        return 2 * self.radius
>>> c = Circle(5)
>>> c.radius
5
>>> c.diameter
10
>>> # 我们可以随时改变radius特性的值,计算属性diameter会自动根据新的值更新自己,因为本质是对getter方法的调用
>>> c.radius = 7
>>> c.diameter
>>> 14

因此,与直接访问特性相比(如令diameter为特性),使用property还有一个巨大的优势:如果你改变了某个特性的定义,只需要在类的定义里修改相关代码即可,不需要再每一处修改。例如,修改特性radius,属性diameter不需要修改,特性diameter则需要手动修改。

使用名称重整保护私有特性

前面的Duck例子中,为了隐藏内部特性,我们曾将其命名为hidden_name,但hidden_name特性仍然能被访问到。其实Python对那些需要可以隐藏在类内部的特性有自己的命名规范:由连续的两个下划线开头(__)

我们来把hidden_name改名为__name,如下所示:
这里写图片描述
这种命名规范本质上并没有把特性变成私有,但Python确实将它的名字重整了,让外部的代码无法使用。

>>> #其实__name重整为_Duck__name
>>> fowl._Duck__name
'Donald'

尽管如我们所见,这种保护特性的方式并不完美,但它确实能在一定程度上避免我们有意无意地对特性进行直接访问。

方法的类型

  • 有些数据(特性)和函数(方法)是类本身的一部分(如 a = A() A.method()中的method)
    • 类方法(class method)会作用于整个类,对类作出的任何改变会对它的所有实例对象产生影响。在类定义内部,用前缀装饰器@classmethod指定的方法都是类方法。
    • 与实例方法类似,类方法的第一个参数是类本身。在Python中,这个参数常被写作cls,因为全称class是保留字,在这里我们无法使用。
  • 有一些数据(特性)和函数(方法)是由类创建的实例的一部分(如 a = A() a.method()中的method)
    • 在类的定义中,以self作为第一个参数的方法都是实例方法(instance method)。它们在创建自定义类时最常用。实例方法的首个参数是self,当它被调用时,Python会把调用该方法的对象作为self参数传入。

这里写图片描述

注意,上面的代码中,我们使用的是A.count(类特性),而不是self.count(对象的特性)。在kids()方法中,我们使用的是cls.count,它与A.count的作用一样。

类特性与对象特性耦合

这里写图片描述

静态方法(static method)

类的定义中的方法还存在着第三种类型,它既不会影响类也不会影响类的方法。它们出现在类的定义中仅仅是为了方便,否则它们智能孤零零地出现在代码的其他地方,这会影响代码的逻辑性。这种类型的方法被称作静态方法(static method),用@staticmethod修饰,它既不需要self参数也不需要class参数

这里写图片描述

注意:你可以发现Python是一种太灵活的语言,所以很容易出问题

Python的特殊方法(special method),有时也被称作魔术方法(magic method)(更多

  • 和比较相关的魔术方法
方法名使用
__eq__(self, other)self == other
__ne__(self, other)self != other
__lt__(self, other)self < other
__gt__(self, other)self > other
__le__(self, other)self <= other
__ge__(self, other)self >= other

这里写图片描述

  • 和数学相关的魔术方法
方法名使用
__add__(self, other)self + other
__sub__(self, other)self - other
__mul__(self, other)self * other
__floordiv__(self, other)self // other
__truediv__(self, other)self / other
__mod__(self, other)self % other
__pow__(self, other)self ** other

这里写图片描述

  • 其它种类的魔术方法
方法名使用
__str__(self)str(self)
__repr__(self)repr(self)
__len__(self)len(self)

这里写图片描述

关于__str__与__repr__

__str__用于定义如何打印对象信息。print()方法,str()方法以及你将在第7章读到关于字符串格式化的相关方法都会用到__str__()

__repr__用于交互式解释器输出变量

例子:
如果在你的类既没有定义__str()也没有定义__repr(),Python会输出类似下面这样的默认字符串:
这里写图片描述

我们将__str__()和__repr__方法都添加到Word类里,让输出的对象信息变得更好看些
这里写图片描述

组合

继承: is-a 关系
组合: has-a关系
这里写图片描述

何时使用类和对象而不是模块

有一些方法可以帮助你决定是把你的代码封装到类里还是模块里。

# 有空补个更好的例子
#singleton.py
count = 0

def add():
    global count
    count += 1

def getCount():
    global count
    return count

使用:

#test.py
import singleton
a = singleton
a.add()
b = singleton
print a
print b
print a.getCount()
print b.getCount()

结果:

<module 'singleton' from '/Users/bin/Desktop/singleton.pyc'>
<module 'singleton' from '/Users/bin/Desktop/singleton.pyc'>
1
1
  • 如果你有一系列包含多个值的变量,并且它们能作为参数传入不同的函数,那么最好将它们封装到类里面。
  • 用最简单的方式解决问题。使用字典、列表和元组往往要比使用模块更加简单、简洁而快速。而使用类则更为复杂。

命名元组

命名元组是元组的子类,你既可以通过名称(使用.name)来访问其中的值,也可以通过位置进行访问(使用[offset]
这里写图片描述
也可以用字典来构造一个命名元组:

>>> parts = {'bill': 'wide orange', 'tail': 'long'}
>>> # 等价于 duck2 = Duck(bill = 'wide orange', tail = 'long')
>>> duck2 = Duck(**parts)
>>> duck2
Duck(bill = 'wide orange', tail = 'long')

对比命名元组和字典:
这里写图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值