菜鸟教程《Python 3 教程》笔记(20)
- 20 面向对象
- 20.1 面向对象技术简介
- 20.2 创建类
- 20.2.1 类定义
- 20.2.2 实例化
- 20.2.3 初始化
- 20.2.4 类变量、实例变量
- 20.2.5 类方法、实例方法、静态方法
- 20.3 访问可见性
- 20.3.1 @property装饰器
- 20.4 动态性
- 20.4.1 __slots__魔法
- 20.5 继承
- 20.5.1 多继承
- 20.5.2 super() 函数
- 20.6 多态
- 20.7 类的专有方法
- 20.7.1 运算符重载
笔记带有个人侧重点,不追求面面俱到。
20 面向对象
出处: 菜鸟教程 - Python3 面向对象
参考内容:
- GtiHub - jackfrued: Python-100-Days/09.面向对象进阶
- 知乎 - 泽霖: python类的实例方法、静态方法和类方法区别
20.1 面向对象技术简介
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- 方法: 类中定义的函数。
- 类变量: 类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 局部变量: 定义在方法中的变量,只作用于当前实例的类。
- 实例变量: 在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
- 数据成员: 类变量或者实例变量用于处理类及其实例对象的相关的数据。
- 实例化: 创建一个类的实例,类的具体对象。
- 对象: 通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
- 继承: 即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
- 方法重写: 如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
20.2 创建类
20.2.1 类定义
使用 class
定义类。
class ClassName:<statement-1>...<statement-N>
最好使用
class ClassName(object)
的形式定义类。
类的属性和方法:
class ClassName(object):a = 0def fuc():print("Hello")
20.2.2 实例化
通过下述方法可以创建类实例。
obj = Myclass()
20.2.3 初始化
类有一个名为 __init__()
的特殊方法(构造方法),该方法在类实例化时会自动调用。
class Complex:def __init__(self, realpart, imagpart):self.r = realpartself.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i) # 输出结果:3.0 -4.5
self代表类的实例,而非类
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self
。
class Test:def prt(self):print(self)print(self.__class__)t = Test()
t.prt()执行结果:
<__main__.Test instance at 0x100771878>
__main__.Test
从执行结果可以很明显的看出,self
代表的是类的实例,代表当前对象的地址,而 self.__class__
则指向类。
self 不是 python 关键字,把它换成 runoob 也是可以正常执行的:
class Test:def prt(runoob):print(runoob)print(runoob.__class__)t = Test()
t.prt()
20.2.4 类变量、实例变量
class test(object):a = 'cls' # 类变量def __init__(self, num):some.b = num # 实例变量print(test.a)
obj = test(10)
print(obj.b)
print(obj.a)
20.2.5 类方法、实例方法、静态方法
一、创建
类方法第一个参数为 cls
,表示这个类本身,包含当前类的相关信息。
实例方法第一个参数为 self
,表示实例本身,包含实例对象的信息。
静态方法可以不用传入参数,相当于把一个外部函数放在类中。
class test(object):a = "类变量"def __init__(self):self.b = "实例变量"@classmethoddef cls_func(cls):print("这是一个类方法")def obj_func(self):print("这是一个实例方法")@staticmethoddef func():print("这是一个静态方法")
二、调用
类内调用:
class Test(object):a = "类变量"def __init__(self):self.b = "实例变量"@classmethoddef cls_func(cls):print("这是一个类方法")def obj_func(self):print("这是一个实例方法")@staticmethoddef func():print("这是一个静态方法")# 类内调用@classmethoddef cls_func1(cls):cls.cls_func()cls.func()obj = cls()obj.obj_func()def obj_func1(self):self.cls_func()self.obj_func()self.func()@staticmethoddef func1():Test.cls_func()obj = Test()obj.obj_func()Test.func()
对象调用:
if __name__ == "__main__":test.cls_func()test.func()obj = test()obj.cls_func()obj.obj_func()obj.func()
三、使用场景
类方法一般是整个类都会使用的操作,不受实例化影响。
实例方法需要实例化,用于涉及特定实例化对象的操作。
静态方法用于不涉及类信息的操作。
四、区别
子类的实例继承了父类的static_method静态方法,调用该方法,还是调用的父类的方法和类属性。
子类的实例继承了父类的class_method类方法,调用该方法,调用的是子类的方法和子类的类属性。
20.3 访问可见性
在 Python 中,属性和方法的访问权限只有2种:公开和私有。以2个下划线开头进行声明,可以将属性和方法定义为私有。声明为私有后,只能在类的内部调用 ,不能在类的外部调用。
class Test:def __init__(self, foo):self.__foo = foodef __bar(self):print(self.__foo)print('__bar')def main():test = Test('hello')# AttributeError: 'Test' object has no attribute '__bar'test.__bar()# AttributeError: 'Test' object has no attribute '__foo'print(test.__foo)if __name__ == "__main__":main()
但是,Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来妨碍对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,下面的代码就可以验证这一点。之所以这样设定,可以用这样一句名言加以解释,就是"We are all consenting adults here"。因为绝大多数程序员都认为开放比封闭要好,而且程序员要自己为自己的行为负责。
class Test:def __init__(self, foo):self.__foo = foodef __bar(self):print(self.__foo)print('__bar')def main():test = Test('hello')test._Test__bar()print(test._Test__foo)if __name__ == "__main__":main()
在实际开发中,并不建议将属性设置为私有的,因为这会导致子类无法访问。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻。
20.3.1 @property装饰器
在不将属性直接暴露给外界的情况下,如果想访问属性可以通过属性的 getter
(访问器)和 setter
(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用 @property
包装器来包装 getter
和 setter
方法,使得对属性的访问既安全又方便。
class Person(object):def __init__(self, name, age):self._name = nameself._age = age# 访问器 - getter方法@propertydef name(self):return self._name# 访问器 - getter方法@propertydef age(self):return self._age# 修改器 - setter方法@age.setterdef age(self, age):self._age = agedef play(self):if self._age <= 16:print('%s正在玩飞行棋.' % self._name)else:print('%s正在玩斗地主.' % self._name)def main():person = Person('王大锤', 12)person.play()person.age = 22person.play()# person.name = '白元芳' # AttributeError: can't set attributeif __name__ == '__main__':main()
20.4 动态性
Python 是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。
import typesclass Pearson(object):def __init__(self, name, age):self._name = nameself._age = agedef is_adult(self):if self._age >= 18:return Trueelse:return Falsedef main():boy = Pearson("小明", 16)boy._gender = "男"boy.is_adult = types.MethodType(is_adult, boy)print(boy._gender)print(boy.is_adult())del boy.is_adultdelattr(boy, "_gender")if __name__ == "__main__":main()
注意:
del
和delattr
功能有限,都是针对实例对象而言的,对于类方法,类属性则删除不了。
更多内容可以参考:CSDN - 涤生大数据:Python语言的动态性:运行时动态绑定,删除属性和方法
20.4.1 __slots__魔法
如果需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义 __slots__
变量来进行限定。需要注意的是 __slots__
的限定只对当前类的对象生效,对子类并不起任何作用。
class Person(object):# 限定Person对象只能绑定_name, _age和_gender属性__slots__ = ('_name', '_age', '_gender')def __init__(self, name, age):self._name = nameself._age = age
20.5 继承
子类(派生类 DerivedClassName)会继承父类(基类 BaseClassName)的属性和方法:
class DerivedClassName(BaseClassName):<statement-1>...<statement-N>
基类定义在另一个模块中时,可以使用:
class DerivedClassName(modname.BaseClassName):
子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力。
20.5.1 多继承
多继承的类定义形如下:
class DerivedClassName(Base1, Base2, Base3):<statement-1>...<statement-N>
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类类中未找到时,python会从左到右查找父类中是否包含方法。
class A(object):@classmethoddef who(cls):print("A")class B(object):@classmethoddef who(cls):print("B")class C(A, B):passdef main():C.who()print(C.__mro__) # 查看类的方法解析顺序if __name__ == "__main__":main()
更多内容可以参考:朋疏哲N:Python多继承实现以及问题应对策略
20.5.2 super() 函数
super() 函数是用于调用父类(超类)的一个方法。super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。MRO 就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表。
语法:
super(type[, object-or-type])
参数:
- type – 类。
- object-or-type – 类,一般是 self。
实例:
class FooParent(object):def __init__(self):self.parent = 'I\'m the parent.'print ('Parent')def bar(self, message):print ("%s from Parent" % message)class FooChild(FooParent):def __init__(self):super().__init__() print ('Child')def bar(self, message):super().bar(message)print ('Child bar fuction')print (self.parent)if __name__ == '__main__':fooChild = FooChild()fooChild.bar('HelloWorld')
菱形继承:
---> B ---
A --| |--> D---> C ---
Python3 中继承遵循广度优先原则:
class A:def __init__(self):print("Enter A")print(self)print("Leave A")class B(A):def __init__(self):print("Enter B")print(self)super(B, self).__init__()print("Leave B")class C(A):def __init__(self):print("Enter C")print(self)super(C, self).__init__()print("Leave C")class D(B, C):def __init__(self):print("Enter D")print(self)super(D, self).__init__()print("Leave D")d = D()
运行结果:
Enter D
<__main__.D object at 0x7fdb02618490>
Enter B
<__main__.D object at 0x7fdb02618490>
Enter C
<__main__.D object at 0x7fdb02618490>
Enter A
<__main__.D object at 0x7fdb02618490>
Leave A
Leave C
Leave B
Leave D
20.6 多态
子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。
class Parent: # 定义父类def myMethod(self):print ('调用父类方法')class Child(Parent): # 定义子类def myMethod(self):print ('调用子类方法')c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
super(Child, c).myMethod() # 用子类对象调用父类已被覆盖的方法
20.7 类的专有方法
__init__
:构造函数,在生成对象时调用;__del__
:析构函数,释放对象时使用;__repr__
:打印,转换;__setitem__
:按照索引赋值;__getitem__
:按照索引获取值;__len__
:获得长度;__cmp__
:比较运算;__call__
:函数调用;__add__
:加运算;__sub__
:减运算;__mul__
:乘运算;__truediv__
:除运算;__mod__
:求余运算;__pow__
:乘方。
参考阅读:
知乎 - 黄同学:Python基础(十九):面向对象“类”之魔法方法
DataScience:Day12.魔法方法&方法重写
20.7.1 运算符重载
#!/usr/bin/python3class Vector:def __init__(self, a, b):self.a = aself.b = bdef __str__(self):return 'Vector (%d, %d)' % (self.a, self.b)def __repr__(self):return 'Vector (%d, %d)' % (self.a, self.b)def __add__(self,other):if other.__class__ is Vector:return Vector(self.a + other.a, self.b + other.b)elif other.__class__ is int:return Vector(self.a+other,self.b)def __radd__(self,other):"""反向算术运算符的重载__add__运算符重载可以保证V+int的情况下不会报错,但是反过来int+V就会报错,通过反向运算符重载可以解决此问题"""if other.__class__ is int or other.__class__ is float:return Vector(self.a+other,self.b)else:raise ValueError("值错误")def __iadd__(self,other):"""复合赋值算数运算符的重载主要用于列表,例如L1+=L2,默认情况下调用__add__,会生成一个新的列表,当数据过大的时候会影响效率,而此函数可以重载+=,使L2直接增加到L1后面"""if other.__class__ is Vector:return Vector(self.a + other.a, self.b + other.b)elif other.__class__ is int:return Vector(self.a+other,self.b)
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)
print (v1 + 5)
print (6 + v2)