Haste makes waste

Python-Liao-04-面向对象(1)

Posted on By lijun

[Python-Liao-XX…]系列,系列根据廖雪峰的python3初级教程学习整理。

36. 面向对象编程

  • 面向过程方式
lijun = {"name":"lijun","score":34}
wangling = {"name":"wangling","score":30}
def print_score(std):
    print("%s:%s" % (std["name"],std["score"]))
print_score(lijun)
lijun:34
  • 面向对象方式
class Student(object):
    def __init__(self,name,score):
        self.name = name
        self.score = score
    
    def print_score(self):
        print("%s:%s" % (self.name,self.score))
lijun = Student("lijun",34)
wangling = Student("wangling",30)
lijun.print_score()
lijun:34
  • 小结:

数据封装,继承和多态,是面向对象的主要特征,python中的这些概念应该与java或是或是C++中相同。 从上面的简单示例可以看出面向对象的一些特点:

  1. 将属性和行为抽象成了一个class,通过class可以生成具有相同属性和行为的对象。
  2. 对象执行自身的行为函数,实现特定的功能。

37. 类和实例

  • 如下是对象在内存的地址
wangling
<__main__.Student at 0x3e8f3b0>
lijun
<__main__.Student at 0x3e8f350>
  • Student是个类
Student
__main__.Student

本章介绍了类和实例的基本概念,与其他语言相通,省略。

38. 访问限制

上面的Student类中,有属性 name 和 score,都是公有属性,即可以通过对象直接访问该属性,如下可以直接修改。

lijun.score = 18

但是这样是非常不安全的,通过在属性前面添加__可以使变量变成私有属性,如下:

class Student2(object):
    def __init__(self,name,score):
        self.__name = name
        self.__score = score
    
    def print_score(self):
        print("%s:%s" % (self.__name,self.__score))
lijun2 = Student2("lijun",34)
wangling2 = Student2("wangling",30)

添加了上面私有属性标识后,就无法直接通过对象访问该变量了,会出现如下错误:

lijun2.score
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-14-7fe4493c4cd9> in <module>()
----> 1 lijun2.score


AttributeError: 'Student2' object has no attribute 'score'

那如果我想修改和访问该变量的话,该怎么办呢?这里的解决方法就是添加setter和getter,如下:

def set_score(self,score):
  self.__score = score

def get_score(self):
  return self.__score

看了上面的例子,可能觉得这是多此一举,但是通过setter和getter可以对对象的属性进行预处理,比如设定score的时候,能先检查score是否在有效范围:

class Student(object): 
    def set_score(self, score): 
        if 0 <= score <= 100: 
            self.__score = score 
        else: 
            raise ValueError('bad score') 

39. 继承和多态

1.继承

class Animal(object):
    def run(self):
        print("Animal is running!")

class Dog(Animal):
    def wangwang(self):
        pass
    def run(self):
        print("Dog is running!")


class Cat(Animal):
    def run(self):
        print("Cat is running!")

utane = Cat()

utasuke = Dog()

utane.run()
utasuke.run()
Cat is running!
Dog is running!

上面的类中,Animal是基类或父类,Dog和Cat继承于它,是其子类,子类中重写了run方法,所以子类的对象在调用run方法时,使用的是被重写后的run方法。

如果Dog类中没有run方法的话,则Dog的对象会调用父类Animal中的run方法。

2.多态

2.1 进入多态之前,先看看数据类型。

  • 通过子类Cat和Dog创建的对象,其类型都是对应子类
isinstance(utane,Cat)
True
isinstance(utasuke,Dog)
True
  • 因为子类Cat和Dog继承于Animal,那么子类的对象,同时也是父类的类型
isinstance(utane,Animal)
True
  • 反之父类的对象,不能是子类的类型
animalA = Animal()
isinstance(animalA,Animal)
True
isinstance(animalA,Dog)
False

2.2 多态的用处

  • 定义一个功能函数
def runtwice(animal):
    animal.run()
    animal.run()
runtwice(Animal())
Animal is running!
Animal is running!
runtwice(Dog())
Dog is running!
Dog is running!
runtwice(Cat())
Cat is running!
Cat is running!
class Monkey(Animal):
    def run(self):
        print("Monkey is running!")
runtwice(Monkey())
Monkey is running!
Monkey is running!

对于runtwice方法,只需要知道接收的参数是Animal类型,无需知道其具体的子类型,在运行的时候确定该对象的确切类型。这就是多态的真正威力,即调用方只管调用,不管细节。 比如,新增Animal的子类Monkey,但是不需要修改依赖Animal类的方法runtwice。

比如,有一个电器启动方法,传入参数是电器对象,方法中的处理分别是开启电源,调整显示,…等一系列操作的处理函数。 但是各个具体的电器的电源开启方法,以及显示调整方法是不同的,这些实现的细节在子类中完成,这个电器启动方法相当于接口,只管调用。 如果新增了一类电器,其启动流程符合这个接口函数,那么该新增电器也可以使用这个接口。

对多态的理解还很浅显,在下一阶段阅读python进阶 的时候再深入。

3. 静态语言与动态语言

上面的runTwice方法接收Animal类型的参数,调用Animal类或子类的run方法。

  • 在动态语言中,编译时会有类型检查,传入的对象必须是Animal类或其子类,否则无法调用run方法。

  • 在静态语言中,传入的对象并不一定要求它是Animal类或子类,只需要其类有run方法就可以调用。

40. 获取对象信息

使用type()

type(123)
int
type("str")
str
type(None)
NoneType
type(abs)
builtin_function_or_method
type(animalA)
__main__.Animal
  • 判断函数,可以使用types模块中定义的常量
import types
def fn():
    pass
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

使用instance()

class DogA(Dog):
    def wangwangA(self):
        pass
    def run(self):
        print("DogA is running!")
a = Animal()
b = Dog()
c = DogA()
isinstance(c,DogA)
True
isinstance(c,Dog)
True
isinstance(c,Animal)
True
isinstance(b,Dog) and isinstance(b,Animal)
True
isinstance(b,DogA)
False

同样,普通数据类型,也可以使用isinstance判断

isinstance([1,2,3],(list,tuple))
True
isinstance((1,2,3),(list,tuple))
True

使用dir()

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

dir("abc")
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rmod__',
 '__rmul__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'capitalize',
 'casefold',
 'center',
 'count',
 'encode',
 'endswith',
 'expandtabs',
 'find',
 'format',
 'format_map',
 'index',
 'isalnum',
 'isalpha',
 'isdecimal',
 'isdigit',
 'isidentifier',
 'islower',
 'isnumeric',
 'isprintable',
 'isspace',
 'istitle',
 'isupper',
 'join',
 'ljust',
 'lower',
 'lstrip',
 'maketrans',
 'partition',
 'replace',
 'rfind',
 'rindex',
 'rjust',
 'rpartition',
 'rsplit',
 'rstrip',
 'split',
 'splitlines',
 'startswith',
 'strip',
 'swapcase',
 'title',
 'translate',
 'upper',
 'zfill']

类似__xxx__的属性和方法在 Python 中都是有特殊用途的,比如__len__ 方法返回长度。在 Python 中,如果你调用 len()函数试图获取一个对象的长度,实际上,在 len()函数内部,它自动去调用该对象的__len__() 方法,所以,下面的代码是等价的:

len("abc")
3
"abc".__len__()
3

我们自己写的类,如果也想用 len(myObj)的话,就自己写一个__len__() 方法

class DogB(Dog):
    def __init__(self,age):
        self.age = age
        
    def wangwangB(self):
        pass
    
    def __len__(self):
        print("DogB len")
        return 999
    
    def run(self):
        print("DogA is running!")
len(DogB())
DogB len





999

关于getattr()、setattr()以及 hasattr()

dogb = DogB(2)
hasattr(dogb,"age")
True
setattr(dogb,"age",20)
getattr(dogb,"age")
20

如果试图获取不存在的属性,会抛出AttributeError 的错误:

getattr(dogb, 'z')
AttributeError                            Traceback (most recent call last)

<ipython-input-109-844d9bbaa80b> in <module>()
----> 1 getattr(dogb, 'z')


AttributeError: 'DogB' object has no attribute 'z'
getattr(dogb, 'z', 404) 
404

获取对象的方法,与上面获取对象的属性的方式一致。

小结

如下是一个实际代码中使用hasasttr()的实例:

def readImage(fp):
    if hasattr(fp,"read"):
        return readData(fp)
    return None

假设我们希望从文件流fp中读取对象,首先要判断fp对象是否有read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。 这时能使用hasattr进行判断。

另外,在python这类动态语言中,根据鸭子类型,有read()方法,不代表fp对象就是一个文件流,也可能是网络流,也可能是内存中的一个字节流,但只要read方法返回的是有效的图像数据就不影响图像的功能。

如同上面的例子,先判断这个电器对象是否有poweron方法,有的话就调用启动方法。