前几天想定制一下博客的模板,博客使用的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.nameself.age来声明对象的两个字段,并将传入该方法的参数nameage赋值给它们。当我们创建类型为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__
  1. 多重继承使用super,是通过类的__mro__顺序执行父类的方法。
  2. 此例也展示了super的用法,python2 要指定参数,python3 不需要参数。
  3. 使用 super(D,self).minus(m) 和 C.minus(self, m) 得到的结果是一样的。B.minus(self, m) 结果不一样。

type, object

  1. object类是所有新式类的父类
  2. 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" 是他们的对象