1. 类和对象
类(class)是用来描述具有相同属性(attribute)和方法(method)的对象的集合,对象(object)是类(class)的具体实例。比如学生都有名字和分数,他们有着共同的属性。这时我们就可以设计一个学生类, 用于记录学生的名字和分数,并自定义方法打印出他们的名字和方法。
class Student(object):
def __init__(self,name,age,score):
self._name = name
self._age = age
self._score = score
def show(self):
print("{}的年龄是{}岁,分数是{}分".format(self._name,self._age,self._score))
aStudent = Student("瑞雯",18,99)
- 属性:类里面用于描述所有对象共同特征的变量或数据。如
_name
、_age
、_score
- 方法:类里面的函数,用来区别类外面的函数, 用来实现某些功能。如
show()
- 对象(实例):根据类创建出来的,如
aStudent
2. 属性
2.1 类属性
定义在类中函数体外的属性,属于类共有属性
class A(object):
count = 0
def __init__(self):
# 类中只有通过类名.类属性访问,self.__class__返回的就是类名
self.__class__.count += 1 # 或 A.count += 1
a = A()
b = A()
# 类外既可以通过类来访问,也可通过类的实例来访问
print(a.count) # 2
print(A.count) # 2
要修改类属性必须通过
类名.属性
的方式来修改,而不能通过实例,因为实例.属性
这种方式其实是创建了同名的实例属性,屏蔽了类的属性,通过del 实例.属性
操作后,会发现实例.属性
还是之前的。
2.2 实例属性
定义在__init__()
方法中,属于对象
class B(object):
def __init__(self,name,age):
self.name = name
self.age = age
a = B("易",18)
b = B("金克斯",8)
一般来说,对象的属性应该设为private,不能被外界直接访问,这里暂且不管访问权限问题。当能从外界直接访问时,必须通过
对象.属性
来访问,这时候属性是属于对象实例的。
3. 访问控制
在Python中并没有public,protected,private这样的关键字,所以无法实现数据封装,只能从语法上来定义可见性,依靠程序员自觉遵守规约。
在Python中,不存在函数的重载,因为函数名和普通变量名一样,都是引用,指向一个对象,是一对一的关系,都是标识符。Python中就是通过标识符的命名来区分访问的可见性。
-
字符开头的标识符,如:
age
这种标识符相当于public,可以通过对象.属性
或者对象.方法
来执行。 -
双下划线开头结尾的标识符,如:
__init__
用户最好不要自定义这种类型的标识符,因为这通常是系统调用。 -
单下划线开头的标识符,如:
_age
这种标识符相当于protected,不过Python中没有protected的概念,所以被视为private,但是,你可以按照public用,属于推荐private但是可以public的。 -
双下划线开头的标识符,如:
name
相当于private,Python通过更改标识符名(改为_类名__标识符
)来实现无法访问的机制。
4. 方法
4.1 实例方法
class Test(object):
def __init__(self,name,age):
self.__name = name
self.__age = age
def grow(self,growth):
self.__age += growth
只能被对象实例调用,第一个参数必须是self,它指向调用这个方法的实例。
4.2 类方法
class Test(object):
def __init__(self,name,age):
self.__name = name
self.__age = age
@classmethod
def getInstance(cls):
return cls("薇恩",16)
绑定到类的方法,必须在定义函数的上方使用 @classmethod 装饰器,同时,第一个参数必须是 cls,这个 cls 代表这个类的类型,如上面例子中的Test,所以在类方法内部可以用 cls(参数) 创建对象,也可以用它调用属于类的属性、其他可使用的方法。对外,类方法可以通过类调用,也可以通过实例对象来调用类方法。
4.3 静态方法
class Test(object):
def __init__(self,name,age):
self.__name = name
self.__age = age
@staticmethod
def static_func():
print("正在调用静态方法")
静态方法不像前两个有特定参数,比较自由,即使你强行把第一个参数名写出self和cls,也不会有相应的作用。静态方法可以通过类或者实例对象调用。
4.4 property方法
内置函数property()
是用来使类的属性能像Java Bean那样操作的函数,它的函数定义为:
property(fget=None, fset=None, fdel=None, doc=None)
- fget: function to be used for getting an attribute value
- fset: function to be used for setting an attribute value
- fdel: function to be used for deleting an attribute
- doc: docstring
class C(object):
def getx(self): return self._x
def setx(self, value): self._x = value + 1
def delx(self): del self._x
x = property(getx, setx, delx)
c = C()
c.x = 5
print(c.x) # 6
del c.x
# print(c.x) AttributeError: 'C' object has no attribute '_x'
这样,既能使对象能简单调用属性,也能保证数据的封装,可是,这么写太麻烦,普遍用的形式是使用装饰器来实现Bean:
class Hero(object):
def __init__(self,name):
self.__name = name
@property
def name(self):
print("getter方法...")
return self.__name
@name.setter
def name(self,value):
print("setter方法...")
self.__name = value
@name.deleter
def name(self):
print("deleter方法...")
del self.__name
p = Hero("蝙蝠侠")
p.name # getter方法...
p.name = "游城十代" # setter方法...
del p.name # deleter方法...
一般@property只用于私有属性“公有化”,并且getter方法和deleter方法只能有self参数。
5. 类的特殊成员
5.1 __doc__
表示类的描述信息
class Foo(object):
'''Foo的描述'''
print(Foo.__doc__) # Foo的描述
5.2 __module__
和__class__
__module__
表示当前操作的对象在那个模块__class__
表示当前操作的对象的类是什么,也就是谁创建了这个类,metaclass
还是type
class Foo(object):
pass
print(Foo.__module__) # __main__
print(Foo.__class__) # <class 'type'>
5.3 __all__
__all__
是一个字符串list,用来定义模块中对于from XXX import *
时要对外导出的符号,即要暴露的借口,但它只对import *
起作用,对from XXX import XXX
不起作用
__all__ = ['MNIST', 'FashionMNIST', 'CIFAR10', 'CIFAR100',
'ImageRecordDataset', 'ImageFolderDataset']
5.4 __init__
构造方法,创建对象时自动执行的初始化函数
class Foo(object):
def __init__(self):
print("init方法...")
f = Foo() # init方法...
5.5 __del__
- 析构方法,当对象在内存中被释放时,自动触发执行
- 此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的
class Foo:
def__del__(self):
pass
5.6 __call__
- 对象后面加括号,触发执行
- 构造方法的执行是由创建对象触发的,即:
对象 = 类名()
;而对于__call__
方法的执行是由对象后加括号触发的,即:对象()
或者类()()
class Demo(object):
def __init__(self):
print("执行init...")
def __call__(self, *args, **kwargs):
print("执行call...")
d = Demo() # 执行init...
d() # 执行call...
5.7 __dict__
- 返回类或对象中的所有成员
- 普通字段属于对象,静态字段和方法属于类
class Province(object):
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print('func')
print(Province.__dict__) # 返回类的所有成员
'''
{'__module__': '__main__', 'country': 'China',
'__init__': <function Province.__init__ at 0x0000022A0C5278C8>,
'func': <function Province.func at 0x0000022A0C527950>,
'__dict__': <attribute '__dict__' of 'Province' objects>,
'__weakref__': <attribute '__weakref__' of 'Province' objects>,
'__doc__': None}
'''
print(Province("卡兹克",1).__dict__) # 返回对象的成员
'''
{'name': '卡兹克', 'count': 1}
'''
5.8 __str__
和__repr__
- 都是更改类的显示方式
__str__
是给用户看到的字符串,__repr__
是给开发者看的,比如debug时,变量列表显示的是__repr__
函数返回的内容- 一般情况下只用写一个
__str__
,然后__repr__ = __str__
class Text(object):
def __init__(self,text):
self.__text = text
def __str__(self):
return self.__text
__repr__ = __str__ # 一般都这么写偷懒
t = Text("无极剑圣")
print(t) # 无极剑圣
5.9 __getitem__
、__setitem__
和__delitem__
- 用于索引操作,如字典。以上分别表示获取、设置、删除数据
__getitem__
也可以传入切片
class Subject(object):
def __getitem__(self, item): # 根据item返回一个值
print("调用getitem...")
def __setitem__(self, key, value): # 设置key对应的值为value
print("调用setitem...")
def __delitem__(self, key): # 删除这组键值对
print("调用delitem...")
m = Subject()
n = m['math'] # 调用getitem...
m['math'] = 100 # 调用setitem...
del m['math'] # 调用delitem...
5.10 __iter__
和__next__
用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了__iter__
和__next__
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def next(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration();
return self.a # 返回下一个值
5.11 __slots__
一般情况下,可以任意地给对象添加属性,这会造成很大的漏洞,影响程序安全,为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class实例能添加的属性
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
s = Student()
s.name = 'Michael'
s.age = 25
s.score = 99 # AttributeError: 'Student' object has no attribute 'score'
显然,这时候不能添加限制以外的属性,但要注意,
__slots__
定义的属性仅对当前类实例起作用,对继承的子类的属性不做限制,除非在子类中也定义__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
其他
例如各种重载运算符的函数,需要用到Google即可
6. 创建对象
6.1 传统创建类
class Demo(object):
def __init__(self,name):
self.__name = name
d = Demo("乐芙兰")
d是通过Demo类实例化的对象,其实不仅d是一个对象,Demo本身也是一个对象,因为在Python中,一切皆对象,d是通过执行Demo类的构造方法创建,那么Demo也应该是通过某个类的构造方法创建。
print(type(d)) # <class '__main__.Demo'> 表示d由Demo类创建
print(type(Demo)) # <class 'type'> 表示Demo由type类创建
所以,d对象是Demo类的一个实例,Demo类对象是 type 类的一个实例,即:Demo类对象是通过type类的构造方法创建。
6.2 type创建类
- 语法:
type('类名',父类的元组,成员字典)
def init(self,name):
self.__name = name
def show(self):
print(self.__name)
Demo = type('Demo',(object,),{'__init__':init,'output':show,'a':3})
d = Demo("诡术妖姬")
print(d) # <__main__.Demo object at 0x0000017F3C0F32E8>
d.output() # 诡术妖姬
- 一般用类名同名的变量来接受创建的类
- 父类只有object时,注意元组单元素时的逗号,成员字典中,成员名是字符串,对应的值可以是方法地址,可以是属性值
init
,show
这些方法可以在前面加 @classmethod 或者 @staticmethod 等,来定义类函数和静态函数
6.3 __new__
方法
__new__
方法是类自带的一个方法,可以重写,__new__
方法在实例化的时候也会执行,并且先于__init__
方法之前执行,简单理解,创建对象和初始化对象。
class Foo(object):
def __init__(self, name):
self.name = name
print("Foo __init__")
def __new__(cls, *args, **kwargs):
print("Foo __new__", cls, *args, **kwargs)
return object.__new__(cls)
f = Foo("凯特琳")
"""
Foo __new__ <class '__main__.Foo'> 凯特琳
Foo __init__
"""
6.4 重写__new__
方法
- 重写时,必须要调用父类的
_new__
方法,不然会覆盖父类的__new__
方法,实例创建不了 __new__()
方法创建出该类的实例,然后返回该实例给__init__()
方法调用。__init__()
方法的self
就是__init__()
方法创建返回的- 依照Python官方文档的说法,
__init__()
方法主要是当你继承一些不可变的class时(比如int
,str
,tuple
), 提供给你一个自定义这些类的实例化过程的途径,还有就是实现自定义的metaclas
等
继承不可变对象的例子
假如我们需要一个永远都是正数的整数类型,通过继承int
class PositiveInteger(int):
def __new__(cls, value):
return super(PositiveInteger, cls).__new__(cls, abs(value))
i = PositiveInteger(-3)
print(i) # 3
这时候可能有的人会通过
__init__
方法来使-3
变为3
,但是,因为继承于int
,是不可变的,__new__
创建完时,值已经确定了,再通过__init__
修改是不行的。
__new__
方法与__init__
方法的关系
class A(object):
def __init__(self):
print(self)
print("这是__init__()方法...")
def __new__(cls):
print(id(cls))
print("这是__new__()方法...")
ret = super().__new__(cls)
print(ret)
return ret
print(id(A))
a = A()
'''
输出:
2151279654824
2151279654824
这是__new__()方法...
<__main__.A object at 0x000001F4E4406FD0>
<__main__.A object at 0x000001F4E4406FD0>
这是__init__()方法...
'''
单例模式
class Number(object):
__instance = None
def __new__(cls, val):
if cls.__instance is None:
cls.__instance = super().__new__(cls)
cls.__instance.__value = val
return cls.__instance
else:
return cls.__instance
def __str__(self):
return str(self.__value)
m = Number(-3)
n = Number(-6)
print(m) # -3
print(n) # -3
print(id(m)) # 2199891453768
print(id(n)) # 2199891453768
metaclass
-
metaclass
,直译为元类,简单的解释就是,当我们定义了类以后,就可以根据这个类创建出实例,所以,先定义类,然后创建实例。但是如果我们想创建出类呢?那就必须根据metaclass
创建出类,所以,先定义metaclass
,然后创建类,连接起来就是:先定义metaclass
,就可以创建类,最后创建实例。所以,metaclass
允许你创建类或者修改类。换句话说,你可以把类看成是metaclass
创建出来的“实例”。 -
metaclass
是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass
的情况,所以,以下内容看不懂也没关系,因为基本上你不会用到
我们先看一个简单的例子,这个metaclass
可以给我们自定义的MyList
增加一个add
方法。定义ListMetaclass
,按照默认习惯,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)
class MyList(list,metaclass=ListMetaclass):
pass
当我们写下metaclass = ListMetaclass
语句时,魔术就生效了,它指示Python解释器在创建MyList
时,要通过ListMetaclass.__new__()
来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义
测试一下MyList
是否可以调用add()
方法:
l = MyList()
l.add(1)
print(l) # [1]
元类中,最好只定义__new__()方法,因为其他类如果使用了“
metaclass = XXXMetaclass
”,只会调用这个metaclass
的__new__()
方法来创建类,也就是避免了直接创建类,在创建类之前还封装了一组操作,但是,metaclas
中其他的方法、属性等,是不会让使用metaclass
创建的类继承的,所以一般在元类中,只写__new__()
方法。metaclass
可以隐式继承到子类