笔记带有个人侧重点,不追求面面俱到。
出处: 菜鸟教程 - Python3 面向对象
参考内容:
使用 class
定义类。
class ClassName:
<statement-1>
.
.
.
<statement-N>
最好使用
class ClassName(object)
的形式定义类。
类的属性和方法:
class ClassName(object):
a = 0
def fuc():
print("Hello")
通过下述方法可以创建类实例。
obj = Myclass()
类有一个名为 __init__()
的特殊方法(构造方法),该方法在类实例化时会自动调用。
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.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()
class test(object):
a = 'cls' # 类变量
def __init__(self, num):
some.b = num # 实例变量
print(test.a)
obj = test(10)
print(obj.b)
print(obj.a)
一、创建
类方法第一个参数为 cls
,表示这个类本身,包含当前类的相关信息。
实例方法第一个参数为 self
,表示实例本身,包含实例对象的信息。
静态方法可以不用传入参数,相当于把一个外部函数放在类中。
class test(object):
a = "类变量"
def __init__(self):
self.b = "实例变量"
@classmethod
def cls_func(cls):
print("这是一个类方法")
def obj_func(self):
print("这是一个实例方法")
@staticmethod
def func():
print("这是一个静态方法")
二、调用
类内调用:
class Test(object):
a = "类变量"
def __init__(self):
self.b = "实例变量"
@classmethod
def cls_func(cls):
print("这是一个类方法")
def obj_func(self):
print("这是一个实例方法")
@staticmethod
def func():
print("这是一个静态方法")
# 类内调用
@classmethod
def 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()
@staticmethod
def 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类方法,调用该方法,调用的是子类的方法和子类的类属性。
在 Python 中,属性和方法的访问权限只有2种:公开和私有。以2个下划线开头进行声明,可以将属性和方法定义为私有。声明为私有后,只能在类的内部调用 ,不能在类的外部调用。
class Test:
def __init__(self, foo):
self.__foo = foo
def __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 = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
test._Test__bar()
print(test._Test__foo)
if __name__ == "__main__":
main()
在实际开发中,并不建议将属性设置为私有的,因为这会导致子类无法访问。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻。
在不将属性直接暴露给外界的情况下,如果想访问属性可以通过属性的 getter
(访问器)和 setter
(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用 @property
包装器来包装 getter
和 setter
方法,使得对属性的访问既安全又方便。
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
# 访问器 - getter方法
@property
def name(self):
return self._name
# 访问器 - getter方法
@property
def age(self):
return self._age
# 修改器 - setter方法
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('王大锤', 12)
person.play()
person.age = 22
person.play()
# person.name = '白元芳' # AttributeError: can't set attribute
if __name__ == '__main__':
main()
Python 是一门动态语言。通常,动态语言允许我们在程序运行时给对象绑定新的属性或方法,当然也可以对已经绑定的属性和方法进行解绑定。
import types
class Pearson(object):
def __init__(self, name, age):
self._name = name
self._age = age
def is_adult(self):
if self._age >= 18:
return True
else:
return False
def main():
boy = Pearson("小明", 16)
boy._gender = "男"
boy.is_adult = types.MethodType(is_adult, boy)
print(boy._gender)
print(boy.is_adult())
del boy.is_adult
delattr(boy, "_gender")
if __name__ == "__main__":
main()
注意:
del
和delattr
功能有限,都是针对实例对象而言的,对于类方法,类属性则删除不了。
更多内容可以参考:CSDN - 涤生大数据:Python语言的动态性:运行时动态绑定,删除属性和方法
如果需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义 __slots__
变量来进行限定。需要注意的是 __slots__
的限定只对当前类的对象生效,对子类并不起任何作用。
class Person(object):
# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')
def __init__(self, name, age):
self._name = name
self._age = age
子类(派生类 DerivedClassName)会继承父类(基类 BaseClassName)的属性和方法:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
基类定义在另一个模块中时,可以使用:
class DerivedClassName(modname.BaseClassName):
子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力。
多继承的类定义形如下:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类类中未找到时,python会从左到右查找父类中是否包含方法。
class A(object):
@classmethod
def who(cls):
print("A")
class B(object):
@classmethod
def who(cls):
print("B")
class C(A, B):
pass
def main():
C.who()
print(C.__mro__) # 查看类的方法解析顺序
if __name__ == "__main__":
main()
更多内容可以参考:朋疏哲N:Python多继承实现以及问题应对策略
super() 函数是用于调用父类(超类)的一个方法。super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。MRO 就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表。
语法:
super(type[, object-or-type])
参数:
实例:
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
子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。
class Parent: # 定义父类
def myMethod(self):
print ('调用父类方法')
class Child(Parent): # 定义子类
def myMethod(self):
print ('调用子类方法')
c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
super(Child, c).myMethod() # 用子类对象调用父类已被覆盖的方法
__init__
:构造函数,在生成对象时调用;__del__
:析构函数,释放对象时使用;__repr__
:打印,转换;__setitem__
:按照索引赋值;__getitem__
:按照索引获取值;__len__
:获得长度;__cmp__
:比较运算;__call__
:函数调用;__add__
:加运算;__sub__
:减运算;__mul__
:乘运算;__truediv__
:除运算;__mod__
:求余运算;__pow__
:乘方。参考阅读:
知乎 - 黄同学:Python基础(十九):面向对象“类”之魔法方法
DataScience:Day12.魔法方法&方法重写
#!/usr/bin/python3
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __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)