python面向对象编程

动态语言的定义

动态编程语言是高级程序设计语言的一个类别,在计算机科学领域已被广泛应用。它是一类 在运行时可以改变其结构的语言 :例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。动态语言目前非常具有活力。例如JavaScript便是一个动态语言,除此之外如 PHP 、 Ruby 、 Python 等也都属于动态语言,而 C 、 C++ 等语言则不属于动态语言。

运行的过程中给对象绑定(添加)属性

1
2
3
4
5
6
7
8
9
10
class Person(object):

def __init__(self,name,age):
self.name = name
self.age =age


P = Person("test1",24)
P.sex="male"
P.sex

运行的过程中给类绑定(添加)属性

1
2
P1 = Person("小丽", "25")
P1.sex

Traceback (most recent call last):
File “<pyshell#21>”, line 1, in
P1.sex
AttributeError: Person instance has no attribute ‘sex’

1
2
3
4
5
>>>> Person.sex = None
>>> P1 = Person("小丽", "25")
>>> print P1.sex
None
>>>

运行的过程中给类绑定(添加)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
>>> class Person(object):
def __init__(self, name = None, age = None):
self.name = name
self.age = age
def eat(self):
print "eat food"


>>> def run(self, speed):
print "Keeping moving, the speed is %d km/h" %speed


>>> P = Person("老王", 24)
>>> P.eat()
eat food
>>>
>>> P.run()
Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
P.run()
AttributeError: Person instance has no attribute 'run'
>>>
>>>
>>> import types
>>> Person.run = types.MethodType(run, None, Person)
>>> P.run(180)
Keeping moving, the speed is 180 km/h
>>>
>>> def testName(self):
print self.name
>>>
>>>
>>> Person.testName = types.MethodType(testName, None, Person)
>>>
>>> P.test()
老王

既然给类添加方法,是使用类名.方法名 = xxxx,那么给对象添加一个方法也是类似的对象.方法名 = xxxx

运行的过程中删除属性、方法

删除的方法:

del 对象.属性名
delattr(对象, “属性名”)
通过以上例子可以得出一个结论:相对于动态语言,静态语言具有严谨性!所以,玩动态语言的时候,小心动态的坑!

那么怎么避免这种情况呢? 请使用slots

slots

现在我们终于明白了,动态语言语句静态语言的不同

动态语言:可以在运行的过程中,修改代码 静态语言:编译时已经确定好代码,运行过程中不能修改

如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的slots变量,来限制该class实例能添加的属性:

1
2
3
4
5
6
7
8
9
10
11
>>> class Person(object):
__slots__ = ("name", "age")

>>> P = Person()
>>> P.name = "老王"
>>> P.age = 20
>>> P.score = 100
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
AttributeError: Person instance has no attribute 'score'
>>>

访问限制

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在Python中,实例的变量名如果以开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person(object):
__slots__ = ("name", "age","__feature")

def __init__(self, name, age, feature):
self.name = name
self.age = age
self.__feature = feature

def print_person(self):
print("name is {},age is {}".format(self.name, self.age))

if __name__ == '__main__':
p = Person("test", 11,"coding")
p.print_person()
p.__feature

Traceback (most recent call last):
File "/Users/zyy/Downloads/workspace/python3/learn/lang/Person.py", line 32, in <module>
p.__feature
AttributeError: 'Person' object has no attribute '__feature'
name is test,age is 11

继承和多态

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

对扩展开放:允许新增Animal子类;

对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Animal(object):
__slot__ = ("__name", "__leg")

def __init__(self, name, leg):
self.__name = name
self.__leg = leg

def get_name(self):
return self.__name

def set_name(self, name):
self.__name = name

def get_leg(self):
return self.__leg

def set_leg(self, leg):
self.__leg = leg

def run(self):
print("animal is running")


class Dog(Animal):

def run(self):
print("Dog is running")


class Cat(Animal):

def run(self):
print("Cat is running")


if __name__ == '__main__':
dog = Dog("dog", 4)
cat = Cat("cat", 3)
print(dog.get_name())
dog.run()
cat.run()
print(isinstance(dog, Animal))
print(isinstance(dog, list))

静态语言 vs 动态语言

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:

1
2
3
class Timer(object):
def run(self):
print('Start...')

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

实例属性和类属性

从上面的例子可以看出,在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

获取对象信息

  1. 使用type()首先,我们来判断对象类型,使用type()函数,基本类型都可以用type()判断;
1
2
3
4
5
6
7
8
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
  1. 对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。

  2. 如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法.

    1
    2
    >>> dir('ABC')
    ['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态: