仅为个人查阅使用,如有错误还请指正。

面向对象编程就是一种程序设计思想。把对象当作程序的基本单元,一个对象包含了数据和操作数据的函数

这种设计思想是从自然界中来的。因为在自然界中,类(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())

从一个调用者来看,我只要在创建实例的时候输入nameage。并且调用该方法,获得我想要的。具体类里面怎么实现的,我不需要去管。

  • 访问限制
    在前面的类和实例中,我们了解到在类的内部,可以有属性和方法。并且外部代码还可以自由修改一个实例的属性。这样其实是不安全的,我们应该要避免这样的操作。也就是要把这变量从公有变到私有。
  • 属性私有化
    在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

属性被私有化之后,就不能在获取了。

  • 获取,修改私有化属性
    其实很简单,就是通过getset方法。现在给类中添加get_nameget_age方法,以及set_nameset_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
    我们在前面提到了访问限制。关于变量的关系。我们使用了getset的方法。看上去有点复杂,没有直接用属性这么方便。
    那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

从上面的运行结果来看,通过使用元类可以动态修改程序中的一批类,对它们集中进行某种修改。这个功能在开发一些基础性框架时非常有用,程序可以通过使用元类为某一批需要具有通用功能的类添加方法。