目录python
面向对象是一种程序设计思想,它把对象做为程序的基本单元,一个对象包含了数据和操做数据的函数。但并非全部语言都支持面向对象编程的。简单的从语言自己来分的话,主要分为如下三种:(并非说其余类型的语言很差,只是场景不适合而已,就比如操做系统,基本都是使用C语言编写的,语言没有优劣,只有适不适合)数据库
面向机器
(汇编语言等):抽象成机器指令,机器容易理解。面向过程
(C语言等):作一件事,排除个步骤,第一步干什么,第二部干什么,若是出现状况A,作什么处理,若是出现过程B,作什么处理。问题规模小,能够步骤话,循序渐进的处理。面向对象OOP
(Java语言,C++语言,Python等):针对状况复杂的状况,好比表示一个大楼的做用,使用函数就比较麻烦了。编程
如今大部分语言都是面向对象的,它适合软件规模比较大的场景使用数据结构
那什么是面向对象?咱们能够简单来讲它就是一套方法论,是 一种认识世界、分析世界的方法论
。将万事万物都抽象为各类对象,在Python中一切皆对象。下面来讲一下,主要的名词含义:app
抽象的概念,是一类事物的共同特征的集合。函数
用计算机语言来描述,就是属性(数据)和方法(操做事物的能力)的集合。但凡说分类:都是抽象的概念学习
对象是类的具象、是一个实体。对于咱们每一个个体,都是抽象概念人类的不一样实体(对象,实例),好比你吃鱼,你是对象,鱼也是对象,吃是动做.
PS:在python中咱们说类的实例一般会用instance或者object来指代。测试
操做:对象行为的抽象,用操做名和实现该操做的方法来描述(函数)操作系统
每一个人都是人类的一个单独实例,都有本身的名字、身高、体重等信息,这些信息是我的的属性,可是这些信息不能保存在人类中,由于它是抽象的概念,不能保存具体的信息,而咱们每一个人,每一个个体,都是具体的人,就能够存储这些具体的属性,并且不一样的人具备不一样的属性。设计
在Python中一切皆对象,对象是数据和操做的封装,对象是独立的,可是对象之间能够相互做用(你推我,我打你,相互做用)。
目前OOP是最接近人类认知的编程范式
面向对象有三大特色:
封装(Encapsulation)
,将数据和操做组装到一块儿,对外只暴露一些借口,经过接口访问对象。(好比驾驶员驾驶汽车,不须要了解汽车的构造,只须要知道使用什么部件怎么驾驶皆能够了,踩了油门就能跑,能够不了解其中的机动原理。)继承(Inheritance)
,多复用(继承来的就不用写了),遵循多继承少修改原则,OCP(Open-closed Principle),使用继承来改变,来体现个性(修改实例本身,不要去修改父类)多态(Polymorphism)
,面向对象编程最灵活的地方,动态绑定,好比人吃和猫吃,都是吃,可是不一样吃又不太同样。简单来讲就是:同一种方法,在不一样的子类上,表现方式不一样。(父类、子类经过继承联系在一块儿,经过一套方法,就能够实现不一样的表现,就是多态)对象是特征(变量)与技能(函数)的结合,类是一些列对象共有的特征与技能的结合体.
定义一个类:
class ClassName: 语句块
规范:
class
关键字大驼峰
命名class MyClass: # class 类名称 '''MyClass Doc''' # 类介绍,经过类对象的__doc__属性获取 x = 100 # 类属性 def showme(self): # 类方法名(函数) print('My Class') # 函数体 showme = lambda self: print('My Class') # 等同于上面定义的函数 p = MyClass() print(p.__doc__)
类对象
:类的定义执行后会产生一个类对象类的属性
:类定义中的变量和类中定义的方法都是类的属性类变量
:x是类MyClass的变量因此根据上面例子来讲:
经过使用类名加括号来构建实例化一个对象。
a = Person() # 实例化
daxin = Person() xiaoming = Person()
注意:上面的两次实例化产生的都是不一样的实例,即使是参数相同。Python类实例化后,会自动调用__init__
方法。这个方法第一个形式参数必须留给self,其余参数随意。
在Python中对象的实例化,其实分为两个部分,即实例化和初始化。主要对应__new__和__init__方法。
__变量名__
的方法叫作魔术方法
__new__方法比较复杂,通常用在元类的定义和修改上,这里先记住:__new__方法实例化一个对象的过程当中会调用__init__方法进行初始化,初始化完毕后再由__new__方法将产生的实例对象进行返回,当__new__方法和__init__方法没有被定义时,会隐式
的向上(父类)查找并调用。
class Person(): def __init__(self, name, age): # 形参 self.name = name # 这里daxin 赋给 self.name self.age = age # 这里20 赋给 self.age def sing(self): print('{} is sing'.format(self.name)) daxin = Person('daxin',20) # 实参
注意:
__init__
: 的self是由__new__方法注入进来的,不用手动传递,这个self就是实例化后的对象。__init__
: 只是添加了一些定制化的属性,并不会返回对象。注意:
__init__
方法只有在实例化的时候才会被调用。
类实例化后必定会得到一个类的实例,就是实例对象。__init__方法的第一个变量self就是实例自己。经过实例化咱们能够得到一个实例对象,好比上面的daxin,咱们能够经过daxin.sing()来调用sing方法,可是咱们并无传递self参数啊,这是由于类实例化后,获得一个实例对象,实例对象会绑定
到方法
上,在使用daxin.sing()进行调用时,会把方法的调用者daxin实例,做为第一个参数self传入。而self.name就是daxin实例的name属性,name保存在daxin实例中,而不是Person类中,因此这里的name被叫作实例变量。
类中定义的方法会存放在类中(仅存一份),而不是实例中,实例直接绑定到方法上。
class Person: def __init__(self,name): self.name = name def sing(self): print('{} is sing'.format(self.name)) daxin = Person('daxin') print(daxin.sing) # <bound method Person.sing of <__main__.Person object at 0x00000198EADD83C8>> 绑定方法 print(Person.sing) # <function Person.sing at 0x00000198EADD99D8> 方法函数
上例中,在调用daxin.sing时,daxin实例是被绑定到了sing方法上,当 a = Person('a') 时,a.sing其实也是把a绑定到了sing方法上,仔细看的话,不一样实例的sing方法的内存地址是相同的.
实例变量是每个实例本身的变量,是本身独有的。类变量是类的变量,是类的全部实例共享的属性和方法。下面咱们从一个例子来看实例变量和类变量
In [2]: class Person: ...: country = 'China' # 类变量 ...: def __init__(self, name, gender): ...: self.name = name # 实例变量 ...: self.gender = gender ...: In [3]: daxin = Person('daxin','male') In [4]: daxin.country Out[4]: 'China' In [5]: daxin.name Out[5]: 'daxin' In [6]: Person.country Out[6]: 'China' In [7]: Person.name --------------------------------------------------------------------------- AttributeError
观察例子,咱们能够发现:
实例变量只能经过实例访问(由于类自己是不会知道谁是他的实力)
类变量能够经过实例调用,但实例变量是实例私有的,没法经过类调用。
PS: 获取类属性的不一样的方式
daxin.age daxin.__class__.age Person.age type(daxin).age
有几个特殊的属性先来看一下,便于后续理解
特殊属性 | 含义 |
---|---|
__name__ |
获取类对象的对象名(返回字符串) |
__class__ |
获取实例对象的类型(至关于type),type和__class__返回的是一样的东西 |
__dict__ |
对象的属性的字典 |
__qualname__ |
类的限定名 |
In [8]: daxin.__class__ Out[8]: __main__.Person In [9]: Person.__class__ Out[9]: type In [10]: type(daxin) Out[10]: __main__.Person In [11]: type(daxin) is Person Out[11]: True In [13]: Person.__name__ Out[13]: 'Person' # 返回字符串
疑问:当类变量和实例变量重名时,到底访问的是哪一个?
class Person: age = 3 height = 170 def __init__(self, name, age=18): self.name = name self.age = age tom = Person('Tom') jerry = Person('jetty', 20) Person.age = 30 print(1, Person.age, tom.age, jerry.age) print(2, Person.height, tom.height, jerry.height) jerry.height = 175 print(3, Person.height, tom.height, jerry.height) tom.height += 10 print(4, Person.height, tom.height, jerry.height) Person.height += 15 print(5, Person.height, tom.height, jerry.height) Person.weight = 70 print(6, Person.weight, tom.weight, jerry.weight)
分析:
经过观察发现,当类变量和实例变量重名时,彷佛有必定的查找规则,那么就要说说__dict__了。
__dict__是一个很是重要的特殊属性,它是一个存放着对象的属性信息的字典(对实例来讲是字典,对类来讲是映射)。
In [14]: Person.__dict__ Out[14]: mappingproxy({'__module__': '__main__', 'country': 'China', '__init__': <function __main__.Person.__init__(self, name, gender)>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}) In [15]: daxin.__dict__ Out[15]: {'name': 'daxin', 'gender': 'male'} In [19]: daxin.__dict__['name'] # 由于__dict__是一个字典,咱们能够直接经过key来访问 Out[19]: 'daxin'
经过观察发现:
方法是记录在类的
__dict__
中的
实例属性查找顺序: 指的是实例使用.点号来访问属性,会先找本身的__dict__,若是没有,而后经过属性__class__找到本身的类,而后再在类的__dict__中查找.直接使用实例.__dict__[变量名],不会在类中查找。
为一个类经过装饰,增长一些类属性。例如可否给一个类增长一个NAME类属性并提供属性值
def dec(name): def warpper(cls): cls.NAME = name return cls return warpper @dec('tom') # Person = dec('tom')(Person) class Person: pass a = Person() print(a.NAME)
类也能够做为一个装饰器,后面会说
能够给类加装饰器,那么能够给类中的方法加装饰器吗?答案固然是能够的。
在类中中定义的方法大多都须要传入一个self参数,用于指定一个实例化对象,用于经过对象来调用这个方法,可是也有例外的状况
定义在类内部的普通函数,只能经过类来进行调用
In [23]: class Person: ...: def normal_function(): ...: print('normal_function') ...: In [24]: Person.normal_function() normal_function In [25]: test = Person() In [27]: test.normal_function() --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-27-9011f04ffcaa> in <module> ----> 1 test.normal_function() TypeError: normal_function() takes 0 positional arguments but 1 was given In [28]:
注意:
当经过实例访问时,因为实例会默认把当前实例看成self传入函数,而函数并无形参接受因此会报错。
虽然能够经过类调用,可是没人这样用,也就是说是
禁止
这么写的
类方法是给类用的,在使用时会将类自己当作参数传给类方法的第一个参数,使用@classmethod
装饰器来把类中的某个函数定义成类方法。
In [31]: class Person: ...: @classmethod ...: def class_method(cls): ...: print('class={0.__name__}({0})'.format(cls)) ...: cls.HEIGHT=170 ...: In [32]: Person.class_method() class=Person(<class '__main__.Person'>) In [35]: daxin = Person() In [36]: daxin.class_method() class=Person(<class '__main__.Person'>) In [37]:
注意:
经过类的实例依旧能够调用类方法,那是由于当经过实例调用时,@classmethod发现调用者是实例时,会把当前实例的__class__对象,传入给类方法的cls。
类方法相似于C++、Java中的静态方法。
静态方法是一种普通函数,位于类定义的命名空间中,不会对任何实例类型进行操做,它有以下特色
@classmethod
装饰器修饰的方法,在调用时不会隐式的传入参数class Person: @staticmethod def static_method(): print('static_method') daxin = Person() Person.static_method() daxin.static_method()
和普通函数的区别是,静态方法可使用实例调用,普通方法不能够。
类几乎能够调用全部方法,普通函数的调用通常不可能出现,由于不容许这么定义。实例也几乎能够调用全部内部定义的方法,可是调用普通方法时会报错,缘由是第一参数必须是类的实例self。
class Test: age = 10 def normal_funtion(): print('normal_function') @classmethod def class_method(cls): print(cls.__name__) @staticmethod def static_method(): # Test.age print('static_method') daxin = Test() daxin.normal_funtion() # 没法调用,由于经过实例调用时,实例会被看成第一个参数传给normal_funcion。 daxin.class_method() # 能够调用,当@classmethod检测到是经过实例调用时,会把当前实例的__class__,看成cls传递,能够访问类属性,但没法访问实例属性 daxin.static_method() # 能够调用,当@staticmethod检测到是经过实例调用时,会在当前实例的类中调用静态方法,没法访问类或实例的属性,可是能够访问全局变量(好比全局变量Test类) Test.normal_funtion() # 能够调用,类调用时不传递参数,恰好normal_function也不须要参数,没法访问类或实例的属性 Test.class_method() # 能够调用,类方法,经过类调用时,类会被看成cls传递给函数,能够访问类属性,没法访问实例属性 Test.static_method() # 能够调用,静态方法,等于定义在类内的函数,没法访问类或实例的属性,可是能够访问全局变量(好比全局变量Test类)
注意:
体会:
class Test: age = 10 def normal_funtion(abc): print('{}'.format(abc)) Test.normal_funtion(Test) Test.normal_funtion(123) a = Test() a.normal_funtion()
normal_function依旧是一个普通方法而已,加了参数发现类和实例均可以调用了,那是由于以前实例没法调用是由于实例调用时默认传递给函数做为第一个参数,那么我给普通函数加一个形参接受就能够了。没有场景这样使用,这里只作学习了解。
封装成类还能够控制什么属性可让别人访问,什么方法不能让别人访问,这就叫作访问控制。
使用 双下划线开头 的属性名,就是私有属性,现有以下例子:
class Person: def __init__(self, name, age): self.name = name self.age = age daxin = Person('daxin', 18) print(daxin.age)
咱们能够在外面直接使用daxin.age来访问daxin的age属性,可是若是这个age是银行卡密码,不能让别人知道该怎么办呢,Python提供了一种私有属性来解决
class Person: def __init__(self, name, age): self.name = name self.__age = age daxin = Person('daxin', 20) print(daxin.__age)
使用__开头的属性被称为私有属性,这种属性在类的外部是没法直接进行访问的,前面所说__dict__中会存放类的属性信息,那么咱们来看一下daxin实例的__dict__
是怎么样的。
print(daxin.__dict__) {'name': 'daxin', '_Person__age': 20}
根据上面结果能够得知:私有属性的本质上实际上是属性更名,设置self.__age属性时,__age 变为了 _Person__age(_类名__属性名)
class Person: def __init__(self,name, age): self.name = name self.__age = age daxin = Person('daxin',18) daxin.__age = 100 print(daxin.__dict__) # {'name': 'daxin', '_Person__age': 18, '__age': 100}
只有在类中定义的私有变量才会被更名,上面咱们虽然指定了实例的变量__age,但因为是在类外定义的,因此它并不会变形,就真的产生了一个__age属性,而在类内定义的,因为变型了,因此不会覆盖。观察__dict__
就能够看出结果。
咱们知道私有属性在定义时会被更名,而且知道更名后的属性名称,那么咱们是否就能够修改了呢?
class Person: def __init__(self,name, age): self.name = name self.__age = age def get_age(self): return self.__age daxin = Person('daxin',18) daxin._Person__age = 100 print(daxin.get_age()) # 100
经过结果咱们能够知道,只要知道了私有变量的名称,就能够直接从外部访问,而且修改它。但并不建议这么作!
Java等其余语言,比较严格,私有在外面是绝对访问不到的。
保护变量(protected),其实Python并不支持保护变量,是开发者本身的不成文的约定。那什么是保护变量呢?在变量前使用 一个下划线
的变量称为保护变量。
class Person: def __init__(self,name): self._name = name daxin = Person('daxin') print(daxin.__dict__) # {'_name': 'daxin'} print(daxin._name) # daxin
注释:
外部依旧能够看到而且调用
若是看见这种变量,就如同私有变量,尽可能不要直接使用
前面说了私有属性,那么私有方法和私有属性是类似的,使用单/双下划线开头的方法称为私有方法(不能已双下划线结尾)
class Person: def __init__(self,name): self._name = name def __age(self): return self._name print(Person.__dict__) # {'__module__': '__main__', '__init__': <function Person.__init__ at 0x00000228BCB22158>, '_Person__age': <function Person.__age at 0x00000228BCB221E0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
经过上面__dict__的内容,咱们发现私有方法和私有属性是相同的,都会被更名,因此知道了私有方法真正的名字,咱们依旧能够在外部进行调用,可是不建议。
单下划线的方法,也和变量相同,解释器不会作任何变型,只是告诉你,它是一个私有方法,不建议直接使用。
能够经过修改或替换类的成员。使用者调用的方式没有改变,可是,类提供的功能可能已经改变了。(好比前面的类装饰器),一般称做猴子补丁(Monkey Patch)
在运行时,对属性、方法、函数等进行动态替换。其目的每每是为了经过替换、修改来加强、扩展原有代码的能力。
class Person: def __init__(self, name): self.name = name def sing(self): print('{} is sing'.format(self.name)) def monkeypatch(): # 猴子补丁,用于动态修改Person中某个方法 Person.sing = lambda self: print('hello world') # 能够来自于不一样包中的某个函数,这里只是使用lambda举例 monkeypatch() # 执行后,Person.sing函数就被替换掉了 daxin = Person('daxin') daxin.sing() # 'hello world'
通常状况下是不建议使用的,可是在某些场景下,好比我替换的方法原来是链接数据库获取数据的,反复链接数据库测试不是很方便,因此在这种状况下,咱们使用补丁的方式,返回部分目标数据就行了,不用每次都去数据库取数据。
被属性装饰器装饰的方法,就变成了属性了。是否是很拗口?简单来讲就是:属性装饰器把一个方法变成属性,进行调用。这么作的目的能够把某些属性保护起来,不让外部访问(经过前面私有属性的了解,咱们知道这是不可能的,嘿嘿嘿)。它主要由下面三种组成,能够组合使用也能够单独使用。
@property
:标识下面的方法为属性方法,同时激活setter,deleter@方法.setter
:设置属性时调用方法@方法.deleter
:删除属性时调用方法不使用属性装饰器时,咱们为了隐藏某个属性可使用以下方法:
class Person: def __init__(self, name, age=18): self.name = name self.__age = age def age(self): # 通常称为getter方法 return self.__age def setage(self, value): # 通常称为setter方法 self.__age = value daxin = Person('daxin') daxin.setage(30) print(daxin.age())
使用起来很别扭,为何获取age属性要加括号执行方法呢?如何能让用户在使用age或者setage时不要认为他们是在调用函数,而是一个属性呢,下面使用属性装饰器property来完成这个需求
class Person: def __init__(self, name, age=18): self.name = name self.__age = age @property def age(self): # 通常称为getter方法 return self.__age daxin = Person('daxin') print(daxin.age)
添加了@property装饰器之后,被他装饰的函数,就能够像普通的属性来访问了(daxin.age,就是daxin.age()的返回值),那是否可使用daxin.age=100来设置age属性的值呢?
daxin.age = 100 >> AttributeError: can't set attribute
没法设置的,是由于property装饰的函数是只读的,若是要使用daxin.age = 100 来赋值时,还须要一个setter装饰器来装饰一个设置属性的函数,而且这个函数必须和property装饰的函数的名称相同。那若是要删除呢,天然就触发了deleter装饰器了,咱们在方法中,删除属性便可。
class Person: def __init__(self, name, age=18): self.name = name self.__age = age @property def age(self): # 通常称为getter方法 return self.__age @age.setter # 被装饰的函数.setter(设置一个属性的值时触发) def age(self,value): self.__age = value @age.deleter # 删除一个属性时触发(很不经常使用) def age(self): del self.__age daxin = Person() print(daxin.age) # 触发@property装饰过的age daxin.age = 200 # 触发age.setter del daxin.age # 触发age.deleter
这样使用起来就比较方便了,固然property还提供了另外一种写法property(getter,settr,deleter,'description')
,由于property其实是一个class类而已。
class Person: def __init__(self, name, age=18): self.name = name self.__age = age def getage(self): return self.__age def setage(self, value): self.__age = value def delage(self): del self.__age age = property(getage,setage,delage,'age property')
例:快速实现一个只读属性
class Person: def __init__(self, name, age=18): self.name = name self.__age = age age = property(lambda self:self.__age) # 快捷包装一个只读属性。
由于lambda不支持等号,而setter中有赋值等号,因此lambda就没法实现这个需求了。
__init__
用于在对象被实例化时为对象初始化一些属性信息,按照其余语言的逻辑,也能够称之为构造器,既然有构造器,那么就会有析构器,即在对象被销毁时执行。在Python的类中使用__del__方法定义的函数称为析构函数,在对象被销毁时触发执行。须要注意的是,这个方法不能引发对象自己的销毁,只是对象销毁的时候会自动调用它。换言之,就是当对象引用计数为0时,触发销毁操做(标记为可回收,等待GC),而销毁操做又会触发对象的__del__方法。
class Person: def __init__(self, name): self.name = name def __del__(self): print('{} is die'.format(self.name)) daxin = Person('daxin') daxin.__del__() # 仅仅是执行__del__方法,不会真正销毁 daxin.__del__() # 仅仅是执行__del__方法,不会真正销毁 del daxin # 在本例中引用计数为0,即真正的销毁 # 代码执行完毕后也会触发销毁操做。
通常在析构函数中清理当前实例中申请的内存空间或者某些对象,作一些资源释放的工做。
在其余面向对象的高级语言中,会有重载的改变。所谓重载,就是同一个方法名,可是参数数量、类型不同,就是同一个方法的重载。
下面是一段模拟其余语言方法重载的伪代码
:
func test(x int, y int) { pass } func test(x array,y array) { pass } test(4,5) test([1],[2])
在其余语言中,当test(4,5)时会调用上面的函数,当test([1],[2])会调用下面的函数,这就叫作类型重载,传递不一样的参数类型,就会执行对应的方法,而在Python中,后面的会直接覆盖前面的同名函数,因此Python没有重载,固然Python也不须要重载,为何呢?由于Python的动态语言的特性,其实悄悄的就实现了其余语言的类型重载
def test(x,y): return x + y test(4,5) test([1],[2]) test('a','b')
在Python中上面是能够执行的,可是在其余语言中,可能就须要对应三个函数处理不一样类型的数据,而后经过类型重载来调用。因为Python语言的特性,天生就能实现类型重载的功能。