美文网首页
跟我一起学Python(六)

跟我一起学Python(六)

作者: _花 | 来源:发表于2018-11-05 17:37 被阅读0次

__ slots __

如何在外部给class绑定方法?

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

动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。
但是,如果我们想要限制动态添加的属性名,比如:只允许给Student市里添加name属性,此时就需要用到__ slots __变量。
Python允许在定义class的时候,定义一个特殊的__ slots __变量,来限制该class实例能添加的属性:

class Student(object):
    __slots__ = ('name') # 用tuple定义允许绑定的属性名称

使用__ slots __要注意,__ slots __定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:子类实例可以动态添加除name外的其他属性

@property

前面提到我们可以在类里定义公有属性也可以在实例外直接动态绑定实例属性,然后直接赋值,调用。这样做的坏处是赋值时没办法检查参数的类型,所以我们运用类封装的特性,把这些属性的赋值与调用都通过实例方法或者类方法来实现,不对其进行直接操作。

>>> s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)

但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。
怎么简化这种步骤呢,Python内置的@property装饰器就是负责把一个方法变成属性调用的:

class Student(object):
    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

raise显示引发异常的方法,程序出错时,python会自动触发异常,也可以通过raise显示引发异常;且一旦执行了raise语句,raise之后的语句不在执行;但需注意如果加入了try,except,那么except里的语句会被执行

try:
    s = None
    if s is None:
        print('s是空对象')     #执行
        raise NameError
    print(len(s))     #不执行

except Exception:
    print('空对象没有长度')     #执行

把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

还可以定义只读属性,只定义一个getter方法
例如:

class Rectangle():
    @property
    def width(self):
        return self._width
    @width.setter
    def width(self,value):
        self._width = value
    @property
    def height(self):
        return self._height
    @height.setter
    def height(self, value):
        self._height = value
    @property 
    def area(self):
        return self._width * self._height

rect = Rectangle()
rect._width = 10
rect._height = 20
print(rect.area)      #200

多重继承

一个子类可以有多个基类
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:

class Dog(Mammal, Runnable):
    pass

MixIn

为了清楚地让人看出继承的主次关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass

MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
Python自带的很多库也使用了MixIn。举个例子,Python自带了TCPServerUDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixInThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。

class MyTCPServer(TCPServer, ForkingMixIn):  #多进程的TCP服务
    pass

定制类

__ len __()

len()函数是用来返回list,tuple,dict,set的长度;如果一个类表现得像一个dict,要获取有多少个元素,就得用 len() 函数。
要让len() 函数工作正常,类必须提供一个特殊方法 __ len __(),它返回元素的个数。
例如,我们写一个 Students 类,把名字传进去:

class Students(object):
    def __init__(self, *args):
        self.names = args
    def __len__(self):
        return len(self.names)
只要正确实现了__len__()方法,就可以用len()函数返回Students实例的“长度”:
>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print len(ss)
3

__ str __()

如果类里没有使用__ str __,则打印类时,会打印出

 print(Student('Michael'))
<__main__.Student object at 0x109afb190>

但是只需要在类里定义好str()方法

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)

__ repr __()

__ str __()方法类似,只不过前者是用在print方法里的,而后者是在交互模式下直接敲类名输出时用到的。

__ iter __()

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__ next __()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化两个计数器a,b

    def __iter__(self):
        return self # 实例本身就是迭代对象,故返回自己

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 计算下一个值
        if self.a > 100000: # 退出循环的条件
            raise StopIteration()
        return self.a # 返回下一个值
 for n in Fib():
       print(n)

__ getitem __()

结合上面代码中的Fib()实例,它可以通过for循环,但却不可以像list一样直接靠下标来取,如:Fib()[2],要想用下标来取值则需要实现getitem()方法:

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a
>>> f = Fib()
>>> f[0]

__ getattr __()

当我们调用类的方法或属性时,如果不存在,就会报错。要避免这个错误,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个getattr()方法,动态返回一个属性。

class Student(object):
    def __init__(self):
        self.name = 'Michael'
    def __getattr__(self, attr):
        if attr=='score':
            return 99
s = Student()
print(s.name)   #Michael
print(s.a)      #None

如果木有写getattr()方法

class Student(object):
    def __init__(self):
        self.name = 'Michael'
    # def __getattr__(self, attr):
    #     if attr=='score':
    #         return 99
s = Student()
print(s.name)   #Michael
print(s.a)      #None

会报如下错误:

$ python new.py
Michael
Traceback (most recent call last):
File "new.py", line 55, in <module>
print(s.a) #None
AttributeError: 'Student' object has no attribute 'a'

当调用不存在的属性时,Python解释器会试图调用\_\_getattr\_\_(self, 'score')来尝试获得属性.
任意调用如s.abc(既不在实例里,又没在__getattr__里)都会返回None,这是因为我们定义的__getattr__默认返回就是None。
如果要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:

class Student(object):
    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25
        raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)

这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。

这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全动态的情况作调用。

举个例子:

现在很多网站都搞REST API,比如新浪微博、豆瓣啥的,调用API的URL类似:

如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。

利用完全动态的__getattr__,我们可以写出一个链式调用:

class Chain(object):
    def __init__(self, path=''):
        self._path = path
    def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path))
    def __str__(self):
        return self._path
    __repr__ = __str__
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'

这样,无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变!

__ call __

任何类,只需要定义一个call()方法,就可以直接对实例进行调用

class Student(object):
    def __init__(self, name):
        self.name = name
    def __call__(self):
        print('My name is %s.' % self.name)
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.

我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,通过callable()函数,我们就可以判断一个对象是否是“可调用”对象

>>> callable(Student())
True

__ dict __

int, list, dict等这些常用的数据类型是没有__ dict __属性的

class A(object):
    a = 0
    b = 1

    def __init__(self):
        self.d = 4

    def test(self):
        print ('a normal func')
a = A()
a.a =3
print(A.__dict__)
print(a.__dict__)

得到的结果是

$ python new.py
{'__module__': '__main__', 'a': 0, 'b': 1, '__init__': <function A.__init__ at 0x00000204DB41B1E0>, 'test': <function A.test at 0x00000204DB444378>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
{'d': 4, 'a': 3}

__setattr__

会拦截所有属性的的赋值语句

class Foo(object):
    def __init__(self,name):
        self.storage = name
    def __setattr__(self, key, value):
        self.storage={'k1':'v1'}
        print(key,value)
    def __getattr__(self, item):
        print(item)


obj = Foo("123")
obj.x = 123

会报错:RecursionError: maximum recursion depth exceeded

原因:当初始化的时候,self.storage,对象调用storage就会自动执行__setattr__方法,
然后__setattr__方法里面又是对象调用属性,就会再执行setattr,这样就是无限递归了。
为了避免这个问题需要用下面这种方式实现:

class Dict(object):
    def __init__(self, name):
        self.name = name

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self.__dict__['name'] = value

d = Dict("Mike")
d.name= "韩某米"

枚举

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 22 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6
print(Weekday.Sun.value) #22

@unique装饰器可以帮助我们检查保证没有重复值。

元类

class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。
type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义:

>>> def fn(self, name='world'): # 先定义函数
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class

要创建一个class对象,type()函数依次传入3个参数:

class的名称;
继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
metaclass:先定义metaclass,然后创建类,直译为元类,

相关文章

网友评论

      本文标题:跟我一起学Python(六)

      本文链接:https://www.haomeiwen.com/subject/tcsqxqtx.html