python class
前几天想定制一下博客的模板,博客使用的pelican
,使用的主题是bootstrap
,模板中有个传入的字段,怎么找也没找到。干脆把pelican
代码读了一遍。最后发现是settings
,比较尴尬...。但通过阅读pelican
还是有一些收获的,如果今天要总结的 python class / object。
类、对象
对象借鉴了现实世界由一个个客体组成的概念,用一个个对象间的互动来组织起程序,跟现实世界的客体类似,对象有自己的特征(对象里的各种值),对象也有自己能够做到的事(通过对象里的各种方法)。对象里的各种值被叫做对象的字段(field) ,对象里的各种方法被叫做对象的方法(method) ,对象的字段跟方法统称为对象的属性(attribute) 。 如何实现对象呢?有一种方法就是通过类(class)来实现。程序中的类(class) 就是基于像这样的一般概念而抽象出来的某一类客体的模板,可以是人的类,苹果的类,猫的类。从类(模板)中可以构造出这一类客体的对象。从类到对象,相当于从蓝图中实现了一个对象,所以可以说某对象是某个类的一个实例(实现了的例子)。反过来,某个类规定了将要实现的对象的该有的属性跟方法,跟别的类实现的对象有了区别,所以对象的类型(type) 就是它所承袭的类。
创建对象
python中内置的所有数据类型都是对象,拥有自己的方法。那么当这些内置的数据类型无法满足我们的需求时,我们如何创建我们自己的类型(type)呢?答案就是通过创建我们自己的类(class)。通过我们自己动手实现的类,我们就可以创建以这个类为模板的对象。从这样的流程来看,面向对象的编程方式是自顶而下,首先需要全盘考虑,才能创建一个足够好的模板,也即类。然后才能将类实例化为对象,通过对象中的属性来解决问题或者与其他对象互动。
class Foo(object):
def __init__(self):
self.a = 1
上面的代码中class是关键字,表明我们要创建一个类了
从Foo类中创建一个该类的实例通过下面的写法:
"""
创建一个实例,通过类名加括号的形式,类似调用函数
"""
foo = Foo()
对象(客体)有自己的特征和自己可以做到的事,对应到程序里就是字段(field) 和方法(method) ,这两个都是对象的属性(attribute) 。对象的字段类似于普通变量,所不同的是对象的字段是对象独有的。对象的方法类似于普通函数,所不同的是对象的方法是对象独有的。上篇文章中我们已经见到过如何使用字段跟方法,那就是通过.操作符。
定义方法
在类中定义对象的方法(method)比较简单,跟实现普通函数类似,只有一点不同,那就是不管方法需不需要参数,你都需要把self作为一个参数名传进去,self这个参数在我们调用方法时我们可以直接忽略,不赋值给它。举个例子:
class Foo():
def hi(self):
print("hi!")
foo = Foo()
foo.hi
"""
程序输出:
hi!
"""
self这个参数名是约定俗成的。在Foo类的代码块里定义hi方法时,传入的参数self将会是某个实例(对象)本身。当foo作为Foo类的实例被创建,并且通过foo.hi()调用hi方法时,python解释器会自动将其转换成Foo.hi(foo)。通过传入实例(对象)本身,也即self,方法(method)就能够访问实例的字段(filed),并对其进行操作,我们之后可以从新的例子中看到。
定义字段(field)
要在类中声明对象的字段,有一个特殊的方法(method)可以做到,那就是__init__
方法,这个方法在init前后都要写上两个下划线__
。__init__
方法会在实例一开始创建的时候就被调用,init 是 initialization 的缩写,顾名思义,就是初始化的意思。__init__
方法在创建对象的时候由 python 解释器自动调用,不需要我们手动来调用。看个例子:
class User:
"""
注意self总是在括号里的最左边
"""
def __init__(self,name,age):
self.name=name
self.age=age
def hi(self):
print("hi!I'm {}".format(self.name))
u=User("li",32)
u.hi()
print(u.name+","+str(u.age))
"""
程序输出:
hi!I'm li
li,32
"""
上面的代码里,在User
类的__init__
方法中self
被传入,那么就可以通过self.name
跟self.age
来声明对象的两个字段,并将传入该方法的参数name
跟age
赋值给它们。当我们创建类型为User
的对象的时候,传入实际的参数"li"
跟32,这两个参数被 python 解释器传入__init__
方法中,"li"
对应name
,32对应age
,__init__
方法立即被调用,将实例u
的字段一一建立。
self.name=name
粗看貌似都是name
变量,但self.name
是实例的字段,专属于实例,name
是创建对象时将要传入的一个参数,将它赋值给self.name
是没有歧义的。
实例变量与类变量
事实上,字段除了独属于实例之外,跟普通变量没有什么差别,所以实例的字段也被称为实例变量。在类的定义中,与实例变量对应的还有类变量,类变量与实例变量类似,通过.操作符来访问。类变量是任何实例共享的,可以理解为是该类型所共有的特征,比如,在User类中,我们可以计算一共有多少被实例化了的用户:
class User:
"""
计算被实例化的用户数量
"""
count=0
def __init__(self,name,age):
"""每次创建一个对象,用户数量在原有基础上加1"""
User.count=User.count+1
self.name=name
self.age=age
def hi(self):
print("hi!I'm {}".format(self.name))
def die(self):
print("I'm {}, dying...".format(self.name))
User.count=User.count-1
del self
@classmethod
def print_count(cls):
print("共有{}名用户".format(cls.count))
u=User("li",32)
User.print_count()
u.hi()
u1=User("ma",30)
u1.__class__.print_count()
u1.hi()
u.die()
User.print_count()
u1.die()
User.print_count()
"""
程序输出:
共有1名用户
hi!I'm li
共有2名用户
hi!I'm ma
I'm li, dying...
共有1名用户
I'm ma, dying...
共有0名用户
"""
上面代码中的count
就是类变量,可以通过User.count
来访问。python通过@classmethod
来表明它下面定义的方法是类的方法(method),类的方法中的cls
是类本身,跟self
使用方法类似,调用类方法时可以直接忽略。类变量跟类的方法(method)都可以被称为类的成员。除了使用类似User.count
这样的方式来访问和使用之外,该类的实例还可以通过__class__
属性来访问和使用类成员,比如上面代码中的u1.__class__.print_count()
。
上面代码中定义的字段跟方法都是公开的,可以通过.操作符访问。但如果属性是以形如__name
这样以双下划线为开头的名称,则python
会自动将名称换成_classname__name
,其中classname
就是类的名称,这样,通过an_object.__name
是访问不到的。
实例方法,类方法、静态方法
Python中至少有三种比较常见的方法类型,即实例方法,类方法、静态方法。它们是如何定义的呢?如何调用的呢?它们又有何区别和作用呢?
首先,这三种方法都定义在类中。下面我先简单说一下怎么定义和调用的。(PS:实例对象的权限最大。)
- 实例方法
定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);
调用:只能由实例对象调用。
- 类方法
定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);
调用:类对象或实例对象都可以调用。
原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。另外,如果需要继承,也可以定义为类方法。
- 静态方法
定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;
调用:类对象或实例对象都可以调用。
静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。
继承
class A(object):
def __init__(self):
self.n = 10
def minus(self, m):
self.n -= m
print 'a', self.n
class B(A):
def __init__(self):
#self.n = 7
pass
def minus(self, m):
print 'b1', self.n
super(B,self).minus(m)
print 'b2', self.n
self.n -= 2
#b=B()
#b.minus(2)
#print b.n
class C(A):
def __init__(self):
#self.n = 12
pass
def minus(self, m):
super(C,self).minus(m)
self.n -= 5
print 'c1', self.n
class D(C, B):
def __init__(self):
self.n = 15
def minus(self, m):
#C.minus(self, m)
#B.minus(self, m)
super(D,self).minus(m)
print 'd1', self.n
self.n -= 2
d=D()
d.minus(2)
print d.n
print D.__mro__
- 多重继承使用super,是通过类的
__mro__
顺序执行父类的方法。 - 此例也展示了
super
的用法,python2 要指定参数,python3 不需要参数。 - 使用 super(D,self).minus(m) 和 C.minus(self, m) 得到的结果是一样的。B.minus(self, m) 结果不一样。
type, object
- object类是所有新式类的父类
- type是所有类的类
a = 1
b = "abc"
print("type a: {}".format(type(a)))
print("type int: {}".format(type(int)))
print("type b: {}".format(type(b)))
print("type str: {}".format(type(str)))
result:
type a: <class 'int'>
type int: <class 'type'>
type b: <class 'str'>
type str: <class 'type'>
在python中是一切皆对象的,类其实也是对象,首先type
生成了<class 'int'>
这个对象,<class 'int'>
又生成了1这个对象,type --> int --> 1
同样,type
生成了<class 'str'>
这个对象,<class 'type'>
又生成了"abc"
这个对象,type --> str--> “abc”
,即type -->生成类对象 -->对象
还可以看出int
, str
是类,1 和 "abc" 是他们的对象