Python进阶与拾遗3:Python中的类
本篇博文,主要讲解Python中的类。Python是一门面向对象的编程语言,因此在所有的Python大型工程中,几乎都是使用 面向对象的形式进行程序的组织与编写。因此,会用到大量关于类与对象的知识,下面开始干货。
类的相关概念
类是什么
在Python中,类本身是一个对象,也是命名空间,支持多个对象的产生,命名空间的继承以及运算符重载。是产生多个实例的工厂。
类提供默认的行为
- class语句创建类对象并将其赋值给变量名,类在命名时一般以一个大写字母开头。
class语句是可执行的语句,产生新的类,在文件导入时就会执行。 - class语句内的赋值语句创建类的属性。
class语句内的顶层的赋值语句会产生类中的属性,类的属性可由变量名点号运算获取。 - 类属性提供对象状态和行为。
类属性记录状态信息和行为,由类创建的所有实例共享。类中的def语句会生成方法。
实例对象是具体的元素
- 调用类会创建新的实例对象。每次类调用,会返回新的实例对象。
- 每个实例对象继承类的属性并获得了自己的命名空间。继承是在属性点号运算时发生,而且只查找连接对象的变量名。实例更改类的属性,只对实例自身有效。类更改类的属性,对所有实例均有效。
- 在方法内对self属性做赋值运算会产生每个实例自己的属性。对self属性的赋值运算,会创建或修改实例内的数据,而不是类的数据。
class FirstClass:
def setdata(self, value):
self.data = value
def display(self):
print(self.data)
def main():
x = FirstClass()
y = FirstClass()
x.setdata(123)
y.setdata("abc")
x.display()
y.display()
if __name__ == '__main__':
main()
# 输出:
# 123
# abc
类通过继承进行定制
类继承是实现编写类层次结构的大门。
- 超类列在类开头的括号中。
- 类从超类中继承属性。超类中有属性就继承,没属性的话会自动创建。
- 实例会继承所有可读类的属性。寻找变量名时,Python会检查实例,然后是类,然后是所有超类。
- 点号属性运算会开启新的独立搜索。每个self.attr表达式都会开启对self及其上层的类的attr语句搜索。
- 逻辑的修改是通过创建子类,而不是修改超类。在子类中重新定义超类的变量名,子类就可以取代超类中的变量。
class FirstClass:
def setdata(self, value):
self.data = value
def display(self):
print(self.data)
class SecondClass(FirstClass):
# display函数的覆盖,不是修改了FirstClass,而是对其完成了定制。
def display(self):
print('Current value = %s' % self.data)
def main():
x = FirstClass()
y = SecondClass()
x.setdata(123)
y.setdata("abc")
x.display()
y.display()
if __name__ == '__main__':
main()
# 输出:
# 123
# Current value = abc
运算符重载
相关概念
- 双下划线命名方法__X__表示拦截运算,是运算符重载的标志。
- 当实例出现在内置运算时,这类方法会自动调用。比如,实例对象继承了__add__方法,当对象出现在+表达式内时,该方法会调用。
- 类可以覆盖多种内置类型运算。有几十种特殊运算符重载方法名称,几乎可截获并实现内置类型的所有运算。
- 运算符覆盖方法没有默认值,也不需要。如果没有定义或继承运算符重载方法,相应的运算在这类实例中并不支持。比如如果没有__add__,+表达式就会引发异常。
- 运算符可使得类与Python对象模型相集成。
运算符重载的例子
- 新的实例构造时,会调用__init__。
- 实例出现在表达式+中时,会调用__add__。
- 打印一个对象时,运行__str__。
运算符重载的用处
- __init__构造函数。用于每个类的初始化,这也是使用最多的运算符重载。
- 在实现本质为数学的运算时,可以使用运算符重载。比如矩阵类。
- 在传递内置类型(如列表,字典)可用的运算符函数,可能会使用运算符重载。
class FirstClass:
def setdata(self, value):
self.data = value
def display(self):
print(self.data)
class SecondClass(FirstClass):
def display(self):
print('Current value = %s' % self.data)
class ThirdClass(SecondClass):
def __init__(self,value):
self.data = value
def __add__(self, other):
return ThirdClass(self.data + other)
def __str__(self):
return '[ThirdClass: %s]' % self.data
def mul(self, other):
self.data *= other
def main():
x = FirstClass()
y = SecondClass()
z = ThirdClass(3.1415926)
x.setdata(123)
y.setdata("abc")
x.display()
y.display()
z.display()
z_1 = z + 10
print(x)
print(y)
print(z)
print(z_1)
if __name__ == '__main__':
main()
'''
输出:
123
Current value = abc
Current value = 3.1415926
<__main__.FirstClass object at 0x00000138DF5A94C0>
<__main__.SecondClass object at 0x00000138DF62E220>
[ThirdClass: 3.1415926]
[ThirdClass: 13.1415926]
'''
类的设计理念
- 继承。
继承是基于Python中的属性查找的(X.name表达式)。 - 多态。
在X.method方法中,method意义取决于X的类型。 - 封装。
方法和运算符实现行为,数据隐藏默认是一种惯例。
类的编写步骤
本小节将从用例的角度出发分享类的编写步骤。一般来说,类名使用大写字母开头,而模块名使用小写字母开头。
步骤一:创建实例
使用构造函数
- 构造函数方法包含了每次创建一个实例的时候Python会自动运行的代码。
- 实例对象的属性,通过给类方法函数中的self属性赋值来创建。赋给实例属性初始值的主流方法是,在__init__构造函数方法中将其赋给self,并可选地提供默认值。
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def main():
# 两个实例对象都是命令空间对象,每一个都拥有各自类所创建的状态信息的独立副本。
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='Teacher', pay=100000)
print(bob.name, bob.pay)
print(sue.name, sue.pay)
if __name__ == "__main__":
main()
'''
输出:
Bob Smith 0
Sue Jones 100000
'''
步骤二:添加行为方法
编写方法
方法定义了处理那些类的实例的常规函数,实例是方法调用的主体,并且会自动传递方法给self参数。Python通过自动把实例传递给第一个参数从而告诉一个方法应该处理哪个实例,通常这个参数叫self。在调用时,有两种语法,可以通过实例进行方法调用,也可以通过类进行方法调用。
# 方法调用
Instance.method(args)
Class.method(instance, args)
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def main():
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='Teacher', pay=100000)
print(bob.name, bob.pay)
print(sue.name, sue.pay)
print(bob.lastName(), sue.lastName())
# print(Person.lastName(bob), Person.lastName(sue)) # 同理
sue.giveRaise(.10)
# Person.giveRaise(sue, .10) # 同理
print(sue.pay)
if __name__ == "__main__":
main()
'''
输出:
Bob Smith 0
Sue Jones 100000
Smith Jones
110000
'''
步骤三:运算符重载
- __init__构造函数就是最常用的运算符重载。
- 当方法在类的实例上运行的时候,方法截获并处理内置的操作。
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __str__(self):
return '[Person: %s, %s]' % (self.name, self.pay)
def main():
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='Teacher', pay=100000)
print(bob)
print(sue)
print(bob.lastName(), sue.lastName())
sue.giveRaise(.10)
print(sue)
if __name__ == "__main__":
main()
'''
输出:
[Person: Bob Smith, 0]
[Person: Sue Jones, 100000]
Smith Jones
[Person: Sue Jones, 110000]
'''
步骤四:通过子类定制行为
继承的意义
- 定制化的操作通过继承实现
- 在实现子类的定制行为时,应该尽量复用父类的版本,以免修改时工作量较大。这对未来维护代码意义重大。
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __str__(self):
return '[Person: %s, %s]' % (self.name, self.pay)
class Manager(Person):
def giveRaise(self, percent, bonus=.10):
Person.giveRaise(self, percent + bonus)
def main():
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='Teacher', pay=100000)
print(bob)
print(sue)
print(bob.lastName(), sue.lastName())
sue.giveRaise(.10)
print(sue)
tom = Manager('Tom Jones', 'mgr', 50000)
tom.giveRaise(.10)
print(tom.lastName())
print(tom)
if __name__ == "__main__":
main()
'''
输出:
[Person: Bob Smith, 0]
[Person: Sue Jones, 100000]
Smith Jones
[Person: Sue Jones, 110000]
Jones
[Person: Tom Jones, 60000]
'''
多重继承
在多重继承类的对象搜索属性时,Python会由坐至右搜索类首行中的超类,直到找到相符者。
Python中的多态
多态是,针对同样名称的成员方法,对不同的实例使用不同的定制版本。
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __str__(self):
return '[Person: %s, %s]' % (self.name, self.pay)
class Manager(Person):
def giveRaise(self, percent, bonus=.10):
Person.giveRaise(self, percent + bonus)
def main():
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='Teacher', pay=100000)
print(bob)
print(sue)
print(bob.lastName(), sue.lastName())
sue.giveRaise(.10)
print(sue)
tom = Manager('Tom Jones', 'mgr', 50000)
tom.giveRaise(.10)
print(tom.lastName())
print(tom)
print("==All three==")
for object in (bob, sue, tom):
object.giveRaise(.10)
print(object)
if __name__ == "__main__":
main()
'''
输出:
[Person: Bob Smith, 0]
[Person: Sue Jones, 100000]
Smith Jones
[Person: Sue Jones, 110000]
Jones
[Person: Tom Jones, 60000]
==All three==
[Person: Bob Smith, 0]
[Person: Sue Jones, 121000]
[Person: Tom Jones, 72000]
'''
步骤五:定制构造函数
- 如果子类定制了构造函数,那么创建子类实例时不会再执行父类的构造函数。
- 如果在子类中要执行父类的构造函数,可以通过父类的名称手动调用。
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __str__(self):
return '[Person: %s, %s]' % (self.name, self.pay)
class Manager(Person):
def __init__(self, name, pay):
Person.__init__(self, name, 'mgr', pay)
# super().__init__(name, 'mgr', pay) # 同理
def giveRaise(self, percent, bonus=.10):
Person.giveRaise(self, percent + bonus)
def main():
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='Teacher', pay=100000)
print(bob)
print(sue)
print(bob.lastName(), sue.lastName())
sue.giveRaise(.10)
print(sue)
tom = Manager('Tom Jones', 50000)
tom.giveRaise(.10)
print(tom.lastName())
print(tom)
if __name__ == "__main__":
main()
'''
输出:
[Person: Bob Smith, 0]
[Person: Sue Jones, 100000]
Smith Jones
[Person: Sue Jones, 110000]
Jones
[Person: Tom Jones, 60000]
'''
步骤六:使用内省工具
内省工具是特殊的属性与函数,允许我们访问对象实现的一些内部机制。
典型的内省工具:
- 实例的__class__属性提供了一个创建他的类的链接;类的__name__属性提供了类的名称,__bases__属性提供了超类。
- 实例的__dict__属性提供了一个字典,可以返回实例的每个属性。也可以调用dir函数,包含了继承的属性。
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __str__(self):
return '[Person: %s, %s]' % (self.name, self.pay)
class Manager(Person):
def __init__(self, name, pay):
Person.__init__(self, name, 'mgr', pay)
def giveRaise(self, percent, bonus=.10):
Person.giveRaise(self, percent + bonus)
def main():
bob = Person('Bob Smith')
tom = Manager('Tom Jones', 50000)
tom.giveRaise(.10)
print(list(bob.__dict__))
print(dir(bob))
print("------------------------------")
print(tom.__dict__)
print(dir(tom))
print("------------------------------")
print(Person.__dict__)
print(Person.__name__)
print(Person.__bases__)
print(dir(Person))
print("------------------------------")
print(Manager.__dict__)
print(Manager.__name__)
print(Manager.__bases__)
print(dir(Manager))
if __name__ == "__main__":
main()
'''
输出:
['name', 'job', 'pay']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'giveRaise', 'job', 'lastName', 'name', 'pay']
------------------------------
{'name': 'Tom Jones', 'job': 'mgr', 'pay': 60000}
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'giveRaise', 'job', 'lastName', 'name', 'pay']
------------------------------
{'__module__': '__main__', '__init__': <function Person.__init__ at 0x00000251D67CFF70>, 'lastName': <function Person.lastName at 0x00000251D67ED040>, 'giveRaise': <function Person.giveRaise at 0x00000251D67ED0D0>, '__str__': <function Person.__str__ at 0x00000251D67ED160>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
Person
(<class 'object'>,)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'giveRaise', 'lastName']
------------------------------
{'__module__': '__main__', '__init__': <function Manager.__init__ at 0x00000251D67ED1F0>, 'giveRaise': <function Manager.giveRaise at 0x00000251D67ED280>, '__doc__': None}
Manager
(<class '__main__.Person'>,)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'giveRaise', 'lastName']
'''
抽象超类
- 简单的做法可以使用assert语句或者NotImplementedError异常。
class Super_1:
def delegate(self):
self.action()
def action(self):
assert False, 'action must be defined'
class Super_2:
def delegate(self):
self.action()
def action(self):
raise NotImplementedError('action must be defined')
class Sub(Super_1):
def action(self):
print("This is the action")
def main():
# X = Super_1()
# X.delegate() # AssertionError: action must be defined
# Y = Super_2()
# Y.delegate() # NotImplementedError: action must be defined
Z = Sub()
Z.delegate()
if __name__ == "__main__":
main()
'''
输出:
This is the action
'''
- 直接使用抽象超类,无法实例化。通过在方法头部使用修饰器语法实现。在Python 3.0及之后的版本中,需要导入模块,但是在python 2.6及之后的版本中,直接使用类属性就行。
from abc import ABCMeta, abstractmethod
class Super(metaclass=ABCMeta):
def delegate(self):
self.action()
@abstractmethod
def action(self):
pass
class Sub(Super):
def action(self):
print("This is the action")
def main():
# X = Super() # TypeError: Can't instantiate abstract class Super with abstract methods action
# X.delegate()
Y = Sub()
Y.delegate()
if __name__ == "__main__":
main()
'''
输出:
This is the action
'''
类的伪私有属性
- 约定俗成:Python中用一个单一的下划线来编写内部名称,比如_X,表示不应修改的变量(对编译器无意义)。
- class语句内开头有两个下划线,但结尾没有两个下划线的变量名,比如__X,会自动扩张,从而包含所在类的名称。
- 伪私有属性,在类的继承中很常见,多用于超类中有同名的属性。
class C1:
def meth1(self): self.__x = 88
def meth2(self): print(self.__x)
class C2:
def metha(self): self.__x = 99
def methb(self): print(self.__x)
class C3(C1, C2):
pass
def main():
I = C3()
I.meth1()
I.metha()
print(I.__dict__)
I.meth2()
I.methb()
if __name__ == "__main__":
main()
'''
输出:
{'_C1__x': 88, '_C2__x': 99}
88
99
'''
以上,欢迎各位读者朋友提出意见或建议。
欢迎阅读笔者后续博客,各位读者朋友的支持与鼓励是我最大的动力!
written by jiong
时穷节乃见,
一一垂丹青。