0.本周知识点预览
- 类的多态
- 面向对象中类的成员
- 字段
- 方法
- 属性
- 成员修饰符
- 类的特殊成员
- 面向对象其他方法
- 异常梳理
- 设计模式之单例模式
1.面向对象进阶
1.多态
在众多语言中,在定义函数时,有的参数类型可以随意传入,有的只能传入指定的类型。这种随意的语言的代表就是python,而规规矩矩的传入指定类型的参数的语言,就如java、c#、c++。下面就拿C++和python做个对比。
以下代码是C++代码:
liukai@bogon:~$ cat test1.cpp #include <iostream> using namespace std; int add(int x, int y) { return (x+y); } int main() { cout<<add(3,5); }
代码解析:C++这类语言的函数参数都要注明传入的类型,假如类型不对就会报错。
以下代码是python代码:
def func(arg): print(arg) func(123) func("hello") func([1,3,4]) func(dict)
执行结果如下:
123 hello [1, 3, 4] <class 'dict'>
代码解析:从上可以看到,python的函数参数可以是任意类型。这个就很随意了,不过也有一点弊端,就是当别人看你的代码的时候,会发现这个参数可以是任意类型,就会比较困惑这个参数到底是做什么的。所以,有利必有弊嘛。
2.类的成员
1.字段
1.静态字段
#静态字段 class Province: country = "中国" def __init__(self,name): # self.name = name pass def show(self): # print(self.name) print("通过类调用:",Province.country) print("类内部调用:",self.country) hlj = Province("黑龙江") hlj.show() print("对象调用:",hlj.country)
执行结果如下:
通过类调用: 中国
类内部调用: 中国
对象调用: 中国
代码解析:静态字段属于类,可以直接由类调用:类名.静态字段名;可以由对象直接调用:对象名.静态字段名;可以在类内部调用:self.静态字段名。
2.普通字段
class Province: country = "中国" def __init__(self,name): self.name = name self.size = 100000 pass def show(self): print("类内部调用:",self.size) hlj = Province("黑龙江") hlj.show() print("对象调用:",hlj.size)
执行结果如下:
类内部调用: 100000
对象调用: 100000
代码解析:普通字段属于对象,可以通过类内部访问:self.普通字段名;可以通过对象直接访问:对象名.普通字段名,但是不能通过类来访问,因为对象属于类,但类不属于对象。
2.方法
1.普通方法
##普通方法 class Province: def __init__(self,name): self.name = name def show(self): print(self.name) p = Province("黑龙江") p.show()
执行结果如下:
黑龙江
代码解析:普通方法就是代码中的show()方法,要注意:第一个参数必须是self,self会把实例化出来的对象传入。上个例子,self就等于"p"这个对象。
2.静态方法
##静态方法 class Province: def __init__(self,name): self.name = name def show(self): print(self.name) @staticmethod def main(args): print(args) p = Province("黑龙江") p.main(123) Province.main([1,3,4])
执行结果如下:
123
[1, 3, 4]
代码解析:静态方法就是上述代码的main(args),要想把普通方法变成静态方法,需要在方法上一行加上@staticmethod装饰器,在把普通方法的self参数去掉即可,后边可以加任意参数。静态方法可以类直接调用:类名.静态方法名;可以对象调用:对象名.静态方法名。
3.类方法
##类方法 class Province: def __init__(self,name): self.name = name def show(self): print(self.name) @staticmethod def main(args): print(args) @classmethod def clsmain(cls,args): print("class method") print(cls) print(args) p = Province("lk") Province.clsmain("123")
执行结果如下:
class method <class '__main__.Province'> 123
代码解析:类方法属于类,类方法的定义格式,在普通方法的上边加上@classmethod装饰器,并且方法的第一个参数self去掉,换成cls。这个cls默认就是类名,和普通方法的self类似。调用类方法,可以用类直接调用:类名.类方法名;可以通过对象调用:对象名.类方法名。
3.属性
1.属性基础
#属性 class Pager: def __init__(self,count): self.count = count @property def main(self): a1, a2 = divmod(self.count,10) if a2 == 0: return a1 else: return a1 + 1 p = Pager(158) result = p.main print(result)
执行结果如下:
16
代码解析:属性的定义格式是:在方法名上一行加上@property的装饰器。然后在调用的时候,直接类名/对象名.属性名。
2.属性进阶
class Pager: def __init__(self,count): self.count = count @property def main(self): a1, a2 = divmod(self.count,10) if a2 == 0: return a1 else: return a1 + 1 @main.setter def main(self,value): print("赋值时调用的方法:",value) @main.deleter def main(self): print("del 时调用的方法") p = Pager(191) ret = p.main print(ret) p.main = 200 del p.main
执行结果如下:
20 赋值时调用的方法: 200 del 时调用的方法
代码解析:在方法上一行加上@方法名.setter 的装饰器,会添加用途:当调用方式为:对象名.方法名 = value 时才会调用该方法。 在方法上加上@方法名.deleter 的装饰器,会添加用途:当调用方式为: del 对象名.方法名 时,才会调用改方法。 也可以这样定义这些特殊的属性:方法名 = property(fget=f1,fset=f2,fdel=f3),fget、fset、fdel参数都是固定的,f1、f2、f3是方法名。效果和加装饰器类似。代码如下:
class Pager: def __init__(self,num): self.num = num def f1(self): return 123 def f2(self,value): print(value) def f3(self): print(789) foo = property(fget=f1,fset=f2,fdel=f3) p = Pager(100) ret = p.foo print(ret) p.foo = "fuck" del p.foo
执行结果如下:
123
fuck
789
3.成员修饰符
1.公有
公有的成员包括:公有字段、公有方法、公有属性。这些成员就是之前介绍过的
2.私有
1.私有字段
定义一个私有字段的格式为: __xx = oo
##私有字段 class Pager: __cc = 123 def __init__(self,value): self.__value = value def f1(self): print("类内对象调用私有静态字段:",self.__cc) print("类内对象调用私有普通字段:",self.__value) def f2(self): print("类内利用类直接调用私有静态字段:",Pager.__cc) p = Pager("fuck") # p.__value ##error p.f1() # Pager.__cc ##error p.f2()
执行结果如下:
类内对象调用私有静态字段: 123
类内对象调用私有普通字段: fuck
类内利用类直接调用私有静态字段: 123
代码解析:私有字段,只能在类内部调用,不可以类、对象直接在外部调用。当然也有例外,python可以强行调用私有字段,不过不推荐!!!
##私有字段 class Pager: __cc = 123 def __init__(self,value): self.__value = value def f1(self): print("类内对象调用私有静态字段:",self.__cc) print("类内对象调用私有普通字段:",self.__value) def f2(self): print("类内利用类直接调用私有静态字段:",Pager.__cc) p = Pager("fuck") print(p._Pager__value) ##不推荐 print(Pager._Pager__cc) ##不推荐
这样也可以强行调用私有字段。不过不推荐!!!
2.私有方法
###私有方法 class Pager2: def __init__(self,value): self.value = value def __f1(self): print(123) @staticmethod def __f2(): print(456) @classmethod def __f3(cls): print(789) def f4(self): self.__f1() def f5(self): Pager2.__f2() def f6(self): Pager2.__f3() p = Pager2("fuck") p.f4() p.f5() p.f6()
执行结果如下:
123 456 789
代码解析:私有方法的定义格式:在类内部,在普通方法的前边加上双下划线:__ ;私有方法只能在类内部调用,在这里只能由对象调用类内部方法来使用。
4.类的特殊成员
1.__init__
### __init__ class Foo: def __init__(self,name,age): self.name = name self.age = age def show(self): print(self.name,self.age) f = Foo("lk",25) f.show()
执行结果如下:
lk 25
代码解析:类的__init__方法的作用是初始化数据,叫做构造方法,方便其他类内部方法使用。
2.__del__、__doc__、__call__、__str__、__dict__、__iter__
1.__del__
__del__叫做析构方法,当对象被回收时自动执行的方法。
2.__doc__
__doc__方法的作用是打印类内部的注释
### __doc__ class Foo: """ 我是注释!!!! """ def __init__(self,name,age): self.name = name self.age = age def show(self): print(self.name,self.age) f = Foo("lk",25) print(f.__doc__)
执行结果如下:
我是注释!!!!
3.__call__
### __call__ class Foo: def __init__(self,name,age): self.name = name self.age = age def show(self): print(self.name,self.age) def __call__(self, *args, **kwargs): print("我是__call__方法") f = Foo("lk",25) f()
执行结果如下:
我是__call__方法
代码解析:f 是 Foo 的一个对象,在对象f 后边加上小括号,就会执行类内部的__call__方法。这个比较坑,很容易被忽略。
4.__str__
## __str__ class Foo: def __init__(self,name,age): self.name = name self.age = age def show(self): print(self.name,self.age) def __str__(self): return self.name f = Foo("lk",25) print(f)
执行结果如下:
lk
代码解析:f 是 Foo类的一个对象,当直接print(f) 的时候,就会调用类内部的__str__方法。
5.__dict__
__dict__方法也非常实用,它可以看到构造方法中的所有字段,包括私有字段!
## __dict__ class Foo: def __init__(self,name,age,salary): self.name = name self.age = age self.__salary = salary def show(self): print(self.name,self.age) f = Foo("lk",25,3000) print(f.__dict__)
执行结果如下:
{'name': 'lk', 'age': 25, '_Foo__salary': 3000}
代码解析:在构造方法中构造了三个字段,执行__dict__,可以以字典的形式打印出所有字段。
6.__iter__
# __item__ class Foo: ##必须是个迭代器才能循环,当循环一个对象时,就会执行__iter__方法. def __iter__(self): yield 2 yield 3 f = Foo() for i in f: print(i)
执行结果如下:
2 3
代码解析:要循环一个对象,必须类内部有一个__iter__方法,且它必须是一个迭代器。才能循环。
3.__getitem__、__setitem__、__delitem__
## __getitem__ __setitem__ __delitem__ class Foo: def __init__(self,name,age): self.name = name self.age = age def __getitem__(self, item): # return self.age print("==get_item==") print(item) print(type(item)) print(item.start,item.stop,item.step) def __setitem__(self, key, value): print("==set_item==") print(type(key),type(value)) print(key.start,key.stop,key.step) def __delitem__(self, key): print("==del_item==") print(type(key))
执行结果如下:
==get_item== slice(1, 4, 2) <class 'slice'> 1 4 2 ==set_item== <class 'slice'> <class 'list'> 1 4 2 ==del_item== <class 'slice'>
代码解析:__getitem__方法:当执行对象[] 的时候,会执行类中的__getitem__方法,[]中的内容可以是字符串、数字(这时就没有item.start、stop、step了),可以是列表分片的形式。当传入字符串、数字就是相应类型。当传入列表分片的形式时,它是一个slice类型。 __setitem__方法:当执行对象[] = xxx 的时候,会执行类内部的__setitem__方法,这个方法有两个参数,一个key,一个value。和__getitem__方法一样,传入的参数可以是字符串、数字、列表分片,列表分片的参数才有start、stop、step的功能。 __delitem__方法:当执行 del 对象[] 的时候,会执行类内部的__delitem__方法,这个方法有一个参数,参数类型和上边两个一样的。
4.super的使用
#super class Foo(): def f1(self): print("Foo.f1") class Bar(Foo): def f1(self): super(Bar,self).f1() print("Bar.f1") b = Bar() b.f1()
执行结果如下:
Foo.f1
Bar.f1
代码解析:super方法用在类继承中,格式为super(子类,self).父类的方法()。它的作用是可以在不修改父类的情况下,添加自己的功能。下面是个有序字典的例子。
##有序字典,利用super class Mydict(dict): def __init__(self): self.li = [] super(Mydict,self).__init__() def __setitem__(self, key, value): self.li.append(key) super(Mydict,self).__setitem__(key,value) def __str__(self): temp_list = [] for i in self.li: value = self.get(i) temp_list.append("'%s':%s" % (i,value)) temp_str = "{" + ",".join(temp_list) + "}" return temp_str obj = Mydict() obj["name"] = "lk" obj["age"] = 23 obj["salary"] = 3000 print(obj) print(type(obj)) print(isinstance(obj,dict))
执行结果如下:
{'name':lk,'age':23,'salary':3000} <class '__main__.Mydict'> True
代码解析:定义一个自己的新字典类型Mydict类,继承字典类;首先要在字典中传值,调用__init__方法,这个要用到字典类的__init__方法,所以要在Mydict类的__init__方法中写入super(Mydict,self).__init__(),而我们想要个有序字典,就是要把传入的key,存到一个列表中,最后循环列表把相应的value从字典中取到就可以了。所以新建个空列表要在子类中的__init__方法中执行。然后,传值的过程中,执行了对象[] = xxx的形式,这就会调用类内部的__setitem__方法,这个方法也要在子类中定义,不过依然要执行父类字典类的方法,所以要写上super(Mydict,self).__setitem__(key,value),不过在这里,我们可以把每个key加入到空列表中去。最后在打印的时候,形式为print(对象),这个形式就会调用类内部的__str__方法,在子类的__str__方法里,就可以根据key,利用字典的get方法,循环打印相应的value值。可以看到,输出的结果是Mydict类,也就是dict的子类。这个结果也可以进行任何字典的操作。
5.设计模式初探之单例模式
概念:单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。
简介:单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义。
动机:对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
#设计模式之单例模式 class Foo(): instance = None def __init__(self,name): self.name = name @classmethod def get_instance(cls,name): if cls.instance: return cls.instance else: obj = cls(name) cls.instance = obj return obj obj1 = Foo.get_instance("lk") obj2 = Foo.get_instance("lk") print(obj1,obj2)
执行结果如下:
<__main__.Foo object at 0x107704e48> <__main__.Foo object at 0x107704e48>
代码解析:第一次执行obj1 = Foo.get_instance("lk")的时候,会调用类方法get_instance,通过判断静态字段instance = None,会生成一个对象:obj = cls(name),把这个对象给静态字段instance,返回这个对象。此时创建成功对象obj1。 第二次执行obj2 = Foo.get_instance("lk")的时候,因为静态字段非空,代表之前创建过对象,所以直接返回第一次创建的对象。 再次输出的时候,可以看到两次对象的内存地址是相同的,这就是单例模式的在Python中的简单实现。