主要学习如何创建对象,多态、封装、方法、属性、超类和继承。
一、对象魔法
在面向对象编程中,术语对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法。
优点:
- 多态:可对不同类型的对象执行相同的操作。
- 封装:可对外部隐藏有关对象工作原理的细节。
- 继承:可基于通用类创建出专用类。
1.1多态
无需知道对象是什么样的就能对其执行操作,这就是多态在起作用。
1.2封装
与多态不同,多态让你无需知道对象1所属的类就能调用其方法,而封装让你无需知道对象的构造就能使用它。
1.3继承
可以避免多次输入同样的代码
二、类
类——一种对象。每个对象都属于特定的类,并被称为该类的实例。
一个类的对象为另一个对象的子集时,前者是后者的子类,后者是前者的超类。因此“云雀”是“鸟类”的子类,而“鸟类”是“云雀”的超类。
2.1创建自定义类
如果没有self,所有的方法都无法访问对象本身——要操作的属性所属的对象。
class Person:
# 其中self指向对象本身
def set_name(self, name):
self.name = name
def get_name(self):
return self.name
def greet(self):
print("Hello, world! I'am {}".format(self.name))
foo = Person()
bar = Person()
foo.set_name('leo')
bar.set_name('zu')
foo.greet()
bar.greet()
# Hello, world! I'am leo
# Hello, world! I'am zu
2.2属性、函数和方法
方法和函数的区别表现在提到的参数self上。方法将其第一个参数关联到它所属的实例,因此无需提供这个参数。
因此无需提供这个参数,无疑可以将属性关联到一个普通函数,但这样就没有特殊的self参数了。
class Class:
def method(self):
print('I have a self')
def function():
print("I don't")
instance = Class()
instance.method()
instance.method = function
instance.method()
# I have a self
# I don't
完全可以让另一个变量指向同一个方法
class Class:
def method(self):
print('I have a self')
instance = Class()
instance.method()
a = instance.method
a()
# I have a self
# I have a self
2.3隐藏,不能从外部访问类内的成员变量
默认情况下,可从外部访问对象的属性。
这违反了封装原则,应该对外部完全隐藏对象的状态(不能从外部访问类内的成员变量)。
为了避免这种情况,可将属性定义为私有。
私有属性不能从对象外部访问,而只能通过存取器方法(如get_name和set_name)来访问。
python没有为私有属性提供直接的支持,而是要求程序员知道在什么情况下从外部修改属性是安全的。
要让方法或属性成为私有的,只需要让其名称以两个下划线打头即可。
class Secretive:
def __inaccessible(self):
print("Bet you can't see me...")
def accessible(self):
print("The secret message is:")
self.__inaccessible()
s = Secretive()
# s.__inaccessible()
# AttributeError: 'Secretive' object has no attribute '__inaccessible'
s.accessible()
# The secret message is:
# Bet you can't see me...
s = Secretive()
s._Secretive__inaccessible() # 从类外访问私有方法
# Bet you can't see me...
在类定义中,对所有以两个下划线打头的名称都进行转换,即在开头加上一个下划线和类名。
如Secretive._Secretive__inaccessible()
只要知道这种幕后处理手法,就能从类外访问私有方法,然而不应该这样做。
2.4类的命名空间
定义类时情况:在class语句中定义的代码都是在一个特殊的命名空间(类的命名空间)内执行的,而类的所有成员都可访问这个命名空间。
2.5指定超类
子类扩展了超类的定义,要指定超类,可在class语句中的类名后加上超类名,并将其用圆括号括起来。
# 定义一个超类
class Filter:
pass
# 定义一个子类,继承超类Filter
class SampleFilter(Filter):
pass
- 子类可以重写超类Filter的方法
- 子类可以直接继承超类的方法定义。
2.6深入讨论继承
# 要确定一个类是否是另一个类的子类
# 使用内置方法issubclass
print(issubclass(SampleFilter, Filter)) # True
# 如果你有一个类,想要知道它的超类
# 可访问其特殊属性__bases__
print(SampleFilter.__bases__)
# (<class '__main__.Filter'>,)
# 要确定对象是否是特定类的实例,可使用isinstance
s = SampleFilter()
print(isinstance(s, SampleFilter)) # True
print(isinstance(s, Filter)) # True
# 从上面可以看到,s是SampleFilter类的实例。
# 但是由于因为SampleFilter是Filter的子类,
# 所以所有SampleFilter的实例都是Filter的实例。
# 如果你想获悉对象属于哪个类,可使用属性__class__
s = SampleFilter()
print(s.__class__) # <class '__main__.SampleFilter'>
2.7多个超类
多重继承,继承多个超类。
class Filter:
pass
class Complex:
pass
# 继承多个超类
class ComplexFilter(Filter, Complex):
pass
除非到万不得已,否则应避免使用多重继承。
需要注意:如果多个超类以不同的方式实现了同一个方法(有多个同名方法),必须在class语句中小心排列这些超类,因为位于前面的方法将覆盖于后面的类的方法。
2.8接口和内省
在Python中,不显式地指定对象必须包含哪些方法才能用作参数。
class Calculator:
def calculate(self, expression):
# eval是计算字符串中的表达式
self.value = eval(expression)
class Talker:
def talk(self):
print('Hi, my value is', self.value)
class TalkingCalculator(Calculator, Talker):
pass
# 检查所需的方法是否存在
tc = TalkingCalculator()
print(hasattr(tc, 'talk')) # True
print(hasattr(tc, 'sing')) # False
# 检查属性talk是否可调用
# getattr,能够指定属性不存在时使用默认值,这里为None
print(callable(getattr(tc, 'talk', None))) # True
2.9抽象基类
一般而言,抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实现的一组抽象方法。
Python通过引入模块abc,来为所谓的抽象基类提供了支持。
from abc import ABC, abstractmethod
class Talker(ABC):
@abstractmethod
def talk(self):
pass
# 实例化抽象类,会报错
# ta = Talker()
# ta.talk()
# 假设从Talker派生出一个子类
class knigget(Talker):
pass
# 由于没有重写方法talk,因此这个类也是抽象的。
# 重写以后,该类就可以实例化了。
形如@this的东西被称为装饰器。这里使用@abstractmethod来将方法标记为抽象的——在子类中必须实现的方法。
抽象类最重要的特征是不能实例化