(Python3)- 模块&面向对象

1.创建模块:如果模块名可能重复的话,可以通过创建目录确保不会重名。例如mod.py位于工作目录下的math目录中,导入模块的时候可以使用import math.mod模块,调用模块的时候,使用math.mod.xxxx捷即可。每一个模块文件中都会有特殊的变量:

  • __author__:当前模块的作者
  • __name__:当值为'__main__'的时候不表示模块是在当前文件被调用的。否则就是在其他的文件中被调用的。

2.查看搜索模块的位置:

>>> import sys
>>> sys.path
sys.path.append('/Users/michael/my_py_scripts') # 添加模块的搜索目录

3.类和实例:这个就不多说了。

<__main__.Student object at 0x10a67a590> #实例
>>> Student
<class '__main__.Student'>#类

实例可以动态的绑定数据,而且这些数据不能在不同的同一个类的实例中共享。如果需要创建共享的数据,可以在类的定义下,直接初始化变量名,之后可以在所有的实例、类使用"."访问。

4.构造函数:__init__(self)方法,self相当于C++里的this指针。这个方法可以在已实例化的对象上进行数据成员的初始,具体的创建工作在__new__方法,他可以决定是否调用__init__方法。__new__方法必须返回一个实例。__init__可以不返回。

# -*- coding: utf-8 -*-

class Person(object):
    """Silly Person"""

    def __new__(cls, name, age):
        print '__new__ called.'
        return super(Person, cls).__new__(cls, name, age)

    def __init__(self, name, age):
        print '__init__ called.'
        self.name = name
        self.age = age

    def __str__(self):
        return '<Person: %s(%s)>' % (self.name, self.age)

if __name__ == '__main__':
    piglei = Person('piglei', 24)
    print piglei
"""
__new__ called.
__init__ called.
<Person: piglei(24)>
"""

5.数据封装:使用__xxx可以表示这个变量是私有的,一般不允许访问。动态绑定数据一般也不要使用__作为前缀。因为Python会对内部的私有变量作处理,例如加上_<类名>.__xxx,但并不能保证以后都是这个规律。如果需要访问私有成员,可以自定义set和get函数。

6.继承&鸭子类型:LikeDuck就是一个鸭子类型,他也有run方法,所以对于动态类型,只要具有相同的属性和方法,他们就可以看作某些类型。

#coding=utf-8
#继承与多态
class Animal(object):
    def run(self):
        print('Animal is running')
class Cat(Animal):
    def run(self):
        print('Cat is running')
class Dog(object):
    def run(self):
        print('Dog is running')
class LikeDuck(object):
    def run(self):
        print('I am a duck! Hahaha')
def pp(an):
    an.run()
a=Animal()
c=Cat()
d=Dog()
du=LikeDuck()
pp(a)
pp(c)
pp(d)
pp(du)
#类型判断 isinstance
print(isinstance(du,LikeDuck))
print(dir(str))
gh=getattr(du,'run',401)
gh()
  • type():可以获取对象、类的类型信息,例如str、int、<class 'builtin_function_or_method'>(内部的方法或函数)

如果需要判断是否是函数信息,可以使用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
  • isinstance(obj,classname)就可以判断实例是否是classname的对象。同type,第二个参数可以传入多个类型的元组,来判断是否是元组的某一个类型。
  • dir(obj):可以获取obj的所有的方法和属性。
  • 获取、检查是否存在某些属性:
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

注意:使用getattr时,如果不存在某些属性,可以指定第三个参数(default)为某个值,不存在的话就返回缺省值。否则就抛出AttributeError的错误。

>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404

所以动态语言如果需要使用参数的某些成员或方法时,需要注意检查是否是包含他们:

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

7.高级的技术:

  • 动态添方法:需要使用types模块的MethodType。注意一定要初始要使用的数据成员。
class Animal(object):
    __slots__=('age','typ')
    def run(self):
        print('Animal is running')
    
a1=Animal()
a2=Animal()
from types import MethodType
def set_age(self,age):
    self.age=age
a1.set_age=MethodType(set_age,a1)
a1.set_age(222)
#给所有的类的实例都添加方法
Animal.set_age=set_age
a1.set_age(88)
a2.set_age(99)
print(a1.age)
print(a2.age)

__slots__是一个元组,表示允许绑定的动态属性的名字的元组。注意,这个对子类是不起作用的。

  • @property:将函数作为一个属性使用(类似C#的get set?)

本来有三个方法:width() height() resolution(),加上@property之后,可以变成一个可读的属性,如果需要可写,那么可以在修改值的方法上加上@<属性名>.setter使方法成为可写属性。例如width、height就是可读写的,而resolution就是只读的。

#print(s.score)
#pratice
class Screen(object):
    @property
    def width(self):
        return self._width
    @property
    def height(self):
        return self._height
    @width.setter
    def width(self,w):
        if w<0:
            raise ValueType('negative width')
        self._width=w
    @height.setter
    def height(self,h):
        if h<0:
            raise ValueType('negative height')
        self._height=h
    @property
    def resolution(self):
        return self.width*self.height
s=Screen()
#s.width=-98 #will raise error
#s.height=-99
s.width=1024
s.height=768
print(s.resolution)
  • 多重继承:表示继承了Mammal和Flyable。
class Bat(Mammal, Flyable):
    pass

MixIn设计模式:在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn

为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn。类似的,你还可以定义出肉食动物CarnivorousMixIn和植食动物HerbivoresMixIn,让某个动物同时拥有好几个MixIn:

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass

MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。

super类:类似于菱形继承,会存在公共的基类。C++可以通过虚继承解决。Python的话,可以使用super来避免多次调用重复的公共基类:

#coding=utf-8
class p1(object):
    def __init__(self):
        print('p1')
        self.geg='egege'
class c1(p1):
    def __init__(self):
        #super(c1,self).__init__()
        p1.__init__(self)
        print('c1')
class c2(p1):
    def __init__(self):
        #super(c2,self).__init__()
        p1.__init__(self)
        print('c2')
class c3(c1,c2):
    def __init__(self):
        super(c3,self).__init__()
        #c1.__init__(self)
        #c2.__init__(self)
        pass
c=c3()
print(c.geg)
"""
不使用super
p1
c1
p1
c2
使用super
p1
c2
c1
"""

注意:在python中引入super()的目的是保证相同的基类只初始化一次:

1.super()机制是用来解决多重继承的,对于直接调用父类名是没有问题的,但在之后根据前人的经验就是:要么都用类名调用(需要创建一个父类对象),要么就全部用super(),不要混合的用,由此为人做事还是要专一的嘛。

2.super()继承只能用于新式类,用于经典类时就会报错。
新式类:必须有继承的类,如果无继承的,则继承object
经典类:没有父类,如果此时调用super就会出现错误:super() argument 1 must be type, not classobj

3.super()是一个类名而非函数,super(class, self)事实上调用了super类的初始化函数,产生了一个super对象。

  • 定制类:
  1. __str__()表示类对于用户显示的名称。(使用print)
  2. __repr__()表示对于调试者显示的名称。(在交互环境下,直接输入变量名显示的名称)
  3. __iter__()返回一个可迭代的对象。
  4. __next__()可以获取下一个对象,直到抛出StopIteration就可以停止循环。
  5. __getitem__()可以返回一个列表可以指定参下标[n]或者切片
  6. __getattr__()可以返回当没有找到指定的属性、方法时返回的值。可以返回一个可调用对象、数值都可以,但是需要预先定义。
  7. __call__()相当于C++的重载调用运算符,可以直接调用实例的方法,而不使用instance.method()
  8. __bases__()返回类的所有的父类
  9. callable()函数可以判断是否是可以调用的对象,例如__call__()的对象、函数等等。
  10. 更多的方法:更多的方法(Python doc)

例子1:

class Animal(object):
    def __init__(self,name):
        self.name=name
    def __str__(self):
        return' Student object (name : %s)' % self.name
    def __repr__(self):
        return'Debug Student object (name : %s)' % self.name
    def __getattr__(self,n):
        if n=='score':
            return lambda: 25
a=Animal('ghh')
print(a.score())

例子2:输出斐波那契数列的若干项

#迭代对象
class Fib(object):
    def __init__(self):
        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 > 1000:
            raise StopIteration()
        return self.a
    #支持下标和切片
    def __getitem__(self,n):
        if isinstance(n,int):
            a,b=1,1
            for x in range(n):
                a,b=b,a+b
            return a
        elif isinstance(n,slice):#并不支持负数和步长
            s=n.start
            e=n.stop
            if s is None:
                s=0
            if e is None:
                e=100#默认的大小
            a,b=1,1
            L=[]
            for x in range(e):
                if x >=s:
                    L.append(a)
                a,b=b,a+b
            return L
"""
for i in Fib():
    print(i)
"""
f=Fib()
print(f[8:10])

3.API模拟生成:

#coding=utf-8
#API的调用
class Chain(object):
    def __init__(self,path=''):
        self._path=path
    def __getattr__(self,path):
        if path=='user':
            return lambda x:Chain('%s/%s' %(self._path,x))#返回一个匿名函数用于生成可另一个对象。
        else:
            return Chain('%s/%s' %(self._path,path))
    def __str__(self):
        return self._path
    __repr__=__str__
    def __call__(self):#相当于C++里的重载调用运算符
        print('fwwsf')
print(Chain('http://api.com').user('shusagsa').copy) #http://api.com/shusage/copy
print(callable(Chain))
print(callable(Chain()))
  • 枚举类型:使用enum模块的Enum类。Enum可以继承。

1.直接创建:指定module表示Enum是在那个模块创建的,不指定的话,会出现 KeyError: 'name' 这种错误。

#枚举类型
from enum import Enum,unique
Animal = Enum('Animal', {'ANT':2,'HJ':3},module=__name__)#创建一个枚举Animal 包含ANT BEE。。。
print(Animal.ANT==1)

2.使用类:使用enum的@unique装饰器可以确保不会重复的成员。

@unique #不允许有重复值
class Job(Enum):#使用类 继承自Enum
    COOK=1
    DRIVER=2

使用枚举:即使数值一样,不同的枚举类型是不一样的。

#coding=utf-8
#枚举类型
from enum import Enum,unique
Animal = Enum('Animal', {'ANT':2,'HJ':3},module=__name__)#创建一个枚举Animal 包含ANT BEE。。。
print(Animal.ANT==1)
@unique #不允许有重复值
class Job(Enum):#使用类 继承自Enum
    COOK=1
    DRIVER=2
print(Job)#<enum 'Job'>
print(Job.COOK)
#迭代
print(list(Animal))
for name,member in Animal.__members__.items():#可以拆分为关键字和值
    print(name,'->',member,':',member.value)#获取枚举的信息
""" 输出的信息
ANT -> Animal.ANT : 2
HJ -> Animal.HJ : 3
"""
print(Animal is not Job)#True
print(Animal.ANT==Job.DRIVER)#False
#使用值访问
print(Animal(3))#Animal.HJ
#通过成员访问
print(Animal['HJ'])#Animal.HJ

还有更多的Flag类、IntFlag可以访问:https://docs.python.org/3/library/enum.html#iteration

注意:

1.如果枚举有值重复的成员,循环遍历枚举时只获取值重复成员的第一个成员。如果想把值重复的成员也遍历出来,要用枚举的一个特殊属性__members__:

from enum import Enum
class Color(Enum):
    red = 1
    orange = 2
    yellow = 3
    green = 4
    blue = 5
    indigo = 6
    purple = 7
    red_alias = 1
for color in Color.__members__.items():
    print(color)

2.枚举成员不能进行大小比较:错误:TypeError: unorderable types: Animal() > Animal()

3.文件名:如果文件名为:enum.py,会报错:ImportError: cannot import name 'Enum'

  • 元类:

1.type()可以动态生成一个类,这个类可以实例。因为类也是一个类型(Type)。

#type创建类型
def fn(self,name='world'):
    print('hello,%s' % name)
hello=type('hello',(object,),dict(hello=fn))#创建hello类,继承object 有函数hello表示为fn
h=hello()#是一个类 hello是一个类型
print(type(h))#<class '__main__.hello'>
print(type(hello))#<class 'type'>
h.hello('fdfdf')
print(hello.__bases__)

2.元类:元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。好吧,元类就是用来创建这些类(对象)的,元类就是类的类。

__metaclass__属性:

class Foo(object):
	__metaclass__ = something…
class Foo(Bar):
    pass

当Python读到Food时:

Python做了如下的操作:

Foo中有__metaclass__这个属性吗?如果是,Python会在内存中通过__metaclass__创建一个名字为Foo的类对象(我说的是类对象,请紧跟我的思路)。如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。

现在的问题就是,你可以在__metaclass__中放置些什么代码呢?可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东东都可以。

2.1.函数版本的“元类”:__metaclass__=new可以将元类new作用于所有的类

#定义一个元类 使用函数
def pr(self):
    print(self.name)
def new(name, parent,attr):
    l={}
    for key,value in attr.items():
        if not key.startswith('__'):
            l[key.upper()]=value#所有的绑定的属性全部为大写
        else:
            l[key]=value#特殊变量不动
    return type(name,parent,l)#返回一个type元类创建的对象
__metaclass__=new#将元类作用于所有的类
class A(object):
    pass
pw=new('ffe',(A,),{'pp':pr})#pw是一个类 而不是对象
print(hasattr(pw,'pp'))
print(hasattr(pw,'PP'))
pw.name='egge'
pppp=pw()
pppp.PP()

2.2 OOP版本的元类,创建一个类:

class Metaclass(type):#将参属性转换为大写
    def __new__(self,name,obj,attr):
        l={}
        for key,value in attr.items():
            if not key.startswith('__'):
                l[key.upper()]=value
            else:
                l[key]=value
        #return type(name,parent,l)
        return super(Metaclass,self).__new__(self,name,obj,l)
class Tri(object):
    bar=12
    money='grgrg'
tt=Metaclass('Tri',(),{'bar':Tri.bar,'money':Tri.money})
print(dir(tt))
print(tt.BAR)

3.元类的用途:

“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters

元类的主要用途是创建API

Python中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了type。type实际上是它自己的元类,在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:

1) Monkey patching

2)   class decorators

当你需要动态修改类时,99%的时间里你最好使用上面这两种技术。当然了,其实在99%的时间里你根本就不需要动态修改类 😀

感谢&引用&参考: