天天看點

Python從門到精通(二):包裝-05-類的屬性(補充)

一、屬性讀取

1.1、getter和setter

這裡可以複寫兩個方法:__getattr__隻在調用不存在的屬性時才會被調用,__getattribute__無條件被調用,如果同時重寫了以上兩個方法,則__getattr__永遠不會被調用。另外__getattr__對于雙下劃線開始和結尾、單下劃線開始的方法是不能用的。

"""
如果自定義這兩個方法,則
__getattr__隻在調用不存在的屬性時才會被調用
__getattribute__無條件被調用
如果同時重寫了以上兩個方法,則__getattr__永遠不會被調用
"""
class A(object):
    def __init__(self, name):
        self.name = name

a = A('attribute')
print(a.name)
#attribute      
class A1(object):
    def __init__(self, name):
        self.name = name
    #用這種方法可以實作更友好的程式和屬性隐藏,
    def __getattr__(self, name):
        print(f'calling __getattr__: {name}')
a = A1('attribute')
print(a.name)
print(a.test)

# attribute
# calling __getattr__: test
# None      
class A1(object):
    def __init__(self, name):
        self.name = name
    def __getattr__(self, name):
        print(f'calling __getattr__: {name}')
    def __getattribute__(self, item):
        print("call getattribut")
a = A1('attribute')
print(a.name)
print(a.test)

# call getattribut
# None  //這行是因為不存在屬性是以列印出來的,因為覆寫了__getattribute__方法
# call getattribut
# None  //這行是因為不存在屬性是以列印出來的      

1.2、proxy

1.2.1、簡單實作

class A(object):
    def spam(self, x):
        pass

    def foo(self):
        pass

class B(object):
    """
    簡單的代理
    """
    def __init__(self):
        self._a = A()

    def spam(self, x):
        return self._a.spam(x)

    def foo(self):
        return self._a.foo()

    def bar(self):
        pass      
# A proxy class that wraps around another object, but exposes its public attributes
class Proxy(object):
    def __init__(self, obj):
        self._obj = obj

    # Delegate attribute lookup to internal obj
    def __getattr__(self, name):
        print(f'getattr: {name}')
        return getattr(self._obj, name)

    # Delegate attribute assignment
    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name, value)
        else:
            print(f'setattr: {name} {value}')
            setattr(self._obj, name, value)

    # Delegate attribute deletion
    def __delattr__(self, name):
        if name.startswith('_'):
            super().__delattr__(name)
        else:
            print(f'delattr: {name}')
            delattr(self._obj, name)


class Spam(object):
    def __init__(self, x):
        self.x = x

    def bar(self, y):
        print(f'Spam.bar: {self.x}, {y}')

# Create an instance
s = Spam(2)
# # Create a proxy around it
p = Proxy(s)
# Access the proxy
p.bar(3)  #Spam.bar: 2, 3      

二、屬性建立

2.2、屬性的建立

class Person:
    def __init__(self, first_name):
        self.first_name = first_name #此處的first_name不帶下劃線的原因是,當建立屬性時會自動調用 @first_name.setter标注的方法,實作檢查的目的

    # Getter function
    @property
    def first_name(self):
        return self._first_name

    # Setter function
    @first_name.setter
    def first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

    # Deleter function (optional)
    @first_name.deleter
    def first_name(self):
        raise AttributeError("Can't delete attribute")


per = Person('Guido')
# Calls the getter
print(f'first name is: {per.first_name}')
# Calls the setter,Err, expected a string
per.first_name = 30
# Calls the deleter, Err, raise ErrorException
del per.first_name      

2.2、在已有get/set方法的基礎上建立property

class Person:
    def __init__(self, first_name):
        self.set_first_name(first_name)

    # Getter function
    def get_first_name(self):
        return self._first_name

    # Setter function
    def set_first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

    # Deleter function (optional)
    def del_first_name(self):
        raise AttributeError("Can't delete attribute")

    # Make a property from existing get/set methods
    name = property(get_first_name, set_first_name, del_first_name)      

2.3、動态計算屬性

import math
class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return math.pi * self.radius ** 2

    @property
    def diameter(self):
        return self.radius * 2

    @property
    def perimeter(self):
        return 2 * math.pi * self.radius


c = Circle(4.0)
print(f'radius is: {c.radius}')
# Notice lack of ()
print(f'area is: {c.area}')
print(f'perimeter is: {c.perimeter}')      

2.4、延遲計算

class LazyProperty:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            value = self.func(instance)
            #下面這行可有可無,其中name指要調用的特定方法
            setattr(instance, self.func.__name__, value) 
            return value

import math
class Circle:
    def __init__(self, radius):
        self.radius = radius

    @LazyProperty
    def area(self):
        print('Computing area')
        return math.pi * self.radius ** 2

    @LazyProperty
    def perimeter(self):
        print('Computing perimeter')
        return 2 * math.pi * self.radius

circle = Circle(6.0)
print(f'radius = {circle.radius}')
print(f'area = {circle.area}')
print(f'perimeter = {circle.perimeter}')      

三、類型檢查

3.1、通用方法

# Base class. Uses a descriptor to set a value
class Descriptor:
    def __init__(self, name=None, **opts):
        self.name = name
        for key, value in opts.items():
            setattr(self, key, value)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value

# Descriptor for enforcing types
class Typed(Descriptor):
    expected_type = type(None)

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError(f'expected {str(self.expected_type)}')
        super().__set__(instance, value)

# Descriptor for enforcing values
class Unsigned(Descriptor):
    def __set__(self, instance, value):
        if value < 0:
            raise ValueError('Expected >= 0')
        super().__set__(instance, value)

class Integer(Typed):
    expected_type = int

class String(Typed):
    expected_type = str

class SizedString(String):
    pass

class Course:
    # Specify constraints
    course_name = SizedString('course_name')
    total_class = Integer('total_class')
    score = String('score')

    def __init__(self, course_name, total_class, score):
        self.course_name = course_name
        self.total_class = total_class
        self.score = score


course = Course('python', 30, "hello")
course.score = 'hello'      

3.2、注解實作

# Class decorator to apply constraints
def check_attributes(**kwargs):
    def decorate(cls):
        for key, value in kwargs.items():
            if isinstance(value, Descriptor):
                value.name = key
                setattr(cls, key, value)
            else:
                setattr(cls, key, value(key))
        return cls

    return decorate

# Example
@check_attributes(course_name=SizedString(size=8),
                  total_class=String,
                  score=String)
class Course:
    def __init__(self, course_name, total_class, score):
        self.course_name = course_name
        self.total_class = total_class
        self.score = score      

繼續閱讀