- 类相关知识
- 对象相关知识
- 类属性增删改查
- 实例属性增删改查
- 对象与实例属性
- 静态属性
- 类方法
- 静态方法
- 组合
- 继承
- 接口设计与归一化设计
- 继承顺序之mro线性顺序列表
- Python2中的继承顺序
- 子类调用父类的方法
- super调用父类的方法
- 多态
- 封装
- 反射
- 动态导入模块
- 类的内置属性attr
- 继承方式完成包装
- 组合方式完成授权
- getattribute
- item
- str与repr
- 自定制format
- slots属性
- doc属性
- module和class
- 析构方法
- call方法
- 迭代器协议
- 描述符理论
- 描述符优先级
- 类的装饰器
- 自定制property
- 元类
- 自定义元类
类相关知识
对象相关知识
类属性增删改查
实例属性增删改查
对象与实例属性
静态属性
类方法
静态方法
组合
继承
接口设计与归一化设计
聚成顺序之mro线性顺序列表
Python2中的继承顺序
子类调用父类的方法
super调用父类的方法
多态
封装
反射
动态导入模块
类的内置属性attr
继承方式完成包装
组合方式完成授权
getattribute
item
str与repr
自定制format
slots属性
doc属性
module和class
析构方法
call方法
迭代器协议
描述符理论
描述符优先级
类的装饰器
自定制property
元类
自定义元类
一. 面向对象编程的一些特性
相比于面向过程的编程方式,采用面向对象的编程方式可以提高代码的复用性和灵活性
1. 复用性
设计一个类,就是为了将这个类作为一个模块来使用,一个类多次被使用就提高了这个类的复用性,最终达到减少代码量以及修改一处处处更新的目的。提高代码复用性的方法有两个:组合,派生
组合
组合就是将B类加入到A类中,两个类的关系就是A has B,通过这种方式来增强B类的功能和A类的代码重用性。
class Course(object):
def __init__(self, name, period):
self.name = name
self.period = period
class Teacher:
def __init__(self, name, gender, course):
self.name = name
self.gender = gender
self.course = course
c1 = Course('python', 7)
t1 = Teacher('Hax', 'male', c1)
print(t1.course.period)
>>7
Course类被组合进Teacher类,Teacher就拥有了Course类的所有属性和方法。
派生
子类继承了父类的属性,然后衍生出自己新的属性,如果子类衍生出的新的属性与父类的某个属性的名字相同,那么再调用子类的这个属性,就以子类的这个属性为准了。
类特征可以在子孙类或子类中进行继承。这些子类从基类继承他们的核心属性,并且不影响父类的的特性。
class Human(object):
def __init__(self, name, gender, birthday):
self.name = name
self.gender = gender
self.birthday = birthday
def prt(self):
print('from Human', self.name, self.gender, self.birthday)
def flag(self):
print('this is Human class')
class Teacher(Human):
def __init__(self, name, gender, birthday):
Human.__init__(self, name, gender, birthday)
def flag(self):
print('this is Teacher class')
h1 = Human('Ken', 'female', '2016/1/2')
t1 = Teacher('Joe', 'male', '2017/1/1')
h1.prt()
t1.prt()
h1.flag()
t1.flag()
>>
from Human Ken female 2016/1/2
from Human Joe male 2017/1/1
this is Human class
this is Teacher class
flag
方法在子类中实现了,调用时只调用子类的flag方法,而不影响父类的flag方法。prt
方法在父类中定义,而在子类中没有定义,那么调用时父类子类都调用父类的prt方法。
2.灵活性
灵活性通过多态和多态性实现
多态是从类的定义的维度来定义的,以动物类为例,鸡、鸭、鱼、狗都是动物,但是是不同的动物,又都有动物的特性,即一类事物的不同形态。
**多态性是从类方法的应用维度定义的。还是以动物类为例。所有的动物都有移动这个特性,移动这个特性包括:爬,游,走。为了方便应用可以定义一个函数接受不同的动物作为参数,执行相同的方法--移动,但执行的结果不同--爬,游,走,这就是多态性。
class Animal(object):
def __init__(self, sex, age):
self.sex = sex
self.age = age
def move(self):
pass
class Fish(Animal):
def __init__(self, sex, age):
Animal.__init__(self, sex, age)
def move(self):
print('Fish is swimming')
class Bird(Animal):
def __init__(self, sex, age):
Animal.__init__(self, sex, age)
def move(self):
print('Bird is flying')
f = Fish('female', 2)
b = Bird('male', 3)
def func(obj):
obj.move()
func(f)
func(b)
代码中定义的鱼和鸟就是动物类的多态表现,通过func
方法传入不同的动物的实例执行实例对应的方法就是多态性的体现
3. 封装
封装数据:保护隐私
封装方法:隔离复杂度
封装需要提供访问被封装内容的接口,接口调用者只能通过接口访问内部数据,并且不能访问被隐藏内容的细节。此外,可以在接口中设计逻辑,限制调用者的访问方式。
封装分为两个层次
第一个层次就是建立类,当类建立之后,调用者只能通过点的方式访问类内的数据。第二层次是通过
第二个层次是将类内的方法或属性定义为私有的,只能在类内部调用,类外部是调用不到的,或者在有需求时通过设计接口的方式供类外部使用。
使用双下划线设置私有变量
class Teacher(object):
def __init__(self, name, salary):
self.name = name
self.__salary = salary # 变形为_Teacher__salary
def __set_salary(self, s): # 变形为_Teacher__set_salary
self.__salary = s
def get_salary(self):
return self.__salary
类中所有__ x
都会变为_类名 __ x
,self.__x
引用的是变形后的属性;这种变形的目的是在类的外部是无法通过类. __x
或对象.__x
来访问到双下滑先开头定义的属性或方法的。</br>
在子类定义的__x
不会覆盖在父类定义的__x
,因为子类中变形成了:_子类名__x
,而父类中变形成了:_父类名__x
,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。</br>
这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性
,然后就可以访问了。</br>
变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形</br>
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
# 不使用双下滑线定义方法,子类方法覆盖了父类方法
class Human(object):
def language(self):
print('speak sth')
def speak(self):
self.language()
class China(Human):
def language(self):
print('speak chinese')
p = China()
p.speak()
>>speak chinese
# 使用双下滑线定义方法,子类方法没有覆盖父类方法
class Human(object):
def __language(self):
print('speak sth')
def speak(self):
self.__language()
class China(Human):
def __language(self):
print('speak chinese')
p = China()
p.speak()
>>speak sth
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *
时不能被导入,但是通过from module import _private_module
依然是可以导入的
property
property是一种特殊的特性,使用该特性后对象调用方法时会使用像对象访问属性一样的方式
import math
class Circle:
def __init__(self,radius):
self.radius=radius
@property
def area(self):
return math.pi * self.radius**2
@property
def perimeter(self):
return 2*math.pi*self.radius
c=Circle(10)
print(c.radius)
print(c.area) # 像访问数据属性访问area,实际执行函数功能,计算面积
print(c.perimeter) # 像访问数据属性访问perimeter,实际执行函数功能,计算周长
在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
class Foo:
def __init__(self, val):
self.__NAME = val #将所有的数据属性都隐藏起来
@property
def name(self):
pass
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError('%s must be str' % value)
self.__NAME = value
@name.getter
def name(self):
return self.__NAME
@name.deleter
def name(self):
raise TypeError('Can not delete')
f = Foo('Jack')
print(f.name)
>>Jack
上述例子通过@name.setter
装饰的方法将私有属性在检查过类型后赋值给了self.__NAME
,然后通过@name.getter
装饰的方法方法获取到了私有属性。
二. 类相关的知识点
1. 类属性及方法的增删改查
2. super
super
用于继承中,用来替换父类,简化代码,下面举例说明
python2
在介绍super
前先引入一个概念:新式类,经典类
在Python2
中类有新式类和经典类两种类,新式类是有父类的类,如果没有实际的父类就使用object
作为父类;经典类是没有父类的类。
python2
中super方法只对新式类有效
下面的例子就是新式类中super方法的使用范例
class People(object):
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
def walk(self):
print '%s is walking' % self.name
class China(People):
country = 'china'
def __init__(self, name, gender, age, language='chinese'):
super(China, self).__init__(name, gender, age)
self.language = language
super
代替People
并在其后加上当前类的类名China
和self
作为参数
python3
在``python3`中对语法进行了进一步的精简
class People(object):
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
def walk(self):
print('%s is walking' % self.name)
class China(People):
country = 'china'
def __init__(self, name, gender, age, language='chinese'):
super().__init__(name, gender, age)
self.language = language
可以看出将super
后面的参数省去了
3. 接口与归一化设计
在Python
中没有接口的概念,为了实现接口设计,可以使用继承实现;为了实现强制子类实现接口功能,可以使用abc
模块
import abc
class File(metaclass=abc.ABCMeta):
@abc.abstractmethod
def file_write(self):
pass
@abc.abstractmethod
def file_read(self):
pass
class Text(File):
def file_write(self):
print('text file execute write operation')
def file_read(self):
print('text file execute read operation')
class Process(File):
def file_write(self):
print('process file execute write operation')
def file_read(self):
print('process file execute read operation')
class Disk(File):
def file_write(self):
print('disk file execute write operation')
def file_read(self):
print('disk file execute read operation')
4. 在类内部定义的函数无的三种用途
绑定到对象的方法
只要是在类内部定义的,并且没有被任何装饰器修饰过的方法,都是绑定到对象的
class Foo:
def test(self): #绑定到对象的方法
pass
def test1(): #也是绑定到对象的方法,只是对象.test1(),会把对象本身自动传给test1,因test1没有参数所以会抛出异常
pass
绑定到对象,指的是:由对象调用
使用方式:对象.对象的绑定方法()
,不用为self传值
特性:调用时会把对象本身当做第一个参数传给对象的绑定方法
绑定到类的方法:classmethod
在类内部定义的,并且被装饰器@classmethod修饰过的方法,都是绑定到类的
class Foo:
def test(self): #绑定到对象的方法
pass
def test1(): #也是绑定到对象的方法,只是对象.test1(),会把对象本身自动传给test1,因test1没有参数所以会抛出异常
pass
f = Foo()
f.test1()
>>TypeError: test1() takes 0 positional arguments but 1 was given
绑定到对象,指的是:就给对象去用,
使用方式:对象.对象的绑定方法()
特性:调用时会把对象本身当做第一个参数传给对象的绑定方法
解除绑定的方法:staticmethod
既不与类绑定,也不与对象绑定,不与任何事物绑定
绑定的特性:自动传值(绑定到类的就是自动传类,绑定到对象的就自动传对象)
解除绑定的特性:不管是类还是对象来调用,都没有自动传值这么一说了
所以说staticmethod就是相当于一个普通的工具包
class Foo:
def test1(self):
pass
def test2():
pass
@classmethod
def test3(cls):
pass
@classmethod
def test4():
pass
@staticmethod
def test5():
pass
test1与test2都是绑定到对象方法:调用时就是操作对象本身
<function Foo.test1 at 0x0000000000D8E488>
<function Foo.test2 at 0x0000000000D8E510>
test3与test4都是绑定到类的方法:调用时就是操作类本身
<bound method Foo.test3 of <class 'main.Foo'>>
<bound method Foo.test4 of <class 'main.Foo'>>
test5是不与任何事物绑定的:就是一个工具包,谁来都可以用,没说专门操作谁这么一说
<function Foo.test5 at 0x0000000000D8E6A8>