博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
面向对象
阅读量:6319 次
发布时间:2019-06-22

本文共 13589 字,大约阅读时间需要 45 分钟。

对象的相关定义:

  对象:对象指的是类的实例。一个对象有自己的状态(属性)行为(方法)和唯一的标识(内存地址);所有相同类型的对象所具有的结构和行为在它们共同的类中被定义。

  状态:包括这个对象已有的属性(通常是类里面已经定义好的),再加上对象具有的当前属性值(往往是动态的)

  行为:指一个对象如何影响外界和被外界影响,表现为对象自身状态的改变和信息的传递。

  标识:值一个对象所具有的区别于所有其他对象的属性(本质上指内存中所创建的对象的地址)

简言之,对象应具有属性(即状态)、方法(即行为)和标识。标识是在内存中自动完成的,不用去管它,平时主要用到属性和方法。

  类:类描述了所创建的对象共同的属性和方法(数据属性和方法属性)

  实例:类是对象的定义,实例才是真实的物件,是具体的个体。实例只有数据属性,没有方法属性。实例调用的方法是类中的方法,由作用域决定。

 

属性:

  类属性:在类中定义的变量,即类的属性。

>>> class A:...     x = 7    # 类中定义变量x...>>> A.x    # 通过类名调用类中变量x7

  类的属性本质上就是类中的变量,它的值不依赖于任何实例,只是由类中所写的变量赋值语句确定。所以这个类属性又叫静态变量或静态数据。类属性只会在创建类的时候定义一次,不会因为对象的创建而重新创建。

  类属性可以增加、删除或者修改。

>>> A.y = 9    # 直接向类A中增加新的属性>>> A.y9>>> del A.x    # 删除类中的已有属性>>> A.xTraceback (most recent call last):  File "
", line 1, in
AttributeError: type object 'A' has no attribute 'x'>>> A.y = 99 # 修改类中的属性值>>> A.y99

  查看类中的内容:

>>> dir(A)['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'y']>>> A.__doc__    # 显示类的文档>>> A.__name__    # 以字符串的形式返回类的名字'A'>>> A.__base__    # 类的父类
>>> A.__dict__ # 以字典的形式显示类的所有属性mappingproxy({
'__doc__': None, '__module__': '__main__', '__dict__':
, '__weakref__':
, 'y': 99})>>> A.__module__ # 类所在的模块'__main__'

  类属性的总结:

    类属性跟类绑定,可以自定义、删除、修改值,也可以随时增加类属性;

        每个类都有一些特殊属性。

  创建实例:创建实例,就是调用类。

    当类被调用之后:

    创建实例对象;

    检查是否实现__init__()方法。如果没有实现,返回实例对象;如果实现了__init__(),则调用该方法,并且将实例对象作为第一个参数self传递进去。

  __init__()是一个特殊方法,它一般规定一些属性或者初始化,让类具有一些基本的特征。__init__()方法没有return语句。

  实例属性:与类属性类似,实例所具有的属性叫做实例属性。每个实例的属性定义在该实例的命名空间(内存地址)中,通过实例可以在不同的方法里直接共享实例属性。不同于函数中的局部变量,只能在函数自己内部使用,不能在其他函数中直接调用。

 

>>> class A:...     x = 7...>>> a = A()>>> A.x    # 类属性7>>> a.x    # 实例属性7>>> a.x += 1    # 修改实例属性>>> a.x8>>> A.x    # 类属性不变7

  类属性和实例属性的区别在于,类属性是直接定义在类中的属性,而实例属性是通过实例化的对象去操作与类中同名的引用。相当于实例新建了一个新的属性,这个属性与类中的属性同名。所以修改实例属性不会影响类属性。

  类属性能够影响实例属性,这是因为实例就是通过调用类来建立的。即实例属性跟着类属性的改变而改变。以上都是类中变量引用不可变对象的结果,比如字符串和数。

  类中变量引用可变数据时,则情形会不同,因为可变数据能够进行原地修改。

>>> class B:...     lst = [1, 2, 3]    # 类中属性是可变类型...>>> b = B()    # 类的实例>>> B.lst[1, 2, 3]>>> b.lst[1, 2, 3]>>> b.lst.append(9)    # 修改实例的属性>>> b.lst[1, 2, 3, 9]>>> B.lst    # 类属性也变化了[1, 2, 3, 9]>>> B.lst.append('python')    # 修改类属性>>> B.lst[1, 2, 3, 9, 'python']>>> b.lst    # 实例属性也变化了[1, 2, 3, 9, 'python']

  当类中变量引用的是可变对象时,类属性和实例属性都能直接修改这个对象,从而影响另一方的值。

  如果增加一个类属性,相应的也增加了一个实例属性。增加实例属性,类属性不会跟着增加。

>>> B.y = 'java'    # 直接增加了一个类属性>>> b.y    # 实例属性相应的增加'java'>>> b.z = 99    # 增加一个实例属性>>> b.z99>>> B.z    # 类中没有此属性Traceback (most recent call last):  File "
", line 1, in
AttributeError: type object 'B' has no attribute 'z'

  以上的实例属性或类属性,都是源自类中的变量所引用的值,是静态数据。还有一种实例属性的生存方法,是在实例创建的时候,通过__init__()初始化方法建立,是动态数据。

  self的作用:类中的任何方法,第一个参数都是self

  看下面的例子:

>>> class Stu:...     def __init__(self):...         self.name = 'Jack'...         self.age = 18...         print(self)    # 打印self的内存地址...         print(type(self))    # 打印self的类型...>>> stu = Stu()    # 实例化时自动执行类中__init__()方法<__main__.Stu object at 0x7fc4dd792780>    # self的地址
# self的类型>>> print(stu) # 与self对应的实例的地址<__main__.Stu object at 0x7fc4dd792780>>>> type(stu)  # 实例的类型

  上面可以看到,self的内存地址和类型与实例stu完全一致。这说明self就是Stu类的实例,即selfstu引用了同一个实例对象。当创建实例的时候,实例变量作为第一个参数,被python解释器传给了self,所以初始化函数中的self.name就是初始化实例的属性。self和实例可以理解为一个主内、一个主外,实例对象的引用会被传给selfself就是实例在类的内部的形式。

  这样,在类的内部,通过self实例,类中所有方法都能承接self实例对象,它的属性也被带到类中的每个方法之中,实现了数据在类内部的流转。

 

方法:

 

  绑定方法和非绑定方法:

>>> class Foo:...     def bar(self):...         print('This is a normal method of class')...>>> foo = Foo()    # 实例化类对象>>> foo.bar()    # 实例调用类中方法This is a normal method of class>>> Foo.bar(foo)    # 类名调用类中方法,必须传入实例对象This is a normal method of class>>> Foo.bar()    # 类名调用类中方法,不传入实例对象时,报错Traceback (most recent call last):  File "
", line 1, in
TypeError: bar() missing 1 required positional argument: 'self'

  报错信息告诉我们,bar()方法缺少一个参数self,它是一个实例,所以要传入一个实例。

 

>>> foo.bar    # 实例调用方法对象
>>>> Foo.bar # 类名调用方法对象

  通过类来获取方法的时候,得到的是非绑定方法对象;

  过实例来获取方法的时候,得到的是绑定方法对象。

  类方法:

    看一个例子:

>>> class Foo:...     lang = 'Java'    # 类属性...     def __init__(self):...         self.lang = 'Python'    # 实例属性...>>> def get_class_attr(cls):...     return cls.lang...>>> if __name__ == '__main__':...     print('Foo.lang:', Foo.lang)    # 打印类属性...     r = get_class_attr(Foo)...     print('get class attribute:', r)    # 打印类属性...     f = Foo()...     print('instance attribute:', f.lang)    # 打印实例属性...Foo.lang: Javaget class attribute: Javainstance attribute: Python

  上面的例子中,在类Foo中,定义了一个属性lang = 'Java',这是类属性;在初始化方法中,又定义了self.lang = 'Python',这是实例属性。

  在这个程序中,较特殊的函数是get_class_attr(cls),它写在类的外面,而这个函数又只能调用前面的那个类对象,这使得类和函数的耦合性太强,应将类和函数融为一体,写成类方法。

 

 

 

>>> class Foo:...     lang = 'Java'...     def __init__(self):...         self.lang = 'Python'...     @classmethod    # 定义类方法...     def get_class_attr(cls):...         return cls.lang...>>> if __name__ == '__main__':...     print('Foo.lang:', Foo.lang)...     r = Foo.get_class_attr()    # 直接使用类名调用类方法...     print('get class attribute:', r)...     f = Foo()...     print('instance attribute:', f.lang)...     print('instance get_class_attr:', f.get_class_attr())   # 也可使用实例调用类方法...Foo.lang: Javaget class attribute: Javainstance attribute: Pythoninstance get_class_attr: Java

  在上面的程序中,装饰器@classmethod所装饰的方法get_class_attr(cls)和类中的其他方法不同,它的第一个参数的cls(习惯用这个)表示类对象,即参数cls引用的对象类对象Foo

  所谓类方法,就是在类里面定义的方法。该方法由装饰器@classmethod装饰,其第一个参数cls引用这个类对象,即将类本身作为对象传入到此方法中。类方法可以直接用类名调用,也可用实例进行调用。

  静态方法:

>>> import random>>> class Foo:...     def __init__(self):...         self.name = 'Jack'...     def get_name(self, age):...         if self.select(age):    # 通过实例self调用静态方法...             return self.name...         else:...             return 'the name is secret.'...     @staticmethod    # 定义静态方法...     def select(n):...         a = random.randint(1, 100)...         return a - n > 0...>>> if __name__ == '__main__':...     f = Foo()...     name = f.get_name(18)...     print(name)...the name is secret.

  上面程序中有一个函数select(n),这个函数虽然定义在类中,但它却是一个独立的方法,跟类没有关系。这个方法前要加装饰器@staticmethod,我们称之为静态方法。静态方法不以self作为第一个参数,当调用它的时候,可以同过实例调用,也可以通过类名调用。它存在的意义在于将要在类中调用的函数定于在类的命名空间中,便于维护。

  当类中要定义的某个方法和实例有关系,则定义为实例方法;当类中要定义的某个方法和类属性有关系,则定义为类方法;当类中要定义的某个既和类有关系又和实例有关系,则定义为静态方法。

 

继承:

  继承可以使得子类别获得父类别的所有属性和方法,而不需要再次编写相同的代码。子类别可以重新定义新的属性,重写父类中的方法,使得子类别获得与父类别不同的功能。另外,也可为子类别追加新的属性和方法。

>>> class Person:...     def __init__(self, name):...         self.name = name...     def height(self, m):...         h = dict((['height', m],))...         return h...>>> class Girl(Person):...     def get_name(self):...         return self.name...>>> if __name__ == '__main__':...     p = Girl('yang')...     print(p.get_name())...     print(p.height(166))...yang{
'height': 166}

  上面的Girl类继承了类Person,那么Girl类就拥有了Person类中的所有方法和属性。当子类创建实例或调用父类中的方法时,也必须按照父类中方法的要求传入参数。

  子类中重写了父类中的方法或属性时,父类中的相应方法或属性将不再被继承到父类中,如果要在子类中继承父类中被重写的方法或属性,怎么办?

>>> class Girl(Person):...     def __init__(self, name):...         Person.__init__(self, name)    # 子类继承父类中的同名方法...            # super(Girl, self).__init__(name)    # 使用super继承父类中的同名方法...         self.real_name = 'zhangsan'...     def get_name(self):...         return self.name...>>> if __name__ == '__main__':...     g = Girl('yang')...     print(g.real_name)...     print(g.get_name())...     print(g.height(166))...zhangsanyang{
'height': 166}

  要继承父类中的同名方法或属性时,可以以类方法的方式调用Person.__init__(self, name),也可以以super的方式调用super(Girl, self).__init__(name)。

  多继承父类调用顺序:使用类名. __mro__查看父类的调用顺序。新式类采用广度优先的顺序;旧式类采用深度优先的顺序。

 

>>> class Person:...     pass...>>> class Stu(Person):...     pass...>>> print(Stu.__mro__)    (
,
,
)

  接口的作用是定义子类必须实现的方法,python没有直接实现接口,需要依靠abc模块来实现接口的定义与继承。

 

多态和封装:

 

  Python不检查传入对象的类型,这种方式被称为“隐式类型”或“结构式类型”,也被称为“鸭子类型”。“鸭子类型”意味着可以向任何对象发送任何消息,语言只关心该对象能否接收该消息,不强求该对象是否为某一种特定的类型。

 

  Python中的封装私有化是将准备私有化的属性(方法)的名字前加双下划线。python中的私有化本质上是将以__开头的私有属性重命名为_类名__属性名。也可以以_开头定义私有属性和方法,以这种方式定义,只是一种规范的约定。

 

>>> class PrivateMe:...     def __init__(self):...         self.__name = 'Hello'    # 私有化属性...     def get_name(self):    # 类中显式方法...         return self.__name...     @property    # 装饰器property...     def name(self):...         return self.__name...>>> if __name__ == '__main__':...     p = PrivateMe()...     print(p.get_name())    # 得到类中私有属性...     print(p.name)...HelloHello

  私有化的方法或属性,按约定在类的外面无法调用。如果要调用类中私有属性,可以用property装饰器。用了@property之后,在调用那个方法的时候,用的是p.name的形式,就好像在调用一个属性一样。除此之外,也可在类中定义显式方法,在显式方法中返回或者输出私有属性,再通过调用类中的显式方法得到私有属性。

  私有方法和属性不能被继承:

>>> class Person:...     def __init__(self):...         self.name = 'zhangsan'...         self.__age = 22    # 私有属性...     def eat(self):...         print('eating...')...     def __sleep(self):    # 私有方法...         print('sleeping...')...>>> class Stu(Person):...     pass...>>> stu = Stu()>>> stu.name'zhangsan'>>> stu.__age    # 私有属性不能被继承在类中,私有属性被重命名为_Person__ageTraceback (most recent call last):  File "
", line 1, in
AttributeError: 'Stu' object has no attribute '__age'>>> stu.eat()eating...>>> stu.__sleep() # 私有方法不能被继承在类中,私有方法被重命名为_Person__sleepTraceback (most recent call last): File "
", line 1, in
AttributeError: 'Stu' object has no attribute '__sleep'

 

类的魔法方法:

  __init__()方法:初始化对象的属性。

 

 

  __new__()方法:创建对象,只用于创建对象。

  对象在实例化时,分三步完成:
    1.先调用__new__方法创建对象;
    2.创建对象后,对象引用空间存在,此时会在对象引用空间内再调用__init__方法初始化对象,也就是说,只要对象引用空间存在了,有另一个变量也指向此对象空间时,__init__方法也会调用。__init__方法的调用不是通过__new__方法来实现的,而是只要有了对象空间,__init__方法便会调用;
    3.最后返回对象。
  只不过对于__new__方法,它只是用来创建对象,只有python内部才能够创建对象,我们几乎不会去重写它。

 

 

>>> class A:...     def __init__(self):...         print('执行初始化')...     def __new__(cls):    # 重写了父类的__new__方法,没有创建对象的操作,对象不会被创建...         print('执行创建对象')... >>> a = A()    # 先调用重写的__new__方法,方法中没有调用__init__方法,对象也不会初始化执行创建对象改进代码:>>> class A:...     def __init__(self):...         print('执行初始化')...     def __new__(cls):    # __new__方法通过类创建对象,所以参数传入cls...         print('执行创建对象')...         return super().__new__(cls)    # 调用父类__new__方法完成对象的创建... >>> a = A()执行创建对象执行初始化

 

  __new__方法通常用来创建单例对象:

    我们知道,每实例化一个对象时,都会创建其自己的引用空间。现在我们要求创建单例对象,即不论实例化多少个对象,它们的引用空间都是同一个,也即所有的对象名都指向同一个对象空间的引用。

>>> class A:...     __instance = None    # 定义一个类属性,用于存放实例化的对象...     def __new__(cls):...         if cls.__instance == None:    # 如果类属性为空,则执行第一次创建...             cls.__instance = super().__new__(cls)...             return cls.__instance    # 返回创建的对象...         else:    # 如果类属性不为空,则不再执行对象创建,直接返回第一次创建的对象...             return cls.__instance... >>> a = A()>>> print(id(a))    # 两个对象名指向了同一个对象的引用空间4341814776>>> b = A()>>> print(id(b))4341814776

  只初始化一次对象:

    当创建了单例对象后,每个实例化的对象名都指向了同一个对象空间,但是当在实例化对象并初始化对象时,初始化方法__init__都会被调用,从而执行多次初始化。

>>> class A:...     __instance = None...     def __new__(cls, name):...         if cls.__instance == None:...             cls.__instance = super().__new__(cls)...             return cls.__instance...         else:...             return cls.__instance...     def __init__(self, name):...         self.name = name... >>> a = A('zhangsan')    # 执行初始化方法__init__>>> print(a.name)zhangsan>>> b = A('lisi')    # 再次执行初始化方法__init__>>> print(b.name)lisi

  由上面代码可以看到,在实例化对象时,虽然__new__方法只被执行了一次,只创建了一个对象空间,但是由于对象空间已经创建,在实例化不同对象时,都会调用初始化方法,即初始化方法__init__被执行了两次。

  修改代码,使单例对象只初始化一次:

>>> class A:    ...      __instance = None...      __flag = False    # 定义类属性作为判断的条件...      def __new__(cls, name):...          if cls.__instance == None:...              cls.__instance = super().__new__(cls)...              return cls.__instance...          else:...              return cls.__instance...      def __init__(self, name):...          if A.__flag == False:    # 满足条件则执行赋值,不满足就不执行...              self.name = name...              A.__flag = True... >>> a = A('zhangsan')>>> print(a.name)zhangsan>>> b = A('lisi')    # 依然会执行初始化方法__init__,但不执行赋值操作>>> print(b.name)zhangsan    # 对象的name属性没有被修改

 

 

  __str__()方法:打印对象名时自动调用此方法。

    没有定义__str__()方法:

>>> class Person:...     def __init__(self, name, age):...         self.name = name...         self.age = age...>>> p = Person('zhangsan', 22)>>> print(p)    # 类中没有定义__str__()方法,打印对象的地址<__main__.Person object at 0x7f94513b26d8>

    定义了__str__()方法:

>>> class Person:...     def __init__(self, name, age):...         self.name = name...         self.age = age...     def __str__(self):...         return '%s的年龄是%d岁。'%(self.name, self.age)...>>> p = Person('zhangsan', 22)>>> print(p)    # 定义了__str__()方法,打印对象名时,自动输出__str__()方法的返回值zhangsan的年龄是22岁。

 

  __repr__()方法:在解析器中应用。

>>> class A:...     def __repr__(self):...         return '在解析器中应用'... >>> a = A()>>> a在解析器中应用

  当类中同时定义了__str__()和__repr__(),打印对象名时,会调用__str__()方法,而当类中只定义了__repr__()方法时,打印对象名时,会调用__repr__()作为替代。

>>> class A:...     def __str__(self):...         return '打印对象名时调用__str__'...     def __repr__(self):...         return '调用__repr__'... >>> a = A()>>> print(a)打印对象名时调用__str__>>> class B:...     def __repr__(self):...         return '调用__repr__作为替代'... >>> b = B()>>> print(b)调用__repr__作为替代

 

  __del__()方法:析构方法,在对象结束前会被自动调用,可用来作对象结束前的善后工作。一般很少使用,因为我们很难明确对象什么时候结束。

   

  __call__()方法:在类中定义了此方法,则类的实例就可调用。函数和方法中都会有此方法,表示函数和方法都是可调用的。

>>> class A:...     def __call__(self):...         print('我执行啦!')... >>> a = A()>>> a()我执行啦!

 

转载于:https://www.cnblogs.com/wgbo/p/9800983.html

你可能感兴趣的文章
PAT 解题报告 1004. Counting Leaves (30)
查看>>
Android开发之蓝牙 --修改本机蓝牙设备的可见性,并扫描周围可用的蓝牙设备
查看>>
[Head First设计模式]生活中学设计模式——外观模式
查看>>
Repository模式中,Update总是失败及其解析
查看>>
.Net 转战 Android 4.4 日常笔记(2)--HelloWorld入门程序
查看>>
[原创]浅谈测试团队转型,思维模式的转变是关键
查看>>
Redis学习-SortedSet
查看>>
android CoordinatorLayout使用
查看>>
机器学习资料大汇总
查看>>
Python selenium 滚动条 详解
查看>>
poj1035Spell checker
查看>>
微信程序开发
查看>>
如何退出minicom【学习笔记】
查看>>
C++内存布局之虚拟继承
查看>>
Sqlserver 数据库基本查询
查看>>
图书馆维护系统总结
查看>>
[hadoop源码阅读][5]-counter的使用和默认counter的含义
查看>>
SAP HUM 如何对一个HU做上架?
查看>>
LINUX系统中动态链接库的创建与使用{补充}
查看>>
三维视觉国际会议首度在中国举办
查看>>