面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数。
面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
class Student(object):
__slots__=("__name" , "score")
age = 18
def __init__(self, name, score):
self.__name = name
self.score = score
def print_score(self):
print(self.__name , self.score , Student.age)
类中的函数(包括构造函数),第一个形参都指向实例自身
成员
成员的增删改查
- 通过
dir()
可以获取一个对象所有的成员的List
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
- 通过
hasattr()
、setattr()
、getattr()
判断/获取/赋值指定成员
>>> hasattr(obj, 'x')
True
>>> obj.x
9
>>> getattr(obj, 'y') # 获取属性'y' 没有则报错
>>> getattr(obj, 'y', 20) # 获取属性'y' 没有则使用默认值20
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
- 通过
del
可以删除实例的成员
del jack.score
私有成员
在类中定义的成员,如以__
开头,则为私有成员
如jack
实例的Student
类中的__name
,在类外会被解释器变为_Student__name
(不同版本解释器可能不一样)
因此外部无法访问jack.__name
,却能访问jack._Student__name
,如对jack.__name
赋值也不会覆盖jack._Student__name
既以__
开头又以__
结尾的视为特殊成员,不受此影响
以_
开头的成员一般约定为私有成员,但解释器不会对其做任何操作
特殊成员
既以__
开头又以__
结尾的成员,通常不需要人为声明
-
__len__
方法
在len()
函数内部,它自动去调用该对象的__len__()
方法,所以,下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
__slots__
__init__()
-
__class__
该对象的类型(类则为type
,实例则为其类)
可以通过self.__class__.
调用类属性 -
__str__()
、__repr__()
变量/对象被print()
时输出的字符串 -
__iter__()
、__next__()
使类可以被for...in
循环 -
__getitem__()
、__setitem__()
、__delitem__()
把对象视作list
、tuple
、dict
进行赋值/删除成员
class Test():
age = 18
def __getitem__(self, item):
return getattr(self, item)
def __setitem__(self, key, value):
setattr(self, key, value)
test = Test()
test["age"] = 88
print(test.age) # 88
print(test["age"]) # 88
-
__getattr__()
没有找到的属性可以交给__getattr__()
进行后续处理,而不报错
存在的属性不会进入__getattr__()
逻辑
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
s.age()#25
-
__call__()
使实例本身可以如同一个函数被调用
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
s = Student('Michael')
s() # self参数不要传入 My name is Michael.
通过callable()
函数,我们就可以判断一个对象是否是“可调用”对象。
类成员、实例成员、静态方法
不同于JS和C#,python当试图获取/判断实例的某个成员是否存在时,若不存在则会取得其class的同名成员,因此建议不要对实例属性和类属性使用相同的名字
该特性对hasattr
、getattr
、dir
均有效,但可以通过 <类名>.__dict__
和 <实例>.__dict__
可以获取类和实例的属性字典,以查看该属性是属于类还是实例
-
两种属性
- 类属性
直接在类中声明 - 实例属性
通常在方法中通过self.
进行声明
- 类属性
-
三种方法,实例对象和类对象都可以调用
- 实例方法
用于给实例调用的方法,其实还是在类上,可以被类的__dict__
获取
第一个参数必须是实例对象(一般约定为self
),通过它来传递实例的属性和方法; - 类方法
使用装饰器@classmethod
。第一个参数必须是当前类对象(一般约定为cls
),通过它来调用类的方法或修改类的属性 - 静态方法
使用装饰器@staticmethod
。参数随意,没有self
和cls
参数,但是方法体中不能使用类或实例的任何属性和方法,一般用于和类对象以及实例对象无关的代码;
- 实例方法
class CocaCola:
formula = ['caffeine', 'sugar', 'water', 'soda']
def drink(self):
print('Energy!')
@classmethod
def drink2(cls):
print('Energy2!')
@staticmethod
def drink3():
print('Energy3!')
coke = CocaCola()
coke.drink()
CocaCola.drink(coke)
coke.drink2()
CocaCola.drink2()
coke.drink3()
CocaCola.drink3()
限制成员内容
通过对类成员__slots__
赋值一个tuple,可以限制该class能添加的实例属性
注意__slots__
不会影响子类,除非在子类中也定义__slots__
,此时子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
限制成员读写
-
@property
装饰器规定了成员的读取,并自动创建@XXX.setter
装饰器 -
@XXX.setter
会对成员写入进行必要检查 - 当只有
@property
时为只读属性
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2015 - self._birth
继承和多态
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
- Python3中,类默认继承
object
,因此写或不写都一样 - Python2中若不写,会少获得一些方法
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
多重继承
class Dog(Mammal, Runnable):
pass
通常,用于给一个类增加额外功能的类,其类名以MixIn
结尾,这种设计也称为MixIn。
JS(TS)、C#、Python中类继承时父类构造函数的调用情况
- JS
可以显式在子类的constructor
中调用super()
(TS中为必须调用)
如果子类未声明constructor
则会在隐式定义的constructor
中调用super()
执行父类构造函数 - C#
子类静态构造函数 => 父类静态构造函数 => 父类实例构造函数 => 子类实例构造函数 - Python
通常在子类的__init__
函数中调用super().__init__()
如无调用则不会触发父类构造函数
枚举类
通过Enum
类实例化直接生成枚举
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
其中value
属性则是自动赋给成员的int常量,默认从1
开始计数。
从Enum
派生出自定义类自定义枚举
@unique
用于检查没有重复值
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
print(Weekday.Mon== Weekday["Mon"])#True
print(Weekday.Mon== Weekday(1))#True
print(Weekday.Tue.value)#2
元类
type
type
是所有类(包括自己)的类型,因此type()
函数可以查看一个类型或变量的类型,也可以创建一个新类
print(type(Hello))#<class 'type'>
依次传入类名、继承的类、类的静态成员,可用于动态创建类
def fn(self, name='world'): # 先定义函数
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>
metaclass 元类
metaclass
允许创建类或者修改类,可以把类看成是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定义类的时候需传入关键字参数metaclass:
class MyList(list, metaclass=ListMetaclass):
pass
如此,则创建MyList时,要通过ListMetaclass.__new__()
- 其中
__new__()
方法接收到的参数依次是:- 当前准备创建的类的对象;
- 类的名字;
- 类继承的父类集合;
- 类的方法集合。
- metaclass会隐式地继承到子类
网友评论