1.类到底是什么
类型的同义词
类——一种对象。每个对象都属于特定的类,并被称为该类的实例。
例如:麻雀是一种鸟,用集合的方式来看的话,麻雀是鸟类这个集合的子集。转化为类的话,麻雀是鸟类的子类,而鸟类为麻雀的超类
2.创建自定义类
` ``python
metaclass=type #Python2请包含这行代码
class Person:
def set_name(self,name):
self.name=name
def get_name(self):
return self.name
def greet(self):
print(‘你好,我是(),很高兴认识你’.format(self.name))
示例包含了三个方法定义,类似于函数定义,但位于class语句内。class语句创建独立的命名空间。参数self是什么呢?它指向对象本身。Person是类的名称
```python
>>>foo=Person()
>>>bar=Person()
>>>foo.set_name('黎明')
>>>bar.set_name('灰烬')
>>>foo.greet
>'你好,我是黎明,很高兴认识你'
>>>bar.greet
>'你好,我是灰烬,很高兴认识你'
由上述代码可以看出self的作用。对foo调用set_name和greet时,foo都会作为第一个参数自动传递给它们。习惯上将其命名为self。实际上可以随意命名。
显然,self很有用,是必不可少的存在。如果没有它,所有的方法都无法访问对象本身——要操作的属性所属的对象。也可以从外部访问这些属性。
3.属性、函数和方法
方法和函数的区别在于参数self。方法(关联的方法)将其第一个参数关联到它所属的实例,因此无需提供这个参数。这无疑可以将属性关联到一个普通函数,这样的话就没有特殊的self参数。
>>>class Class:
... def method(self):
... print('I have a self!')
...
>>>def function():
... print("I don't...")
...
>>>instance=Class()
>>>instance.method()I have a self!
>>>instance.method=function
>>>instance.method()I don't...
请注意,有没有参数self并不取决于是否以刚才使用的方式调用方法。
实际上,完全可以让另一个变量指向同一个方法。
>>>class Bird:
... song='Good!'
... def sing(self):
... print()self.song)
...
>>>bird=Bird()
>>>bird.sing()
>Good!
>>>birdsong=bird.sing
>>>birdsong()
>Good!
虽然最后一个方法调用看起来像是函数调用,但变量birdsong指向的是关联的方法bird.sing,这意味着它也能访问参数self。(即它也被关联到类的实例)。
4.隐藏
默认情况下,,可从外部访问对象的属性。
>>>A.name
>'黎明'
>>>A.name='灰烬'
>A.get_name
>'灰烬'
有些人认为这没问题,有些人认为这违反了封装原则。他们认为应该对外部完全隐藏对象的状态(即不能从外部访问它们)。为什么要向外部隐藏属性呢?毕竟,如果可以直接访问ClosedObject(一个类)的属性name,就不需要创建方法setName和getName了。
关键是其他人不知道(也不应知道)对象内部发生的情况。例如,ClosedObject可能在对象修改名称时向管理员发送电子邮件。这种方法可能包含在方法set_name中。但若直接设置为c.name结果将如何呢?显而易见是不会发送邮件的。为避免该类问题,可以将属性定义为私有。私有属性不能从外部访问,只能通过存取器方法(如get_name和set_name)来访问。
Python没有为私有属性提供直接的支持,而是要求程序员知道在什么情况下从外部修改属性是安全的。要让方法或属性成为私有的,只需在其名称以两个下划线打头即可。
在类定义中,对所有以两个下划线打头的名称都进行转换,即在开头加上一个下划线
5.命名空间
#语句作用相同(即两者等价)
def foo(x):returnx*x
foo=lambdax:x*x
两者都创建了一个返回参数平方的函数,并将这个函数和变量foo相关联。即可以在全局作用域内定义foo,也可以在函数或方法内定义foo。定义类时同样如此:在class语句中定义的代码都在一个特殊的命名空间内执行,而类的所有成员都可以调用访问它。(类定义其实就是要执行的代码块)
类并不是只能包含def语句****,例如:
>>>class A:
print('下午好')
...
...
下午好
这个实例有点简单,看看下面的代码:
>>>class D:
number=0
def init(self):
D.number+=1
...
...
>>>D1=D()
>>>D1.init()
>>>D.number
1
>>>D2=D()
>>>D2.init()
>>>D.number #上述代码在类作用域内定义了一个变量,所有成员(实例)都可访问它。
2 #这里使用它计算类实例的数量,这里还使用了init来初始化所有实例。
>>>D1.number #每个实例都可访问这个类作用域的变量,就像方法一样。
2
>>>D2.number
2
>>>D1.number=10 #给属性number赋值
>>>D1.number
10
>>>D2.number #结果发现新值(新值被写入D1的一个属性中)覆盖了类级变量
2
6.超类
子类扩展了超类的定义。(子类和超类的关系可以把它们比作数学中的集合中的子集来理解。)要制定超类,可以在class语句中的类名后加上超类名,并将其用圆括号括起。
class Filter:
def init(self):
self.blocked=[]
def filter(self,sequence):
return[x for x in sequence if x not in self.blocked]
class SPAMFilter(Filter):
def init(self):
self.blocked=['SPAM']
#Filter是一个过滤序列的通用类。实际上,他不会过滤掉任何东西。
>>>f=Filter
>>>f.init()
>>>f.filter([1,2,3])
>[1,2,3]
#Filter类的用途在于可用作其他类的基类(超类)
>>>s=SPAMFilter()
>>>s.init()
>>>s.filter(['SPEM','egg','SPEM','add','SPEM'])
>['egg','add']
SPAMFilter类的定义中有两个要点:
□以提供新定义的方式重写了Filter类中方法init的定义。
□直接从Filter类继承了方法Filter的定义,因此无须重新编写其定义。
7.继承
要确定一个类是否为另一个类的子类,可使用内置方法issubclass
>>>issubclass(SPAMFilter,Filter)
>True
>>>issubclass(Filter,SPAMFilter)
>False
如果你有一个类,并想知道它的基类,可访问其特殊属性_bases_.
>>>SPAMFilter._bases_
>(<class _main_.Filter at 0x171e40>,)
>>>Filter._bases_
>(<class'object'>,)
要确定对象是否是特定类的实例,可使用isinstance。
>>>s=SPAMFilter()
>>>isinstance(s,SPAMFilter)
>True
>>>isinstance(s,Filter)
>True
>>>isinstance(s,str)
>False
如果要获悉对象属于哪个类,可使用属性_class_。
>>>s._class_
<class _main_.SPAMFilter at 0x1707c0>
8.多个超类
class Calculator:
def calculate(self,expression):
self.value=eval(expression)
class Talker:
def talk(self):
print('Hi,my value is',self.value)
class TalkingCalculator(Calculator,Talker):
pass
子类TalkingCalculator本身毫无作为,所有的行为都是从超类哪里继承过来的。关键是是从Calculator继承了calculate,并从Talker继承了talk。
>>>tc=TalkingCalculator()
>>>tc.calculate('1+2*3')
>>>tc.talk()
Hi,my value is 7
这被称为多重继承,是一个功能强大的工具。
通常情况下,应避免使用它。使用时,务必注意:如果多个超类以不同的方式实现了同一个方法(即有多个同名方法),必须在class语句中小心排列这些超类,因为位于前面的类的方法将覆盖位于后面的类的方法。所以,如果Calculator类中存在方法talk,那么这方法将覆盖Talker类的方法talk(导致它不可访问)。
class TalkingCalculator(Talker,Calculator):pass
这将导致Talker的方法是可以访问的。
当多个超类的超类相同时,查找特定方法或属性时访问超类的顺序称为**方法解析顺序(MRO)**它使用的算法非常复杂。
9.接口和内省
接口:两个不同事物之间进行适配的一种工具、规范和协议。
接口这一概念和多态息息相关。处理多态对象时,你只关心其接口(协议)——对外暴露的方法和属性。
通常,你要求对象遵循特定的接口(即实现特定的方法),但如果需要,也可以非常灵活地提出要求:不是直接调用方法,而是检查所需的方法是否存在;若不存在,就换一种方法。
>>>hasattr(tc,'talk')
>True
>>>hasattr(tc,'fnord')
>False
tc包含属性talk,但不包含fnord。
#检查talk是否可调用
>>>callable(getattr(tc,'talk',None))
True
>>>callable(getattr(tc,'fnord',None))
False
getattr:可以让你指定属性不存在时使用的默认值,这里为None,然后对返回的对象调用callable。
⚠️⚠️⚠️
setattr和getattr功能相反。可用于设置对象的属性。
- 查看对象中存储的所有值,可检查_dict_属性。
- 确定对象由什么组成,应研究模块inspect。该模块主要供高级用户创建对象浏览器(让对象能够以图像的形式浏览Python对象的程序)以及其他需要这种功能的类似程序。
10.抽象基类
Python不像其他语言,历史上大部分时间内,Python几乎都只依赖于鸭子类型,同时使用hasattr来检查所需的方法是否存在。很多其他语言(如Java和Go)都采用显式指定接口的理念。最终,Python通过引进模块abc提供了官方解决方案。这个模块为抽象基类提供了支持。
一般而言,抽象类是不能(不应该)实例化的类,其职责是定义子类应实现的一组抽象方法。
from abc import ABC,abstractmethod
class Talker(ABC):
@abstractmethod
def talk(self):
pass
形如@this的东西被称为装饰器。
抽象类最重要的特征是不能实例化。
>>>Talker()
Traceback(most recent call last):
File"<stain>",line 1,in <module>
TypeError:can't instantiate abstract class Talker with abstract methods talk
假设像下面从派生出一个子类:
class Knigget(Talker):
pass
由于没有重写方法talk,因此这个类也是抽象的,不能实例化。可以重新编写这个类,以达到实现要求的方法。
class Knigget(Talker):
def talk(self):
print("Ni!")
现在实例它没有任何问题。这是抽象基类的主要用途,而且只有在这种情形下使用isinstance才是妥当的:先检查给定的实例是Talker对象,就能说明这个实例在需要的情况下有方法talk。
>>>k=knigget()
>>>isinstance(k,Talker)
True
>>>k.talk()
Ni!
然而还缺少一个部分——使isinstance多态程度更高的部分。
只要实现了方法talk,即使不是Talker的子类,也能通过类型检查。
class Herring:
def talk(self):
print("Ni!")
它能通过检查,但不是Talker对象
>>>h=Herring
>>>isinstance(h,Talker)
False
可以从Talker派生出Herring。但是如果Herring可能是从他人的模块中导入的,就不能采用这样的做法。想用的话,可以把Herring注册为Talker,这样所有的Herring对象都将视为Talker对象。
>>>Talker.register(Herring)
<class '_main_.Hering'>
>>>isinstance(h,talker)
True
>>>issubclass(Herring,Talker)
True
这样做是没有问题的,但是从抽象类派生提供的保障没有了(缺点)
>>>class Clam:
... pass
...
>>>Talker.register(Clam)
<class '_main_.Clam'>
>>>issubclass(Clam,Talker)
True
>>>c=Clam
>>>isinstance(c,Talker)
True
>>>c.talk()
Traceback(most recent call last):
File"<stain>",line 1,in <module>
AttributeError:'Clam' object has no attribute 'talk'
将isinstance返回True视为一种意图表达。在这里,Clam有成为Talker的意图。虽然我们相信它能承担Talker的职责,但它失败了