ShannonChenCHN / APythonTour

Yes, beautiful is better than ugly.
MIT License
0 stars 2 forks source link

4.2 Python 高级编程之面向对象高级编程 #9

Open ShannonChenCHN opened 6 years ago

ShannonChenCHN commented 6 years ago

日期:2018.07.13 周五


日期:2018.07.16 周一

参考

ShannonChenCHN commented 6 years ago

动态绑定(Dynamic Binding)

Python 是一门动态语言,创建了一个 class 的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。

给对象绑定属性和方法

class Student(object):
    pass

# 创建一个实例对象
s = Student()

# 动态给实例绑定一个属性
s.name = 'Michael' 
print(s.name)    # Michael

# 定义一个函数作为实例方法
def set_age(self, age):   
     self.age = age

# 给实例绑定一个方法
from types import MethodType
s.set_age = MethodType(set_age, s) 

s.set_age(25) # 调用实例方法
print(s.age) # 输出25

# 如果不将函数转成 MethodType 类型的话,这个方法内部的 self 就需要显式传入了
# s.set_age = set_age
# s.set_age(s, 25)
# print(s.age) # 输出25

值得注意的是,给一个实例绑定的方法,对另一个实例是不起作用的。如果需要给一个类的所有的实例动态绑定方法/属性,可以通过给 class 绑定方法/属性来实现:

def set_score(self, score):
     self.score = score

Student.set_score = set_score

s.set_score(100)
s.score  # 输出 100
ShannonChenCHN commented 6 years ago

__slot__

Python允许在定义class的时候,定义一个特殊的 __slots__ 变量,来限制该class实例能添加的属性。

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

s = Student() # 创建新的实例
s.name = 'Michael' # 绑定属性'name'
s.age = 25 # 绑定属性'age'
s.score = 99 # 绑定属性'score',这里会报错

值得注意的是,__slots__ 定义的属性仅对当前类实例起作用,对子类实例是不起作用的。除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的 __slots__ 加上父类的 __slots__

class HighSchoolStu(Student):
    pass

hs = HighSchoolStu()
hs.sex = 'M'  # __slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的

class GraduateStudent(Student):
    __slots__ = ('sex') # 如果子类中也定义了 __slots__,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__
    pass

g = GraduateStudent()
g.sex = 'F'
# g.city = 'Shanghai'  # 父类和子类的 __slot__ 中均没有声明 city 属性,所以会报错
ShannonChenCHN commented 6 years ago

@property

跟 Objective-C、Java 等静态语言类似,在 Python 中,我们可以直接访问或者修改一个对象公开的“实例变量”,但是出于不想破坏对象的封装性、校验数据等目的,我们需要通过 getter 和 setter 来访问和修改“实例变量”,这里的 getter 和 setter 也就是属性 property。

Python 内置的 @property 装饰器可以把一个 getter/setter 方法变成属性的调用。详见示例代码。

把一个 getter 方法变成属性,只需要加上 @property 就可以了。此时,@property 本身又创建了另一个装饰器 @<ivarname>.setter,如果我们在自定义的 setter 方法前添加这个 setter 装饰器,装饰器就会把这个 setter 方法变成属性赋值。于是,我们就拥有一个可控的属性操作。

只读属性

如果只为一个属性定义 getter 方法,而不定义 setter 方法,这个属性就是只读的。

示例代码

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

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2015 - self._birth

s = Student()
s.score = 60 # OK,实际转化为s.set_score(60)
print(s.score)
s.age = 10 # 这里实际调用的是 age() 方法,但是因为 age 属性是只读的,所以这里会报错

参考

ShannonChenCHN commented 6 years ago

多继承

多继承

面向对象编程中的多重继承(英语:multiple inheritance,缩写:MI)指的是一个类可以同时从多个父类继承行为与特征的功能。与单一继承相对,单一继承指一个类别只可以继承自一个父类。

class Animal(object):
    pass

# 大类:
class Mammal(Animal):
    pass

class Bird(Animal):
    pass

class RunnableMixIn(object):
    def run(self):
        print('Running...')

class FlyableMixIn(object):
    def fly(self):
        print('Flying...')

# 各种动物:
class Dog(Mammal, RunnableMixIn):
    pass

class Bat(Mammal, FlyableMixIn):
    pass

class Parrot(Bird, FlyableMixIn):
    pass

class Ostrich(Bird, FlyableMixIn):
    pass

MixIn 设计

在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich 继承自“主类” Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让 Ostrich 除了继承自“主类” Bird 外,再同时继承“副类” RunnableMixIn。这种设计通常称之为 “MixIn”。

MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。

Python自带的很多库也使用了MixIn。

由于 Python 允许使用多重继承,因此,MixIn就是一种常见的设计。只允许单一继承的语言(如Java、Objective-C)不能使用MixIn的设计。

注:在很多不支持多继承的语言中,比如 Java 和 Objective-C,虽然不支持“实现多继承”,但是提供了“接口多继承”,通过定义协议或者接口,这样一个类除了继承一个父类之外,还可以继承一些接口。

参考:

ShannonChenCHN commented 6 years ago

定制类

Python 的 class 允许定义许多定制方法,可以让我们非常方便地生成特定的类。 下面是一些最常用的定制方法(如需了解更多可定制的方法,请参考Python的官方文档)。

示例代码见这里

ShannonChenCHN commented 6 years ago

枚举类

Python提供了 Enum 类来实现枚举功能。枚举类会自动赋给每个成员一个 int 类型的value 属性作为常量,默认从 1 开始计数。

使用枚举类的有两种方式:

直接使用 Enum 类构造一个枚举对象

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

print(Month.Jan, Month.Jan.value)

for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

从Enum派生出自定义类

from enum import Enum, unique

@unique  # `@unique` 装饰器可以帮助我们检查保证没有重复值
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

获取枚举值

既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。

>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(Weekday(1))
Weekday.Mon
ShannonChenCHN commented 6 years ago

元类(Meta Class)

元类,即类的类,元类的概念在 Objective-C 中也出现过。

1. 使用 type() 函数动态定义一个类

通过 type() 函数,我们可以查看一个类型或变量的类型,一个对象的类型是它所属的类,而一个类的类型是 type

type() 函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过 type() 函数创建出 Hello 类,而无需通过 class Hello(object)... 的定义。

原理:通过 type() 函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

延伸:正常情况下,我们都用class Xxx...来定义类,但是,type() 函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。

2. 使用元类控制类的创建

通过使用元类我们可以控制类的创建过程,比如在创建对象时,动态修改、添加一些属性、方法。

2.1 使用方法

定义元类,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:

# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass:

class MyList(list, metaclass=ListMetaclass):
    pass

2.2 原理

当我们传入关键字参数 metaclass时,魔术就生效了,它指示Python解释器在创建 MyList 类时,要通过ListMetaclass.__new__() 来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。

参考

ShannonChenCHN commented 6 years ago

@classmethod@staticmethod

@classmethod

@classmethod 是一个函数装饰器,通过 @classmethod 关键字可以将一个普通方法转成类方法,类方法的第一个参数是隐式参数 class,就像实例方法的第一个参数是实例一样。

跟 Objective-C 中的类方法不同的是,Python 中的类方法既可以通过类调用也可以通过对象调用。

声明实例方法:

class C:
    @classmethod
    def f(cls, arg1, arg2, ...): ...

@staticmethod

跟类方法类似,@staticmethod 是一个函数装饰器,通过 @staticmethod 关键字可以将一个普通方法转成静态方法。跟类方法不同的是,静态方法的没有隐式参数 class,完全可以当成一个普通函数来使用。

静态方法既可以通过类调用也可以通过对象调用。

声明静态方法:

class C:
    @staticmethod
    def f(arg1, arg2, ...): ...

小结:类方法和静态方法的相似之处在于,既可以通过类调用也可以通过对象调用。 不同点在于,类方法的第一个参数是隐式参数 class,而静态方法的参数没有要求。

示例代码:


class Kls(object):
    def __init__(self, data):
        self.data = data
    def printd(self):
        print(self.data)
    @staticmethod
    def smethod(*arg):
        print('Static:', arg)
    @classmethod
    def cmethod(*arg):
        print('Class:', arg)

>>> ik = Kls(23)
>>> ik.printd()
23
>>> ik.smethod()
Static: ()
>>> ik.cmethod()
Class: (<class '__main__.Kls'>,)
>>> Kls.printd()
TypeError: unbound method printd() must be called with Kls instance as first argument (got nothing instead)
>>> Kls.smethod()
Static: ()
>>> Kls.cmethod()
Class: (<class '__main__.Kls'>,)

参考