W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
你想定義某些在屬性賦值上面有限制的數(shù)據(jù)結(jié)構(gòu)。
在這個問題中,你需要在對某些實例屬性賦值時進行檢查。所以你要自定義屬性賦值函數(shù),這種情況下最好使用描述器。
下面的代碼使用描述器實現(xiàn)了一個系統(tǒng)類型和賦值驗證框架:
# 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('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 MaxSized(Descriptor):
def __init__(self, name=None, **opts):
if 'size' not in opts:
raise TypeError('missing size option')
super().__init__(name, **opts)
def __set__(self, instance, value):
if len(value) >= self.size:
raise ValueError('size must be < ' + str(self.size))
super().__set__(instance, value)
這些類就是你要創(chuàng)建的數(shù)據(jù)模型或類型系統(tǒng)的基礎構(gòu)建模塊。下面就是我們實際定義的各種不同的數(shù)據(jù)類型:
class Integer(Typed):
expected_type = int
class UnsignedInteger(Integer, Unsigned):
pass
class Float(Typed):
expected_type = float
class UnsignedFloat(Float, Unsigned):
pass
class String(Typed):
expected_type = str
class SizedString(String, MaxSized):
pass
然后使用這些自定義數(shù)據(jù)類型,我們定義一個類:
class Stock:
# Specify constraints
name = SizedString('name', size=8)
shares = UnsignedInteger('shares')
price = UnsignedFloat('price')
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
然后測試這個類的屬性賦值約束,可發(fā)現(xiàn)對某些屬性的賦值違法了約束是不合法的:
>>> s.name
'ACME'
>>> s.shares = 75
>>> s.shares = -10
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "example.py", line 17, in __set__
super().__set__(instance, value)
File "example.py", line 23, in __set__
raise ValueError('Expected >= 0')
ValueError: Expected >= 0
>>> s.price = 'a lot'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "example.py", line 16, in __set__
raise TypeError('expected ' + str(self.expected_type))
TypeError: expected <class 'float'>
>>> s.name = 'ABRACADABRA'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "example.py", line 17, in __set__
super().__set__(instance, value)
File "example.py", line 35, in __set__
raise ValueError('size must be < ' + str(self.size))
ValueError: size must be < 8
>>>
還有一些技術可以簡化上面的代碼,其中一種是使用類裝飾器:
# 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(name=SizedString(size=8),
shares=UnsignedInteger,
price=UnsignedFloat)
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
另外一種方式是使用元類:
# A metaclass that applies checking
class checkedmeta(type):
def __new__(cls, clsname, bases, methods):
# Attach attribute names to the descriptors
for key, value in methods.items():
if isinstance(value, Descriptor):
value.name = key
return type.__new__(cls, clsname, bases, methods)
# Example
class Stock2(metaclass=checkedmeta):
name = SizedString(size=8)
shares = UnsignedInteger()
price = UnsignedFloat()
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
本節(jié)使用了很多高級技術,包括描述器、混入類、super()
的使用、類裝飾器和元類。不可能在這里一一詳細展開來講,但是可以在8.9、8.18、9.19小節(jié)找到更多例子。但是,我在這里還是要提一下幾個需要注意的點。
首先,在 Descriptor
基類中你會看到有個 __set__()
方法,卻沒有相應的 __get__()
方法。如果一個描述僅僅是從底層實例字典中獲取某個屬性值的話,那么沒必要去定義 __get__()
方法。
所有描述器類都是基于混入類來實現(xiàn)的。比如 Unsigned
和 MaxSized
要跟其他繼承自 Typed
類混入。這里利用多繼承來實現(xiàn)相應的功能。
混入類的一個比較難理解的地方是,調(diào)用 super()
函數(shù)時,你并不知道究竟要調(diào)用哪個具體類。你需要跟其他類結(jié)合后才能正確的使用,也就是必須合作才能產(chǎn)生效果。
使用類裝飾器和元類通??梢院喕a。上面兩個例子中你會發(fā)現(xiàn)你只需要輸入一次屬性名即可了。
# Normal
class Point:
x = Integer('x')
y = Integer('y')
# Metaclass
class Point(metaclass=checkedmeta):
x = Integer()
y = Integer()
所有方法中,類裝飾器方案應該是最靈活和最高明的。首先,它并不依賴任何其他新的技術,比如元類。其次,裝飾器可以很容易的添加或刪除。
最后,裝飾器還能作為混入類的替代技術來實現(xiàn)同樣的效果;
# Decorator for applying type checking
def Typed(expected_type, cls=None):
if cls is None:
return lambda cls: Typed(expected_type, cls)
super_set = cls.__set__
def __set__(self, instance, value):
if not isinstance(value, expected_type):
raise TypeError('expected ' + str(expected_type))
super_set(self, instance, value)
cls.__set__ = __set__
return cls
# Decorator for unsigned values
def Unsigned(cls):
super_set = cls.__set__
def __set__(self, instance, value):
if value < 0:
raise ValueError('Expected >= 0')
super_set(self, instance, value)
cls.__set__ = __set__
return cls
# Decorator for allowing sized values
def MaxSized(cls):
super_init = cls.__init__
def __init__(self, name=None, **opts):
if 'size' not in opts:
raise TypeError('missing size option')
super_init(self, name, **opts)
cls.__init__ = __init__
super_set = cls.__set__
def __set__(self, instance, value):
if len(value) >= self.size:
raise ValueError('size must be < ' + str(self.size))
super_set(self, instance, value)
cls.__set__ = __set__
return cls
# Specialized descriptors
@Typed(int)
class Integer(Descriptor):
pass
@Unsigned
class UnsignedInteger(Integer):
pass
@Typed(float)
class Float(Descriptor):
pass
@Unsigned
class UnsignedFloat(Float):
pass
@Typed(str)
class String(Descriptor):
pass
@MaxSized
class SizedString(String):
pass
這種方式定義的類跟之前的效果一樣,而且執(zhí)行速度會更快。設置一個簡單的類型屬性的值,裝飾器方式要比之前的混入類的方式幾乎快100%?,F(xiàn)在你應該慶幸自己讀完了本節(jié)全部內(nèi)容了吧?^_^
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: