python面对对象的编程语言_程序语言 -- Python面向对象

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

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

目录

... 属性

... 方法

封装

类和实例

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()

# 输出: __main__.A

print type(A), a.__class__, type(a)

# 输出:

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>

new...

init... <__main__.singleton object at>

2 2

linhl linhl

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

__str__(self) 和 __repr__(self) :

>>> class A(object):

pass

>>> a = A()

>>> print a

>>> a

如上直接打印的实例很不好看,我们可以通过实现__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())

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值