程序语言 -- Python面向对象

TOP


Python是面向对象的,我们现在来学习与之相关的内容。

面向对象自身的概念,没接触过的同学自行google百度吧,这里就不提了。

目录

封装

... 类和实例

... 属性

... 方法

... 访问限制

... 内置属性

... 内置方法

... @property

... 动态绑定方法

继承

多态

封装

类和实例

Python类的定义:

class 类名:
	pass

支持多继承,基类跟在类名后面的小括号中,多个基类用逗号分隔:

class 类名(基类1, 基类2):
	pass

类可以有docstrings,与函数一样,建议docstrings必写。

类中可以定义 __init__ 方法,你可以认为它是初始化方法或构造函数,但需要注意的是,这个方法是在类的实例创建后立即调用的,也就是说,此方法执行时,类的实例对象已经创建好了。

类的每个实例方法(包括 __init__ 方法)的第一个参数,都是指向当前类实例的引用,参数名习惯上使用 self (建议不要使用其它名字)。方法定义的时候必须明确指定第一个参数为self,调用时不需要传递,因为Python会自动传递,但是在子类调用父类方法时,必须传递此参数。

父类的方法不会在子类方法执行前自动调用(包括 __init__ 方法),如果子类需要扩展父类的行为,需要显示调用父类的方法。

实例化一个类,写法与函数调用一致,不需要new之类的关键字,参数传递__init__ 方法定义的参数,返回新创建的类实例对象。

我们不需要特别关注垃圾回收的问题,不需要考虑什么时候明确地释放实例,因为当指派给它的变量超出作用域时,会被自动释放。具体原理,感兴趣的同学可以查一下:引用计数标记-清除

经典类,新式类

新式类从Python2.2开始引入,用于统一 class 和 type 的概念。新式类继承自object,它仅仅代表一个用户创建的类型:

class A:
	pass

class B(object):
	pass

a = A()
b = B()

# 输出:<type 'classobj'> __main__.A <type 'instance'>
print type(A), a.__class__, type(a)  
# 输出:<type 'type'> <class '__main__.B'> <class '__main__.B'>
print type(B), b.__class__, type(b)  

还有重要的一点,在多继承中,两种类使用的查找算法不同,具体见下一节继承

在python3中只保留了新式类,去除了经典类。

属性

直接在类中定义的是类属性(类变量),可在类的所有实例中共享,使用 类名. 调用;在方法中定义且以self.开头的是实例属性(实例变量),对每个实例是独立的,使用 实例名.self. 调用 。实例属性一般在__init__方法中初始化。

# -*- coding: UTF-8 -*-

class TestClass:
	'测试类,了解类变量和实例变量'

	# 类变量
	className = 'className1'

    def __init__(self,name):
        # 实例变量
        self.name = name

    def print_name(self):
        print '{0}, {1}, {2} '.format(TestClass.className, self.className, self.name)

tc = TestClass('instName1')
tc.print_name()    # 输出: className1, className1, instName1
# 修改实例变量
tc.name = 'instName2'
# 定义了一个与类变量同名的实例变量
tc.className = 'instName3'
tc.print_name()    # 输出: className1, instName3, instName2
# 修改类变量
TestClass.className = 'className2'
tc.print_name()    # 输出: className2, instName3, instName2
# 删除实例变量
del tc.className
tc.print_name()    # 输出: className2, className2, instName2

看出上面的玄机了吗?想想看下面的输出吧:

class TestClass2:
    '测试类,了解类变量和实例变量'

    # 类变量
    alist1 = []
    alist2 = []

    def __init__(self,name):
        # 实例变量
        self.alist1 = []
        self.alist1.append(name)
        self.alist2.append(name)

tc2 = TestClass2('aItem')
print tc2.alist1, TestClass2.alist1
tc2.alist1 = []
print tc2.alist1, TestClass2.alist1

print tc2.alist2, TestClass2.alist2
tc2.alist2 = []
print tc2.alist2, TestClass2.alist2

方法

类中可以定义三种方法:实例方法,类方法,静态方法。

我们前面看到的都是实例方法,第一个参数是实例对象 self(self 是约定俗成,不是关键字),必须通过实例去调用。

类方法的定义需要在方法前加 @classmethod 的标记符,第一个参数是类本身,一般写作: cls,类和实例都可以调用。

@classmethod
def test_class(cls):
    print cls

静态方法的定义需要在方法前加 @staticmethod 的标记符,没有隐含传递的参数:

@staticmethod
def test_static():
    print 'a static method'

访问限制

当属性或方法以双下划线 __ 开头时,表明是私有属性或私有方法,外部不可以访问。但有一些以双下划线 __ 开头且以双下划线 __ 结尾的属性或方法是内置的,所以我们尽量不要用前后双下划线的方式命名自己的属性方法。

内置属性

我们前面看到过的 __doc____class__ 都是类的内置属性,下面介绍另外一个常用的属性:__slots__

在前面的例子中我们注意到,创建一个类的实例后,可以随意给该实例绑定属性,如果觉得这样会不好控制,那么我们可以通过给 __slots__ 赋一个tuple,以限制实例可绑定的属性:

class Student(object):
	# 只有name和grade属性
    __slots__ = ('name','grade')

    def __init__(self,name,grade = 1):
        self.name = name
        self.grade = grade

s = Student('Lili')
s.age = 7     # 这里会报错:AttributeError: 'Student' object has no attribute 'age'

需要注意,如果子类没有定义 __slots__ ,那么还是可以绑定任意属性;一旦子类也定义了 __slots__ ,那么子类可以绑定自身的 __slots__ 以及父类的 __slots__

内置方法

__init__(self,..) 就是一个内置方法,它在实例创建后立即运行,并不需要手工调用。

__new__(cls,*args,**kwd) 可能更适合叫初始化方法,因为它是用来生成实例对象的,在 __init__ 之前执行。网上的例子一般用它来实现单例模式。

class Singleton(object):
    __instance = None
    count = 0

    def __init__(self):
        print 'init...',self
        Singleton.count = Singleton.count + 1

    def __new__(cls, *args, **kwd):
        print 'new...'
        if cls.__instance is None:
            cls.__instance = object.__new__(cls, *args, **kwd)
        return cls.__instance

s1 = Singleton()
s2 = Singleton()
print s1.count, s2.count
s1.name = 'linhl'
print s1.name, s2.name

输出结果:

new...
init... <__main__.Singleton object at 0x00000000029B0B00>
new...
init... <__main__.Singleton object at 0x00000000029B0B00>
2 2
linhl linhl

大家可以将__new__方法注释掉看看运行结果。

__str__(self)__repr__(self)

>>> class A(object):
		pass

>>> a = A()
>>> print a
<__main__.A object at 0x000000000289A860>
>>> a
<__main__.A object at 0x000000000289A860>

如上直接打印的实例很不好看,我们可以通过实现__str__(self)__repr__(self)使得实例打印得漂亮一点:

>>> class A(object):

		def __str__(self):
			return 'A Class'

		__repr__ = __str__

>>> a = A()
>>> print a      # 相当于 str(a), str()会调用__str__()
A Class          # 由 __str__() 返回
>>> a            # 相当于 repr(a), repr()会调用__repr__()
A Class          # 由 __repr__() 返回

__len__(self) 会由len()调用,返回实例的长度:

>>> class A(object):

		def __len__(self):
			return 10

>>> len(A())
10

__cmp__(stc,dst) 通过返回大于、等于或小于0的数,来比较stc和dst的大小:

>>> class A(object):
		def __init__(self,age):
			self.age = age
		def __cmp__(stc,dst):
			if stc.age == dst.age:
				return 0
			elif stc.age < dst.age:
				return -1
			else:
				return 1

		
>>> a = A(10)
>>> b = A(12)
>>> a == b
False
>>> a > b
False
>>> a < b
True

__iter__(self)next(self) 可以使得我们定义的普通的类变成一个迭代器:

class Fab(object): 
	def __init__(self, max): 
		self.max = max
		self.a, self.b = 0, 1
	 
	# 一般都是返回自身
	def __iter__(self): 
		return self
	 
	def next(self): 
		self.a, self.b = self.b, self.a + self.b 
		if self.a > self.max:
			raise StopIteration()
		return self.a

直观的,我们可以这样用这两个方法:

f = Fab(10)
fetch = iter(f)              # iter()中默认调用__iter__()
while True:
    try:
        n = fetch.next()
    except StopIteration:    # 遇到 StopIteration 异常则退出
        break
    print n	

如果每次都这么用就太麻烦了,Python给我们提供了语法糖:

f = Fab(10)
for n in f:
	print n

虽然我们可以在类里定义一个list来达到类似的目的,但当数据量大的时候,需要考虑内存问题,用如上的next则可以复用内存。

__getitem__(self,key) 使得我们可以像list一样获取指定索引位置key的元素:

class Week(object):
    def __init__(self):
        self.__weekdays = ('星期日','星期一','星期二','星期三','星期四','星期五','星期六')

    def __getitem__(self,key):
        if isinstance(key,int):
            return self.__weekdays[key]
        elif isinstance(key,slice):      # 支持切片
            return self.__weekdays[key.start:key.stop]

w = Week()
print w[5]
print w[2:4]

__getattr__(self,name), __getattribute__(self,name) 这两个方法很像,既然是两个方法,那肯定是有区别的:

class Test(object):
    def __init__(self,name):
        self.name = name
        
    def __getattr__(self, value):
        # 只有当address属性不存在时才返回 zj
        if value == 'address':
            return 'zj'
		if value == 'age':
            return 7
        raise AttributeError('Test 类没有属性 {0}'.format(value))
        
    def __getattribute__(self, value):
		# name属性始终返回 wang
        if value == 'name':
            return 'wang'
        
        if value == 'address':
            return object.__getattribute__(self, value)
        
        raise AttributeError('Test 类没有属性 {0}'.format(value))

test = Test('li')
print test.name          # 始终输出:wang
print test.address       # 输出:zj
test.address = 'sh'      # 绑定address属性
print test.address       # 输出:sh
print test.age           # 输出:7
print test.grade         # 得到AttributeError: Test 类没有属性 grade

__getattr__ 在找不到需要的属性时调用,返回属性的值,或者抛出 AttributeError 异常。__getattribute__ 只要定义了,就会无条件执行,如果同时也定义了__getattr__(如上例),__getattr__不会被调用,除非被显示调用或者__getattribute__ 引发了AttributeError

@property

给实例属性赋值时,一般会需要做一些校验。如果每次赋值都要写同样的校验逻辑,显然不符合我们的编程思想,那如果把校验逻辑提成独立的方法,调用起来又有点麻烦,不像直接用属性那么简单。可以用 @property 装饰器来帮助我们:

class Student(object):
    __slots__ = ('name','__grade')

    def __init__(self,name,grade = 1):
        self.name = name
		# 私有属性
        self.__grade = grade

	# 定义grade的get方法
    @property
    def grade(self):
        return self.__grade

	# 定义grade的set方法,如果不定义此方法则grade是只读属性
    @grade.setter 
    def grade(self, value):
        if not isinstance(value,int):
            raise ValueError('grade 必须是数字!')
        elif value < 1 or value > 6:
            raise ValueError('grade 只能在 1 ~ 6 之间!')
        else:
            self.__grade = value

s = Student('Lili')
# 像使用一般属性一样调用
s.grade = 11        # 这里会报错: ValueError: grade 只能在 1 ~ 6 之间!

动态绑定方法

使用 MethodType 可以给类动态绑定方法:

from types import MethodType

def print_student(self):
    print 'Name: {0}  Grade: {1}'.format(self.name,self.grade)

class Student(object):
    __slots__ = ('name','grade')

    def __init__(self,name,grade = 1):
        self.name = name
        self.grade = grade
		
Student.print_student = MethodType(print_student,None,Student)
s = Student('Lili')
s.print_student()      # 输出: Name: Lili  Grade: 1

继承

教程里多以Animal为例解释继承,我们也拿来主义。现在定义一个Animal类:

class Animal(object):
    def run(self):
        print 'I can run.'

    def sound(self):
        raise NotImplementedError

我们考虑的动物都可以跑,都可以发出声音,但每种动物的叫声不同,所以这个类提供了一个run方法和一个sound方法,但sound方法没有实现。下面定义一个Cat类,一个Dog类,都继承自Animal,这两个子类分别实现了自己的sound方法,同时默认地继承了run方法:

class Animal(object):
    def __init__(self,name):
        self.name = name
        
    def run(self):
        print '{0} can run.'.format(self.name)

    def sound(self):
        raise NotImplementedError

class Cat(Animal):
    def sound(self):
        print 'meow meow'

class Dog(Animal):
    def sound(self):
        print 'woof woof'


cat = Cat('Cat')
cat.run()             # 输出: Cat can run.
cat.sound()           # 输出: meow meow

dog = Dog('Dog')
dog.run()             # 输出: Dog can run.
dog.sound()           # 输出: woof woof

如果需要在子类中调用父类的方法,要用 父类.方法名() 的方式去调,同时需要传递self 参数。子类不能调用父类中的私有属性和私有方法。


多继承

传统概念中,面向对象一般是不支持多继承的,否则调用子类中某个继承自父类的方法时,是调用父类A的方法呢,还是父类B的方法呢?但Python是支持多继承的,它又是怎么处理这个问题的呢?

对于经典类,查找基于深度优先算法:

class Grandpa:
    def eyelid(self):
        print 'single-fold eyelid'

class Father(Grandpa):
    pass

class Mother(Grandpa):
    def eyelid(self):
        print 'double-fold eyelids'

class Child(Father,Mother):
    pass

c = Child()
c.eyelid()

c.eyelid() 打印的是 single-fold eyelid 。但我们知道双眼皮是显性基因,这里妈妈是双眼皮,孩子应该也是双眼皮才对,而这里的深度优先算法绕过了Mother类中实现的eyelid方法,取了Father的父类Grandpa中的eyelid方法。

如果我们把Grandpa改为新式类(继承自object),结果会怎样呢?新式类采用的是广度优先算法,所以会打印 double-fold eyelids。 有说新式类是C3算法,具体我真没看明白,不过这种写法是会造成一定理解上的混乱,能避免就避免吧。

多态

面向对象中,多态是指根据对象的类型用不同方式处理,产生不同的结果。我们通过一段java代码来理解一下:

public class Animal{
	public void sound(){
		System.out.println("Animal sound");
	}
}

public class Cat extends Animal{
	public void sound(){
		System.out.println("Cat: meow meow");
	}
}

public class Dog extends Animal{
	public void sound(){
		System.out.println("Dog: woof woof");
	}
}

public class Test{
	public static Animal getAnimal(int i){
		if(i%2 == 0)
			return new Cat();
		else
			return new Dog();
	}

	public static void main(String[] args){
		//随机获取
		Animal animal = getAnimal((new Random(100)).nextInt());
		animal.sound();
	}
}

即使没有基础,基本上也看得明白上面这段代码的意思。main中的animal可能是Cat,也可能是Dog,但不管它到底是什么,它都是一个动物,可以调用sound()方法,而在运行期就可以打印出对应的正确的语句。

Python因为是动态类型语言,定义变量的时候并不申明变量的类型,多态性表现得不像java这么直接,但它确实可以起到相同的作用。我们接着上一节举的Animal的例子写:

def print_sound(animal):
	animal.sound()

print_sound(cat)
print_sound(dog)

看下结果如何呢?有人说,这里与是否继承自Animal类没有关系,只要有sound方法就行:

class OtherClass(object):
	def sound(self):
		print 'OtherClass sound'

print_sound(OtherClass())

不要太钻牛角尖,我们的目标是实现功能,解决问题,这就足够了。

原文地址:https://www.cnblogs.com/qhlinhl/p/4598005.html