W3Cschool
恭喜您成為首批注冊(cè)用戶(hù)
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
你想定義某些在屬性賦值上面有限制的數(shù)據(jù)結(jié)構(gòu)。
在這個(gè)問(wèn)題中,你需要在對(duì)某些實(shí)例屬性賦值時(shí)進(jìn)行檢查。所以你要自定義屬性賦值函數(shù),這種情況下最好使用描述器。
下面的代碼使用描述器實(shí)現(xiàn)了一個(gè)系統(tǒng)類(lèi)型和賦值驗(yàn)證框架:
# 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)
這些類(lèi)就是你要?jiǎng)?chuàng)建的數(shù)據(jù)模型或類(lèi)型系統(tǒng)的基礎(chǔ)構(gòu)建模塊。下面就是我們實(shí)際定義的各種不同的數(shù)據(jù)類(lèi)型:
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ù)類(lèi)型,我們定義一個(gè)類(lèi):
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
然后測(cè)試這個(gè)類(lèi)的屬性賦值約束,可發(fā)現(xiàn)對(duì)某些屬性的賦值違法了約束是不合法的:
>>> 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
>>>
還有一些技術(shù)可以簡(jiǎn)化上面的代碼,其中一種是使用類(lèi)裝飾器:
# 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
另外一種方式是使用元類(lèi):
# 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é)使用了很多高級(jí)技術(shù),包括描述器、混入類(lèi)、super()
的使用、類(lèi)裝飾器和元類(lèi)。不可能在這里一一詳細(xì)展開(kāi)來(lái)講,但是可以在8.9、8.18、9.19小節(jié)找到更多例子。但是,我在這里還是要提一下幾個(gè)需要注意的點(diǎn)。
首先,在 Descriptor
基類(lèi)中你會(huì)看到有個(gè) __set__()
方法,卻沒(méi)有相應(yīng)的 __get__()
方法。如果一個(gè)描述僅僅是從底層實(shí)例字典中獲取某個(gè)屬性值的話(huà),那么沒(méi)必要去定義 __get__()
方法。
所有描述器類(lèi)都是基于混入類(lèi)來(lái)實(shí)現(xiàn)的。比如 Unsigned
和 MaxSized
要跟其他繼承自 Typed
類(lèi)混入。這里利用多繼承來(lái)實(shí)現(xiàn)相應(yīng)的功能。
混入類(lèi)的一個(gè)比較難理解的地方是,調(diào)用 super()
函數(shù)時(shí),你并不知道究竟要調(diào)用哪個(gè)具體類(lèi)。你需要跟其他類(lèi)結(jié)合后才能正確的使用,也就是必須合作才能產(chǎn)生效果。
使用類(lèi)裝飾器和元類(lèi)通常可以簡(jiǎn)化代碼。上面兩個(gè)例子中你會(huì)發(fā)現(xiàn)你只需要輸入一次屬性名即可了。
# Normal
class Point:
x = Integer('x')
y = Integer('y')
# Metaclass
class Point(metaclass=checkedmeta):
x = Integer()
y = Integer()
所有方法中,類(lèi)裝飾器方案應(yīng)該是最靈活和最高明的。首先,它并不依賴(lài)任何其他新的技術(shù),比如元類(lèi)。其次,裝飾器可以很容易的添加或刪除。
最后,裝飾器還能作為混入類(lèi)的替代技術(shù)來(lái)實(shí)現(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
這種方式定義的類(lèi)跟之前的效果一樣,而且執(zhí)行速度會(huì)更快。設(shè)置一個(gè)簡(jiǎn)單的類(lèi)型屬性的值,裝飾器方式要比之前的混入類(lèi)的方式幾乎快100%?,F(xiàn)在你應(yīng)該慶幸自己讀完了本節(jié)全部?jī)?nèi)容了吧?^_^
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話(huà):173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: