仅为个人查阅使用,如有错误还请指正。
面向对象编程就是一种程序设计思想。把对象当作程序的基本单元,一个对象包含了数据和操作数据的函数。
这种设计思想是从自然界中来的。因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义一个运动员类:Class-Player,是指运动员这个概念,而实例(Instance)则是一个个具体的Player,比如:Jordan,Durant他们是具体的。
有编程经验的都知道,面向对象的三个特点:封装,继承,多态。
- 类和实例
类是抽象的模板。比如Player类。实例是一个个具体的对象,每个对象都拥有相同的方法。
- 定义类
通过关键字class
,后面紧跟类名(首字母大写),最后是(object)。可以表明他是承继哪个类,如果没有合适的,就选择object
这个类,因为它是所以类的祖宗。
class Player(object):
pass
class Player(object):
pass
- 创建实例
跟函数调用差不多,这里是类名()的操作。
# 创建实例1
player1 = Player()
# 创建实例2
player2 = Player()
# 创建实例1
player1 = Player()
# 创建实例2
player2 = Player()
- 绑定属性
通过创建__init__.py
的方法,self
代表实例本身。
class Player(object):
def __init__(self, name, age):
self.name = name
self.age = age
class Player(object):
def __init__(self, name, age):
self.name = name
self.age = age
获取属性的方式可以通过对象.属性
# 创建实例1
player1 = Player("Jordan", 45)
# 获取属性
name = player1.name
age = player1.age
# 创建实例1
player1 = Player("Jordan", 45)
# 获取属性
name = player1.name
age = player1.age
- 封装
现在,我们知道实例可以拥有这些属性。可以直接在类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。而该函数就是类的方法。
实现一个打印姓名年龄的方法
class Player(object):
def __init__(self, name, age):
self.name = name
self.age = age
def print_name_age(self):
print("%s, %d"%(self.name, self.age))
player1 = Player("Jack", "30")
print(player1.print_name_age())
class Player(object):
def __init__(self, name, age):
self.name = name
self.age = age
def print_name_age(self):
print("%s, %d"%(self.name, self.age))
player1 = Player("Jack", "30")
print(player1.print_name_age())
从一个调用者来看,我只要在创建实例的时候输入name
,age
。并且调用该方法,获得我想要的。具体类里面怎么实现的,我不需要去管。
- 访问限制
在前面的类和实例中,我们了解到在类的内部,可以有属性和方法。并且外部代码还可以自由修改一个实例的属性。这样其实是不安全的,我们应该要避免这样的操作。也就是要把这变量从公有变到私有。
- 属性私有化
在Python中,使用两个下划线__
就可以表示私有属性。继续改我们的Player这个类。
class Player(object):
def __init__(self, name, age):
self.__name = name
self.__age = age
def print_name_age(self):
return "%s, %d" % (self.__name, self.__age)
player1 = Player("Jack", 30)
print(player1.__name) # ERROR
class Player(object):
def __init__(self, name, age):
self.__name = name
self.__age = age
def print_name_age(self):
return "%s, %d" % (self.__name, self.__age)
player1 = Player("Jack", 30)
print(player1.__name) # ERROR
属性被私有化之后,就不能在获取了。
- 获取,修改私有化属性
其实很简单,就是通过get
,set
方法。现在给类中添加get_name
,get_age
方法,以及set_name
,set_age
方法。继续改我们的Player类。
class Player(object):
def __init__(self, name, age):
self.__name = name
self.__age = age
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def set_age(self, age):
self.__age = age
def print_name_age(self):
return "%s, %d" % (self.__name, self.__age)
player1 = Player("Jack", 30)
player1.set_age(100)
print(player1.get_name()) # Jack
print(player1.get_age()) # 100
class Player(object):
def __init__(self, name, age):
self.__name = name
self.__age = age
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def set_age(self, age):
self.__age = age
def print_name_age(self):
return "%s, %d" % (self.__name, self.__age)
player1 = Player("Jack", 30)
player1.set_age(100)
print(player1.get_name()) # Jack
print(player1.get_age()) # 100
很明显,实现了改功能。本来可以不加方法,就可以直接修改,获取,现在加了方法有点画蛇添足。其实不是,加方法的目的是为了对参数做检查,避免传入无效的参数。
实例
def set_age(self, age):
if 0 <= age <= 100:
self.__age = age
else:
raise ValueError('bad score')
def set_age(self, age):
if 0 <= age <= 100:
self.__age = age
else:
raise ValueError('bad score')
当然,在实际开发项目中,你会看到一个下划线开头的实例属性,_name
。访问是可以访问,但请你把它视为私有。
最后,如果不写get
方法能不能调用,当然可以,因为Python解释器把__name
属性变成了_Player__name
。但是建议你忘记。不推荐
- 继承和多态
- 继承
在类的定义就讲过,所有的类都可以继承object
,同样也可以继承我们自己定义的类。
比如说,我定义一个Animal
动物类,该动物有一个run()
方法。
如果我再编写一个狗类和猫类,我就可以去继承这个动物类。
实例
class Animal(object):
def run(self):
return "animal is running"
class Dog(Animal):
pass
class Cat(Animal):
pass
class Animal(object):
def run(self):
return "animal is running"
class Dog(Animal):
pass
class Cat(Animal):
pass
继承的好处:子类可以获得父类的全部功能,也就是说狗类和猫类已经拥有run()
方法了。
基于上面的实例,我们去调用。
dog = Dog()
print(dog.run())
cat = Cat()
print(cat.run())
# output
animal is running
animal is running
dog = Dog()
print(dog.run())
cat = Cat()
print(cat.run())
# output
animal is running
animal is running
为了符合逻辑性,我们继续改代码
class Animal(object):
def run(self):
return "animal is running"
class Dog(Animal):
def run(self):
return "dog is running"
class Cat(Animal):
def run(self):
return "cat is running"
dog = Dog()
print(dog.run())
cat = Cat()
print(cat.run())
# output:
dog is running
cat is running
class Animal(object):
def run(self):
return "animal is running"
class Dog(Animal):
def run(self):
return "dog is running"
class Cat(Animal):
def run(self):
return "cat is running"
dog = Dog()
print(dog.run())
cat = Cat()
print(cat.run())
# output:
dog is running
cat is running
这样就很明确了,是谁在跑。
当子类和父类都存在相同的run()
方法时。子类的已经把父类的给覆盖了。
- 多态
对于初学者来说,多态还是有点难理解的,只能不断的通过程序来强化。
class Animal(object):
def run(self):
return "animal is running"
class Dog(Animal):
pass
class Cat(Animal):
def run(self):
return "cat is running"
dog = Dog()
print(isinstance(dog, Dog))
print(isinstance(dog, Animal))
# output:
True
True
class Animal(object):
def run(self):
return "animal is running"
class Dog(Animal):
pass
class Cat(Animal):
def run(self):
return "cat is running"
dog = Dog()
print(isinstance(dog, Dog))
print(isinstance(dog, Animal))
# output:
True
True
从上面的代码可以看出,dog
属于Dog
类型,又属于Animal
类型。同理,cat
也是这样的。
当我们传入一个对象时,如果该对象有run()
方法,就执行。没有的话,就去调用父类的run()
方法。这就是多态。
这就是动态语言,动态语言调用实例方法,不检查类型,只要方法存在,参数正确,就可以调用。
继续研究
import json
f = open('/path/to/file.json', 'r')
print(json.load(f))
class Students(object):
def __init__(self, strlist):
self.strlist = strlist
def read(self):
return(self.strlist)
s = Students('["Tim", "Bob", "Alice"]')
print(json.load(s))
import json
f = open('/path/to/file.json', 'r')
print(json.load(f))
class Students(object):
def __init__(self, strlist):
self.strlist = strlist
def read(self):
return(self.strlist)
s = Students('["Tim", "Bob", "Alice"]')
print(json.load(s))
因为f
对象具有read()
方法,而s
对象也有read()
方法。
因此就可以认为,任何对象,只要有read()
方法,就称为File-like Object,都可以传给json.load()
。
- 获取对象信息
判断对象类型:isinstance()
函数,获取对象类型:type()
。
获取对象属性与方法:dir()
函数。 - 实例属性和类属性
前面我们提到,可以使用__init__.py
方法进行实例属性绑定。
同样,类也可以有属性。且这个属性归类所有。
实例
class Player(object):
country = "chinese"
class Player(object):
country = "chinese"
当我们定义一个类属性后,这个属性虽然归类所有,但类的所以实例都可以访问到。
测试以上的说法
p = Player() # 创建实例p
print(p.country) # 打印country属性,因为实例并没有country属性,所以会继续查找类属性
print(Player.country) # 打印类属性
p.country = "small Japan" # 给实例绑定country属性
print(p.country) # 由于实例属性的优先级高于类属性,因此屏蔽了类的属性
print(Player.country) # 但是类属性并未消失。还是可以访问
del p.country # 删除实例绑定的属性之后
print(p.country) # 再去调用,就只能委屈得到类属性了。
p = Player() # 创建实例p
print(p.country) # 打印country属性,因为实例并没有country属性,所以会继续查找类属性
print(Player.country) # 打印类属性
p.country = "small Japan" # 给实例绑定country属性
print(p.country) # 由于实例属性的优先级高于类属性,因此屏蔽了类的属性
print(Player.country) # 但是类属性并未消失。还是可以访问
del p.country # 删除实例绑定的属性之后
print(p.country) # 再去调用,就只能委屈得到类属性了。
注意:实际编写程序的时候,千万不要对实例属性和类属性使用相同的名字。
- 类方法
前面说到实例有属性和方法,那类也有属性,同样也有方法。
实例
class Person(object):
count = 0
@classmethod
def how_many(cls):
return cls.count
def __init__(self, name):
self.name = name
Person.count = Person.count + 1
print(Person.how_many())
p1 = Person('Bob')
print(Person.how_many())
class Person(object):
count = 0
@classmethod
def how_many(cls):
return cls.count
def __init__(self, name):
self.name = name
Person.count = Person.count + 1
print(Person.how_many())
p1 = Person('Bob')
print(Person.how_many())
可以看到,通过标记一个@classmethod,该方法将绑定到Person
类上,而非类的实例。类方法的第一个参数将传入类本身,通常将参数名命名为cls
,上面的cls.count
实际上相当于Person.count
。
因为是在类上调用,而非实例上调用,因此类方法无法获得任何实例变量,只能获取类的引用。
- __slots__
因为可以动态绑定,所以我们需要限制,该方法就是用来限制实例的属性。例如如下程序:
class Student(object):
__slots__ = ('name', 'age')
s = Student()
s.name = "Jack"
s.age = 20
s.score = 90 # AttributeError
class Student(object):
__slots__ = ('name', 'age')
s = Student()
s.name = "Jack"
s.age = 20
s.score = 90 # AttributeError
上面程序可以看出:用tuple定义允许绑定的属性名称,由于score没有被放到__slots__
中,所以不能绑定score属性。如果有Student有子类,slots
定义的属性对子类是没有作用的。
- @property
我们在前面提到了访问限制。关于变量的关系。我们使用了get
,set
的方法。看上去有点复杂,没有直接用属性这么方便。
那Python中有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?是存在的。
好比装饰器可以给函数动态加上功能,对于类方法,装饰器一样起到作用。
Python内置的@property
装饰器就是负责把一个方法变成属性调用的。例如如下程序
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s = Student()
s.score = 60
print(s.score)
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s = Student()
s.score = 60
print(s.score)
@property
的作用就是把一个getter方法变成属性,只需要加上@property
就可以了,此时,@property
本身又创建了另一个装饰器@score.setter
负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作。再看代码,s.score = 60
实际转化为s.set_score(60)
,s.score
实际转化为s.get_score()
。所以本质其实还是通过getter和setter方法来实现的。
- 枚举类
在某些情况下,一个类的对象是有限且固定的,比如季节类,它只有4个对象;行星类,目前只要8个对象;月份类,它有12个对象,这种实例有限且固定的类,被称为枚举类。
- 定义枚举类
通过使用Enum()函数来创建。第一个参数是枚举类的类名,第二个参数是一个元组格式的枚举值。
import enum
Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'FALL', 'WINTER'))
import enum
Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'FALL', 'WINTER'))
- 访问枚举值
每个成员都有 name、value 两个属性。
# 直接访问指定枚举
print(Season.SPRING)
# 访问枚举成员的变量名
print(Season.SPRING.name)
# 访问枚举成员的值
print(Season.SPRING.value)
# output:
Season.SPRING
SPRING
1
# 直接访问指定枚举
print(Season.SPRING)
# 访问枚举成员的变量名
print(Season.SPRING.name)
# 访问枚举成员的值
print(Season.SPRING.value)
# output:
Season.SPRING
SPRING
1
此外,还提高了一个__members__
属性,该属性返回一个 dict 字典。
for name, member in Season.__members__.items():
print(name, '=>', member, ',', member.value)
# output:
SPRING => Season.SPRING , 1
SUMMER => Season.SUMMER , 2
FALL => Season.FALL , 3
WINTER => Season.WINTER , 4
for name, member in Season.__members__.items():
print(name, '=>', member, ',', member.value)
# output:
SPRING => Season.SPRING , 1
SUMMER => Season.SUMMER , 2
FALL => Season.FALL , 3
WINTER => Season.WINTER , 4
- type()
前面有提到,type()函数可以查看变量的类型,但如果想使用type()直接查看某个类的类型呢?请看以下代码:
class Role:
pass
r = Role()
# 查看变量r的类型
print(type(r)) # <class '__main__.Role'>
# 查看Role类本身的类型
print(type(Role)) # <class 'type'>
class Role:
pass
r = Role()
# 查看变量r的类型
print(type(r)) # <class '__main__.Role'>
# 查看Role类本身的类型
print(type(Role)) # <class 'type'>
从上面的输出结果可以卡的看到,Role类本身的类型是 type。这句话有点拗口,怎样理解 Role 类的类型是 type?
从 Python 解释器的角度来看,当程序使用 class 定义 Role 类时,也可理解为定义了一个特殊的对象(type 类的对象),并将该对象赋值给 Role 变量。因此,程序使用 class 定义的所有类都是 type 类的实例。
实际上 Python 完全允许使用 type() 函数(相当于 type 类的构造器函数)来创建 type 对象,又由于 type 类的实例就是类,因此 Python 可以使用 type() 函数来动态创建类。例如如下程序:
def fn(self):
print('fn函数')
# 使用type()定义Dog类
Dog = type('Dog', (object,), dict(walk=fn, age=6))
# 创建Dog对象
d = Dog()
# 分别查看d、Dog的类型
print(type(d))
print(type(Dog))
d.walk()
print(Dog.age)
#output:
<class '__main__.Dog'>
<class 'type'>
fn函数
6
def fn(self):
print('fn函数')
# 使用type()定义Dog类
Dog = type('Dog', (object,), dict(walk=fn, age=6))
# 创建Dog对象
d = Dog()
# 分别查看d、Dog的类型
print(type(d))
print(type(Dog))
d.walk()
print(Dog.age)
#output:
<class '__main__.Dog'>
<class 'type'>
fn函数
6
使用 type() 定义类时可指定三个参数:
1、参数一:创建的类名。
2、参数二:该类继承的父类集合。由于 Python 支持多继承,因此此处使用元组指定它的多个父类。即使实际只有一个父类,也需要使用元组语法(必须要多一个逗号)。
3、参数三:该字典对象为该类绑定的类变量和方法。其中字典的 key 就是类变量或方法名,如果字典的 value 是普通值,那就代表类变量;如果字典的 value 是函数,则代表方法。
由此可见,第 5 行代码定义了一个 Dog 类,该类继承了 object 类,还为该类定义了一个 walk() 方法和一个 age 类变量。
- 元类
理解元类不难,看完以下整个过程,如果你还不明白,那你别学了。
前面讲了 type() 函数,其实它时适用于动态创建相对简单的类,如果要创建复杂的类,则需要通过 MetaClass(元类)的方式。
元类可以简单的理解为,就是创建类的类。
- 定义元类
需令其继承与 type 类,且默认的命名习惯是,让类名以 MetaClass 结尾。不仅如此,元类中需要定义并实现__new__()
方法(一定要有返回值)。因为元类在创建类时,该__new__()
方法将会被调用,用来生成新建的类。
# 定义Item元类,继承type
class ItemMetaClass(type):
# cls代表动态修改的类
# name代表动态修改的类名
# bases代表被动态修改的类的所有父类
# attr代表被动态修改的类的所有属性、方法组成的字典
def __new__(cls, name, bases, attrs):
# 动态为该类添加一个cal_price方法
attrs['cal_price'] = lambda self: self.price * self.discount
return type.__new__(cls, name, bases, attrs)
# 定义Item元类,继承type
class ItemMetaClass(type):
# cls代表动态修改的类
# name代表动态修改的类名
# bases代表被动态修改的类的所有父类
# attr代表被动态修改的类的所有属性、方法组成的字典
def __new__(cls, name, bases, attrs):
# 动态为该类添加一个cal_price方法
attrs['cal_price'] = lambda self: self.price * self.discount
return type.__new__(cls, name, bases, attrs)
上面程序中,在重写该方法时为目标类动态添加了一个 cal_price 方法。
- 使用元类创建类
# 定义Book类
class Book(metaclass=ItemMetaClass):
__slots__ = ('name', 'price', '_discount')
def __init__(self, name, price):
self.name = name
self.price = price
@property
def discount(self):
return self._discount
@discount.setter
def discount(self, discount):
self._discount = discount
# 定义cellPhone类
class CellPhone(metaclass=ItemMetaClass):
__slots__ = ('price', '_discount' )
def __init__(self, price):
self.price = price
@property
def discount(self):
return self._discount
@discount.setter
def discount(self, discount):
self._discount = discount
# 定义Book类
class Book(metaclass=ItemMetaClass):
__slots__ = ('name', 'price', '_discount')
def __init__(self, name, price):
self.name = name
self.price = price
@property
def discount(self):
return self._discount
@discount.setter
def discount(self, discount):
self._discount = discount
# 定义cellPhone类
class CellPhone(metaclass=ItemMetaClass):
__slots__ = ('price', '_discount' )
def __init__(self, price):
self.price = price
@property
def discount(self):
return self._discount
@discount.setter
def discount(self, discount):
self._discount = discount
上面程序定义了 Book 和 CellPhone 两个类,在定义这两个类时都指定了元类信息,因此当 Python 解释器在创建这两个类时,ItemMetaClass 的 __new__
方法就会被调用,用于修改这两个类。
所以定义的这两个类,依然是有 cal_price() 方法。如下代码进行检测。
b = Book("Python基础教程", 89)
b.discount = 0.75
# 创建Book对象的cal_price()方法
print(b.cal_price())
cp = CellPhone(2399)
cp.discount = 0.85
# 创建CellPhone对象的cal_price()方法
print(cp.cal_price())
# output:
66.75
2039.1499999999999
b = Book("Python基础教程", 89)
b.discount = 0.75
# 创建Book对象的cal_price()方法
print(b.cal_price())
cp = CellPhone(2399)
cp.discount = 0.85
# 创建CellPhone对象的cal_price()方法
print(cp.cal_price())
# output:
66.75
2039.1499999999999
从上面的运行结果来看,通过使用元类可以动态修改程序中的一批类,对它们集中进行某种修改。这个功能在开发一些基础性框架时非常有用,程序可以通过使用元类为某一批需要具有通用功能的类添加方法。