Python-类和对象
类是一个模板,用于定义一组相关的数据和方法。类中可以包含数据成员(如字符串、数字、列表等)和方法成员(如构造函数、析构函数、方法等)。类可以被看作是一种数据结构,用于组织和管理数据。
对象是类的实例,是类的具体表现形式。每个对象都有一个类(称为类对象),并且可以被赋值给变量。对象包含类的数据成员和方法成员,并且可以执行类中定义的方法。
一、对象=属性+方法
Python中一个对象的特征称为“属性”,一个对象的行为称为“方法”。
以下代码定义了对象的特征(属性)和行为(方法),但还不是一个完整的对象,将定义的这些称为类(class)。需要使用类来创建一个真正的对象,这个对象就称为这个类的一个实例(instance),也叫实例对象(instance objects)。
真正的实例对象,需要将类实体化,如下列程序所示
>>> # 这里先运行p11_1.py
>>> tt = Turtle()
>>>
注意:类名后面跟着小括号,这与调用函数是一样的,所以在Python中,类名约定用大写字母开头,函数用小写字母开头,这样更容易区分。另外赋值操作并不是必需的,但如果没有把创建好的实例对象赋值给一个变量,那这个对象就没办法使用,因为没有任何引用指向这个实例,最终会被Python的垃圾收集机制自动回收。
如果要调用对象里的方法,使用点操作符(.)即可:
>>> tt.climb()
我正在很努力的向前爬...
>>> tt.bite()
咬死你咬死你!!
>>> tt.sleep()
困了,睡了,晚安,Zzzz
二、面向对象编程
- self
对象的方法都会有一个self参数,它相当于C++的this指针,由同一个类可以生成无数对象,当一个对象的方法被调用的时候,对象会将自身的引用作为第一个参数传给该方法,那么Python就知道需要操作哪个对象的方法了。
2.Python魔法方法
Python的对象拥有一些神奇的方法,它们是面向对象的Python的一切,如果对象实现了这些方法中的某一个,那么这个方法就会在特殊的情况下被Python调用,而这一切都是自动发生的,Python的这些具有魔力的方法,总是被左右各两个下画线所包围。
一个最基本的特殊方法:_ _init_ _(),通常把_ _init_ _()方法称为构造方法,_ _init_ _()方法的魔力体现在只要实例化一个对象,这个方法就会在对象被创建时自动调用(在C++里也可以看到类似的东西,叫“构造函数”)。其实,实例化对象时是可以传入参数的,这些参数会自动传入_ _init_ _()方法中,可以通过重写这个方法来自定义对象的初始化操作。
3、公有和私有
Python中默认对象的属性和方法都是公开的,可以直接通过点操作符(.)进行访问:
为了实现类似私有变量的特征,Python内部采用了一种叫Name Mangling(名字改编)的技术,在Python中定义私有变量只需要在变量名或函数名前加上“_ _”两个下画线,那么这个函数或变量就会成为私有的了:
这样在外部将变量名“隐藏”起来了,理论上如果要访问,就需要从内部进行:
但可以在外部使用“_类名_ _变量名”,访问两个下画线开头的私有变量了:
>>> p._Person_ _name
'小甲鱼'
注意:Python目前的私有机制其实是伪私有,Python的类是没有权限控制的,所有变量都是可以被外部调用的。
三、继承
(Python的继承是一种面向对象编程的概念,它允许一个类(称为子类或派生类)继承另一个类(称为基类或父类)的属性和方法。子类可以重写(覆盖)从父类继承的方法,并可以添加新的属性和方法以改进其行为。这种能力使得Python的代码更加模块化和复用,因为可以在不改变原始代码的情况下扩展或修改其行为。
以下是Python继承的一些基本概念:
- 继承:子类(派生类)从父类(基类)继承属性和方法。这种关系称为继承。
- 基类:基类是一个类,它定义了父类(超类)的属性和方法。基类是其他类的父类,它们可以从基类继承属性和方法。
- 子类:子类是基类的一个实例,它具有从基类继承的属性和方法。子类可以覆盖(重写)从基类继承的方法,并可以添加新的属性和方法以改进其行为。
- 访问权限:在Python中,基类的属性和方法默认是公共的,可以从多个派生类中访问。然而,私有属性和方法只能在派生类中访问,不能从基类或其他派生类中访问。
- 多重继承:在某些情况下,一个子类可以继承多个基类。这种情况下,子类将获得所有从基类继承的属性和方法,这被称为多重继承。)
继承的语法:
被继承的类称为基类、父类或超类;继承者称为子类,一个子类可以继承它的父类的任何属性和方法。
需要注意的是,如果子类中定义与父类同名的方法或属性,则会自动覆盖父类对应的方法或属性:
下面写一个对鱼类进行细分的程序,有金鱼(Goldfish)、鲤鱼(Carp)、鲨鱼(Shark),还有好吃的三文鱼(Salmo)。
程序实现如下:
鲨鱼(Shark)一移动就会报错,因为Shark对象没有x属性,在Shark类中,重写了魔法方法_ _init_ _,但新的_ _init_ _方法里边没有初始化鲨鱼的x坐标和y坐标,因此调用move方法就会出错。
解决这个问题的方案:在鲨鱼类中重写_ _init_ _方法的时候先调用基类Fish的_ _init_ _方法。
下面介绍两种可以实现的技术:
• 调用未绑定的父类方法。
• 使用super函数。
1、调用未绑定的父类方法
再运行后发现鲨鱼也可以成功移动了:
>>> # 先运行修改后的p11-2.py
>>> shark = Shark()
>>> shark.move()
我的位置是: 7 9
>>> shark.move()
我的位置是: 6 9
需要注意的是,这个self并不是父类Fish的实例对象,而是子类Shark的实例对象,所以,这里说的未绑定是指并不需要绑定父类的实例对象,使用子类的实例对象代替即可。
2.使用super函数
super函数能够自动找到基类的方法,而且还传入了self参数:
运行后得到同样的结果:
>>> # 先运行修改后的p11-2.py
>>> shark = Shark()
>>> shark.move()
我的位置是: 6 1
>>> shark.move()
我的位置是: 5 1
super函数的“超级”之处在于:不需要明确给出任何基类的名字,它会自动找出所有基类以及对应的方法。由于不用给出基类的名字,这就意味着如果需要改变类继承关系,只要改变class语句里的父类即可,而不必在大量代码中去修改所有被继承的方法。
四、多重继承
Python还支持多继承,就是可以同时继承多个父类的属性和方法。多重继承的语法如下:
举个例子:
但多重继承容易导致代码混乱,所以当不确定是否必须使用多重继承的时候,请尽量避免使用它,因为有些时候会出现不可预见的BUG。
五、组合
把需要的类放进去实例化就可以了,这就叫组合:
程序实现如下:
>>> # 先运行p11_3.py
>>> pool = Pool(1, 10)
>>> pool.print_num()
水池里总共有乌龟 1 只,小鱼 10 条!
六、类、类对象和实例对象
对实例对象c的count属性进行赋值后,就相当于覆盖了类对象C的count属性,如果没有赋值覆盖,那么引用的是类对象的count属性。
需要注意的是,类中定义的属性是静态变量,也就是相当于C语言中加上static关键字声明的变量,类的属性是与类对象进行绑定,并不会依赖任何它的实例对象。这点待会儿继续讲解。
另外,如果属性的名字与方法名相同,属性会覆盖方法:
为了避免名字上的冲突,编写代码时应该遵守一些约定俗成的规矩:
• 类的定义要“少吃多餐”,不要试图在一个类里边定义出所有能想到的特性和方法,应该利用继承和组合机制来进行扩展。
• 用不同的词性命名,如属性名用名词、方法名用动词,并使用骆驼命名法。骆驼式命名法(CamelCase)又称驼峰命名法,是电脑程式编写时的一套命名规则(惯例)。正如它的名称CamelCase所表示的那样,是指混合使用大小写字母来构成变量和函数的名字,程序员为了自己的代码能更容易在同行之间交流,所以多采取统一的可读性比较好的命名方式。
七、绑定
1.绑定
Python严格要求方法需要有实例才能被调用,这种限制其实就是Python所谓的绑定概念。
但有时类可以直接被调用
但这样做会有一个问题,就是根据类实例化后的对象根本无法调用里边的函数:
经过如下改进后即可使用
class BB:
def printBB(self):
print("123")
bb = BB()
bb.printBB()
而错误产生实际上由于Python的绑定机制,这里自动把bb对象作为第一个参数传入,所以才会出现TypeError。
2._ _dict_ _
_ _dict_ _属性由一个字典组成,字典中仅有实例对象的属性,不显示类属性和特殊属性,键表示的是属性名,值表示属性相应的数据值。
>>> dd.setXY(4, 5)
>>> dd._ _dict_ _
{'x': 4, 'y': 5}
现在实例对象dd有了两个新属性,而且这两个属性仅属于实例对象:
>>> CC._ _dict_ _
mappingproxy({'_ _doc_ _': None, '_ _dict_ _': <attribute '_ _dict_ _' of 'CC'
objects>, '_ _weakref_ _': <attribute '_ _weakref_ _' of 'CC' objects>,
'printXY': <function CC.printXY at 0x0370D2B8>,'_ _module_ _': '_ _main_ _',
'setXY': <function CC.setXY at 0x034A1420>})
只属于dd而不属于CC的原因是:self参数,当实例对象dd去调用setXY方法的时候,它传入的第一个参数就是dd,那么self.x = 4,self.y = 5也就相当于dd.x = 4,dd.y = 5,所以在实例对象甚至类对象中,都看不到x和y,因为这两个属性是只属于实例对象dd的。
如果把类实例删除掉,实例对象dd还能调用printXY方法。
>>> del CC
>>> dd.printXY()
4 5
八、一些相关的BIF
- issubclass(class, classinfo)
如果第一个参数(class)是第二个参数(classinfo)的一个子类,则返回True,否则返回False。
(1)一个类被认为是其自身的子类。
(2)classinfo可以是类对象组成的元组,只要class是其中任何一个候选类的子类,则返回True。
(3)在其他情况下,会抛出一个TypeError异常。
2. isinstance(object, classinfo)
如果第一个参数(object)是第二个参数(classinfo)的实例对象,则返回True,否则返回False。
(1)如果object是classinfo的子类的一个实例,也符合条件。
(2)如果第一个参数不是对象,则永远返回False。
(3)classinfo可以是类对象组成的元组,只要object是其中任何一个候选类的子类,则返回True。
(4)如果第二个参数不是类或者由类对象组成的元组,会抛出一个TypeError异常。
>>> issubclass(B, C)
False
>>> b1 = B()
>>> isinstance(b1, B)
True
>>> isinstance(b1, C)
False
>>> isinstance(b1, A)
True
>>> isinstance(b1, (A, B, C))
True
Python提供了以下几个BIF用于访问对象的属性。
3. hasattr(object, name)
attr即attribute的缩写,属性的意思。第一个参数(object)是对象,第二个参数(name)是属性名(属性的字符串名字)。
4. getattr(object, name[, default])
返回对象指定的属性值,如果指定的属性不存在,则返回default(可选参数)的值;若没有设置default参数,则抛出ArttributeError异常。
5. setattr(object, name, value)
与getattr()对应,setattr()可以设置对象中指定属性的值,如果指定的属性不存在,则会新建属性并赋值。
>>> setattr(c1, 'y', 'FishC')
>>> getattr(c1, 'y')
'FishC'
6. delattr(object, name)
与setattr()相反,delattr()用于删除对象中指定的属性,如果属性不存在,则抛出AttributeError异常。
7)property(fget=None, fset=None, fdel=None, doc=None)
property()是一个比较“奇葩”的BIF,它的作用是通过属性来设置属性。
程序实现如下:
property()返回一个可以设置属性的属性,当然如何设置属性还是需要人为来写代码。第一个参数是获得属性的方法名(例子中是getSize),第二个参数是设置属性的方法名(例子中是setSize),第三个参数是删除属性的方法名(例子中是delSize)。
property()可用于在大型程序,对对象的属性进行修改。

浙公网安备 33010602011771号