Open ShannonChenCHN opened 6 years ago
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
__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 属性,所以会报错
@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 属性是只读的,所以这里会报错
面向对象编程中的多重继承(英语: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
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich
继承自“主类” Bird
。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让 Ostrich
除了继承自“主类” Bird
外,再同时继承“副类” RunnableMixIn
。这种设计通常称之为 “MixIn”。
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
Python自带的很多库也使用了MixIn。
由于 Python 允许使用多重继承,因此,MixIn就是一种常见的设计。只允许单一继承的语言(如Java、Objective-C)不能使用MixIn的设计。
注:在很多不支持多继承的语言中,比如 Java 和 Objective-C,虽然不支持“实现多继承”,但是提供了“接口多继承”,通过定义协议或者接口,这样一个类除了继承一个父类之外,还可以继承一些接口。
Python 的 class 允许定义许多定制方法,可以让我们非常方便地生成特定的类。 下面是一些最常用的定制方法(如需了解更多可定制的方法,请参考Python的官方文档)。
__slots__
:限制类的属性__len__
:可以让对象实例响应 len()
方法的调用__str__
:将对象以字符串的形式表现出来__repr__
:作用跟 __str__
类似,但是一般只用于 debug__iter__
和 __next__
:使一个对象支持 for 循环__getitem__
和 __setitem__
:使对象支持下标访问,想字典和数组一样__getattr__
:当调用类的方法或属性时,如果不存在,就会调用该方法,这样就可以把一个类的所有属性和方法调用实现动态化处理__call__
:任何类,只需要定义一个 __call__()
方法,就可以想调用函数一样直接对其实例进行调用示例代码见这里。
Python提供了 Enum 类来实现枚举功能。枚举类会自动赋给每个成员一个 int
类型的value
属性作为常量,默认从 1 开始计数。
使用枚举类的有两种方式:
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)
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
元类,即类的类,元类的概念在 Objective-C 中也出现过。
type()
函数动态定义一个类通过 type()
函数,我们可以查看一个类型或变量的类型,一个对象的类型是它所属的类,而一个类的类型是 type
。
type()
函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过 type()
函数创建出 Hello
类,而无需通过 class Hello(object)...
的定义。
原理:通过
type()
函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()
函数创建出class。延伸:正常情况下,我们都用
class Xxx...
来定义类,但是,type()
函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。
通过使用元类我们可以控制类的创建过程,比如在创建对象时,动态修改、添加一些属性、方法。
定义元类,按照默认习惯,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
当我们传入关键字参数 metaclass
时,魔术就生效了,它指示Python解释器在创建 MyList
类时,要通过ListMetaclass.__new__()
来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
@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'>,)
日期:2018.07.13 周五
@property
日期:2018.07.16 周一
参考