对象的相关定义:
对象:对象指的是类的实例。一个对象有自己的状态(属性)、行为(方法)和唯一的标识(内存地址);所有相同类型的对象所具有的结构和行为在它们共同的类中被定义。
状态:包括这个对象已有的属性(通常是类里面已经定义好的),再加上对象具有的当前属性值(往往是动态的)。
行为:指一个对象如何影响外界和被外界影响,表现为对象自身状态的改变和信息的传递。
标识:值一个对象所具有的区别于所有其他对象的属性(本质上指内存中所创建的对象的地址)。
简言之,对象应具有属性(即状态)、方法(即行为)和标识。标识是在内存中自动完成的,不用去管它,平时主要用到属性和方法。
类:类描述了所创建的对象共同的属性和方法(数据属性和方法属性)。
实例:类是对象的定义,实例才是真实的物件,是具体的个体。实例只有数据属性,没有方法属性。实例调用的方法是类中的方法,由作用域决定。
属性:
类属性:在类中定义的变量,即类的属性。
>>> 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类的实例,即self和stu引用了同一个实例对象。当创建实例的时候,实例变量作为第一个参数,被python解释器传给了self,所以初始化函数中的self.name就是初始化实例的属性。self和实例可以理解为一个主内、一个主外,实例对象的引用会被传给self,self就是实例在类的内部的形式。
这样,在类的内部,通过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()我执行啦!