Python类

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功能相反。可用于设置对象的属性。

  1. 查看对象中存储的所有值,可检查_dict_属性。
  2. 确定对象由什么组成,应研究模块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的职责,但它失败了

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值