Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
P
pytensor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
testgroup
pytensor
Commits
197ad003
提交
197ad003
authored
11月 14, 2008
作者:
Olivier Breuleux
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
documentation for Module, Module and FancyModule are now ComponentDict and Module
上级
8469332e
隐藏空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
256 行增加
和
22 行删除
+256
-22
__init__.py
theano/__init__.py
+3
-1
module.py
theano/compile/module.py
+252
-20
raw_random.py
theano/tensor/raw_random.py
+1
-1
没有找到文件。
theano/__init__.py
浏览文件 @
197ad003
...
...
@@ -44,7 +44,9 @@ from compile import \
predefined_modes
,
predefined_linkers
,
predefined_optimizers
,
\
FunctionMaker
,
function
,
OpFromGraph
,
\
Component
,
External
,
Member
,
KitComponent
,
Method
,
\
Composite
,
ComponentList
,
Module
,
FancyModule
Composite
,
ComponentList
,
ComponentDict
,
Module
FancyModule
=
Module
from
printing
import
\
pprint
,
pp
...
...
theano/compile/module.py
浏览文件 @
197ad003
...
...
@@ -10,11 +10,24 @@ import function_module as F
def
join
(
*
args
):
"""
Creates a string representation for the given names:
join('a', 'b', 'c') => 'a.b.c'
"""
return
"."
.
join
(
arg
for
arg
in
args
if
arg
)
def
split
(
sym
,
n
=-
1
):
"""
Gets the names from their joined representation
split('a.b.c') => 'a', 'b', 'c'
Returns the n first names, if n==-1 returns all of them.
"""
return
sym
.
split
(
'.'
,
n
)
def
canonicalize
(
name
):
"""
Splits the name and converts each name to the
right type (e.g. "2" -> 2)
"""
if
isinstance
(
name
,
str
):
name
=
split
(
name
)
def
convert
(
x
):
...
...
@@ -26,18 +39,44 @@ def canonicalize(name):
class
AllocationError
(
Exception
):
"""
Exception raised when a Result has no associated storage.
"""
pass
class
BindError
(
Exception
):
"""
Exception raised when a Component is already bound and we try to
bound it again.
"""
pass
class
Component
(
object
):
"""
Base class for the various kinds of components which are not
structural but may be meaningfully used in structures (Member,
Method, etc.)
"""
def
__init__
(
self
):
self
.
__dict__
[
'_name'
]
=
''
self
.
__dict__
[
'parent'
]
=
None
def
bind
(
self
,
parent
,
name
,
dup_ok
=
True
):
"""
Marks this component as belonging to the parent (the parent is
typically a Composite instance). The component can be accessed
through the parent with the specified name. If dup_ok is True
and that this Component is already bound, a duplicate of the
component will be made using the dup() method and the
duplicate will be bound instead of this Component. If dup_ok
is False and this Component is already bound, a BindError wil
be raised.
bind() returns the Component instance which has been bound to
the parent. For an unbound instance, this will usually be
self.
"""
if
self
.
bound
():
if
dup_ok
:
try
:
...
...
@@ -54,21 +93,48 @@ class Component(object):
return
self
def
bound
(
self
):
"""
Returns True if this Component instance is bound to a
Composite.
"""
return
self
.
parent
is
not
None
def
allocate
(
self
,
memo
):
"""
Populates the memo dictionary with Result -> Container
pairings.
"""
raise
NotImplementedError
def
build
(
self
,
mode
,
memo
):
"""
Makes an instance of this Component using the mode provided
and taking the containers in the memo dictionary.
A Component which builds nothing may return None.
"""
raise
NotImplementedError
def
make_no_init
(
self
,
mode
=
'FAST_COMPILE'
):
"""
Allocates the necessary containers using allocate() and uses
build() with the provided mode to make an instance which will
be returned. The initialize() method of the instance will not
be called.
"""
memo
=
{}
self
.
allocate
(
memo
)
rval
=
self
.
build
(
mode
,
memo
)
return
rval
def
make
(
self
,
*
args
,
**
kwargs
):
"""
Allocates the necessary containers using allocate() and uses
build() to make an instance which will be returned. The
initialize() method of the instance will be called with the
arguments and the keyword arguments. If 'mode' is in the
keyword arguments it will be passed to build().
"""
mode
=
kwargs
.
pop
(
'mode'
,
'FAST_COMPILE'
)
rval
=
self
.
make_no_init
(
mode
)
if
hasattr
(
rval
,
'initialize'
):
...
...
@@ -82,20 +148,34 @@ class Component(object):
return
self
.
__class__
.
__name__
def
pretty
(
self
,
**
kwargs
):
"""
Returns a pretty representation of this Component, suitable
for reading.
"""
raise
NotImplementedError
def
__get_name__
(
self
):
"""
Getter for self.name
"""
return
self
.
_name
def
__set_name__
(
self
,
name
):
"""
Setter for self.name
"""
self
.
_name
=
name
name
=
property
(
lambda
self
:
self
.
__get_name__
(),
lambda
self
,
value
:
self
.
__set_name__
(
value
))
lambda
self
,
value
:
self
.
__set_name__
(
value
),
"Contains the name of this Component"
)
class
_RComponent
(
Component
):
"""
Base class for a Component wrapping a Result. For internal use.
"""
def
__init__
(
self
,
r
):
super
(
_RComponent
,
self
)
.
__init__
()
...
...
@@ -119,12 +199,19 @@ class _RComponent(Component):
class
External
(
_RComponent
):
"""
External represents a Result which comes from somewhere else
(another module) or is a temporary calculation.
"""
def
allocate
(
self
,
memo
):
# nothing to allocate
return
None
def
build
(
self
,
mode
,
memo
):
"""
Builds nothing.
"""
return
None
def
pretty
(
self
,
**
kwargs
):
...
...
@@ -136,8 +223,19 @@ class External(_RComponent):
class
Member
(
_RComponent
):
"""
Member represents a Result which is a state of a Composite. That
Result will be accessible from a built Composite and it is
possible to do updates on Members.
Member builds a gof.Container.
"""
def
allocate
(
self
,
memo
):
"""
If the memo does not have a Container associated to this
Member's Result, instantiates one and sets it in the memo.
"""
r
=
self
.
r
if
memo
and
r
in
memo
:
return
memo
[
r
]
...
...
@@ -146,6 +244,9 @@ class Member(_RComponent):
return
rval
def
build
(
self
,
mode
,
memo
):
"""
Returns the Container associated to this Member's Result.
"""
return
memo
[
self
.
r
]
...
...
@@ -153,6 +254,20 @@ class Member(_RComponent):
class
Method
(
Component
):
def
__init__
(
self
,
inputs
,
outputs
,
updates
=
{},
kits
=
[],
**
kwupdates
):
"""
Method is a declaration of a function. It contains inputs,
outputs, updates and kits. If the Method is part of a
Composite which holds references to Members, the Method may
use them without declaring them in the inputs, outputs or
updates list.
inputs, outputs or updates may be strings. In that case, they
will be resolved in the Composite which is the parent of this
Method.
Method builds a Function (same structure as a call to
theano.function)
"""
super
(
Method
,
self
)
.
__init__
()
self
.
inputs
=
inputs
self
.
outputs
=
outputs
...
...
@@ -165,6 +280,9 @@ class Method(Component):
return
rval
def
resolve
(
self
,
name
):
"""
Resolves the name of an input or output in the parent.
"""
if
not
self
.
bound
():
raise
ValueError
(
'Trying to resolve a name on an unbound Method.'
)
result
=
self
.
parent
.
resolve
(
name
)
...
...
@@ -175,16 +293,23 @@ class Method(Component):
def
resolve_result
(
self
,
x
):
if
isinstance
(
x
,
gof
.
Result
):
return
x
elif
isinstance
(
x
,
_RComponent
):
return
x
.
r
else
:
return
self
.
resolve
(
x
)
.
r
def
resolve_all
(
self
):
if
not
isinstance
(
self
.
inputs
,
(
list
,
tuple
)):
"""
Resolves all inputs, outputs and updates that were given as
strings so that the fields contain the corresponding Result
instances instead.
"""
if
isinstance
(
self
.
inputs
,
(
gof
.
Result
,
str
)):
inputs
=
[
self
.
inputs
]
else
:
inputs
=
self
.
inputs
inputs
=
list
(
self
.
inputs
)
self
.
inputs
=
[
self
.
resolve_result
(
input
)
for
input
in
inputs
]
if
isinstance
(
self
.
outputs
,
(
list
,
tuple
)):
if
isinstance
(
self
.
outputs
,
(
list
,
tuple
,
ComponentList
)):
self
.
outputs
=
[
self
.
resolve_result
(
output
)
for
output
in
self
.
outputs
]
else
:
self
.
outputs
=
self
.
resolve_result
(
self
.
outputs
)
...
...
@@ -195,11 +320,22 @@ class Method(Component):
self
.
updates
[
k
]
=
v
def
allocate
(
self
,
memo
):
"""
Method allocates nothing.
"""
return
None
def
build
(
self
,
mode
,
memo
,
allocate_all
=
False
):
self
.
resolve_all
()
"""
Produces a function. If allocate_all is True, storage will be
allocated for all needed Results, even if there is no
associated storage for them in the memo. If allocate_all is
False, storage will only be allocated for Results that are
reachable from the inputs list.
"""
self
.
resolve_all
()
# resolve all so we don't have to mess with strings
def
get_storage
(
r
,
require
=
False
):
# If require is True, we can only get storage from the memo.
try
:
return
memo
[
r
]
except
KeyError
:
...
...
@@ -209,11 +345,13 @@ class Method(Component):
' enclosing module or of one of its submodules.'
%
(
r
,
self
))
else
:
return
gof
.
Container
(
r
,
storage
=
[
None
])
# Wrap the inputs in In instances.
inputs
=
self
.
inputs
inputs
=
[
io
.
In
(
result
=
input
,
value
=
get_storage
(
input
),
mutable
=
False
)
for
input
in
inputs
]
# Add the members to update to the inputs.
inputs
+=
[
io
.
In
(
result
=
k
,
update
=
v
,
value
=
get_storage
(
k
,
not
allocate_all
),
...
...
@@ -222,13 +360,20 @@ class Method(Component):
for
k
,
v
in
self
.
updates
.
iteritems
()]
outputs
=
self
.
outputs
_inputs
=
[
x
.
result
for
x
in
inputs
]
# Grab the results that are not accessible from either the inputs or the updates.
for
input
in
gof
.
graph
.
inputs
((
list
(
outputs
)
if
isinstance
(
outputs
,
(
list
,
tuple
))
else
[
outputs
])
+
[
x
.
update
for
x
in
inputs
if
getattr
(
x
,
'update'
,
False
)],
blockers
=
_inputs
):
if
input
not
in
_inputs
and
not
isinstance
(
input
,
gof
.
Value
):
# Add this input to the inputs; we require that storage already exists for them,
# but otherwise they are immutable.
inputs
+=
[
io
.
In
(
result
=
input
,
value
=
get_storage
(
input
,
not
allocate_all
),
mutable
=
False
)]
# Add the kits to the input. The kit should be associated in
# memo to a list of Containers. theano.function handles that
# case by picking only the needed Containers from the list, so
# here we can just delegate to theano.function.
inputs
+=
[(
kit
,
get_storage
(
kit
,
not
allocate_all
))
for
kit
in
self
.
kits
]
return
F
.
function
(
inputs
,
outputs
,
mode
)
...
...
@@ -238,8 +383,10 @@ class Method(Component):
rval
=
'inputs:
%
s
\n
'
%
", "
.
join
(
map
(
str
,
self
.
inputs
))
else
:
rval
=
''
mode
=
kwargs
.
pop
(
'mode'
,
None
)
inputs
,
outputs
,
updates
=
self
.
inputs
,
self
.
outputs
if
isinstance
(
self
.
outputs
,
(
list
,
tuple
))
else
[
self
.
outputs
],
self
.
updates
# If mode is in kwargs, prints the optimized version of the method
mode
=
kwargs
.
pop
(
'mode'
,
None
)
if
mode
:
f
=
self
.
build
(
mode
,
{},
True
)
einputs
,
eoutputs
=
f
.
maker
.
env
.
inputs
,
f
.
maker
.
env
.
outputs
...
...
@@ -282,13 +429,21 @@ class Method(Component):
class
CompositeInstance
(
object
):
"""
Generic type which various Composite subclasses are intended to
build.
"""
def
__init__
(
self
,
component
,
__items__
):
# The Component that built this CompositeInstance
self
.
__dict__
[
'component'
]
=
component
# Some data structure indexable using []
self
.
__dict__
[
'__items__'
]
=
__items__
def
__getitem__
(
self
,
item
):
x
=
self
.
__items__
[
item
]
# For practical reasons, if the item is a Container, we
# return its contents.
if
isinstance
(
x
,
gof
.
Container
):
return
x
.
value
return
x
...
...
@@ -296,14 +451,20 @@ class CompositeInstance(object):
def
__setitem__
(
self
,
item
,
value
):
x
=
self
.
__items__
[
item
]
if
isinstance
(
x
,
gof
.
Container
):
# If the item is a Container, we set its value
x
.
value
=
value
elif
hasattr
(
x
,
'initialize'
):
# If the item has an initialize() method, we use
# it with the value as argument
x
.
initialize
(
value
)
else
:
##self.__items__[item] = value
raise
KeyError
(
'Cannot set item
%
s'
%
item
)
class
Composite
(
Component
):
"""
Composite represents a structure that contains Components.
"""
def
resolve
(
self
,
name
):
raise
NotImplementedError
...
...
@@ -321,6 +482,12 @@ class Composite(Component):
raise
NotImplementedError
def
flat_components
(
self
,
include_self
=
False
):
"""
Generator that yields each component in a flattened hierarchy
of composites and components. If include_self is True, the
list will include the Composite instances, else it will only
yield the list of leaves.
"""
if
include_self
:
yield
self
for
component
in
self
.
components
():
...
...
@@ -331,6 +498,15 @@ class Composite(Component):
yield
component
def
flat_components_map
(
self
,
include_self
=
False
,
path
=
[]):
"""
Generator that yields (path, component) pairs in a flattened
hierarchy of composites and components, where path is a
sequence of keys such that
component is self[path[0]][path[1]]...
If include_self is True, the list will include the Composite
instances, else it will only yield the list of leaves.
"""
if
include_self
:
yield
path
,
self
for
name
,
component
in
self
.
components_map
():
...
...
@@ -342,22 +518,33 @@ class Composite(Component):
yield
path2
,
component
def
allocate
(
self
,
memo
):
"""
Does allocation for each component in the composite.
"""
for
member
in
self
.
components
():
member
.
allocate
(
memo
)
def
get
(
self
,
item
):
"""
Get the Component associated to the key.
"""
raise
NotImplementedError
def
set
(
self
,
item
,
value
):
"""
Set the Component associated to the key.
"""
raise
NotImplementedError
def
__getitem__
(
self
,
item
):
# Uses get() internally
x
=
self
.
get
(
item
)
if
isinstance
(
x
,
(
External
,
Member
)):
return
x
.
r
return
x
def
__setitem__
(
self
,
item
,
value
):
# Uses set() internally
self
.
set
(
item
,
value
)
def
__iter__
(
self
):
...
...
@@ -378,6 +565,10 @@ class ComponentListInstance(CompositeInstance):
self
[
i
]
=
initv
class
ComponentList
(
Composite
):
"""
ComponentList represents a sequence of Component. It builds a
ComponentListInstance.
"""
def
__init__
(
self
,
*
_components
):
super
(
ComponentList
,
self
)
.
__init__
()
...
...
@@ -388,6 +579,9 @@ class ComponentList(Composite):
self
.
append
(
c
)
def
resolve
(
self
,
name
):
# resolves # to the #th number in the list
# resolves name string to parent.resolve(name)
# TODO: eliminate canonicalize
name
=
canonicalize
(
name
)
try
:
item
=
self
.
get
(
name
[
0
])
...
...
@@ -397,6 +591,7 @@ class ComponentList(Composite):
raise
TypeError
(
'Cannot resolve a non-integer name on an unbound ComponentList.'
)
return
self
.
parent
.
resolve
(
name
)
if
len
(
name
)
>
1
:
# TODO: eliminate
return
item
.
resolve
(
name
[
1
:])
return
item
...
...
@@ -469,13 +664,18 @@ class ComponentList(Composite):
return
self
.
__class__
(
*
[
c
.
dup
()
for
c
in
self
.
_components
])
class
ModuleInstance
(
CompositeInstance
):
class
ComponentDictInstance
(
CompositeInstance
):
"""
ComponentDictInstance is meant to be instantiated by ComponentDict.
"""
def
__setitem__
(
self
,
item
,
value
):
if
item
not
in
self
.
__items__
:
# Set it if it's not there
# TODO: is this needed here? move to ModuleInstance?
self
.
__items__
[
item
]
=
value
return
super
(
Module
Instance
,
self
)
.
__setitem__
(
item
,
value
)
super
(
ComponentDict
Instance
,
self
)
.
__setitem__
(
item
,
value
)
def
__str__
(
self
):
strings
=
[]
...
...
@@ -488,11 +688,11 @@ class ModuleInstance(CompositeInstance):
return
'{
%
s}'
%
'
\n
'
.
join
(
strings
)
.
replace
(
'
\n
'
,
'
\n
'
)
class
Module
(
Composite
):
InstanceType
=
ModuleI
nstance
class
ComponentDict
(
Composite
):
InstanceType
=
ComponentDictInstance
# Type used by build() to make the i
nstance
def
__init__
(
self
,
components
=
{},
**
kwcomponents
):
super
(
Module
,
self
)
.
__init__
()
super
(
ComponentDict
,
self
)
.
__init__
()
components
=
dict
(
components
,
**
kwcomponents
)
self
.
__dict__
[
'_components'
]
=
components
...
...
@@ -522,7 +722,7 @@ class Module(Composite):
def
set
(
self
,
item
,
value
):
if
not
isinstance
(
value
,
Component
):
raise
TypeError
(
'
Module
may only contain Components.'
,
value
,
type
(
value
))
raise
TypeError
(
'
ComponentDict
may only contain Components.'
,
value
,
type
(
value
))
value
=
value
.
bind
(
self
,
item
)
self
.
_components
[
item
]
=
value
...
...
@@ -530,7 +730,7 @@ class Module(Composite):
cr
=
'
\n
'
#if header else '\n'
strings
=
[]
# if header:
# rval += "
Module
:"
# rval += "
ComponentDict
:"
for
name
,
component
in
self
.
components_map
():
if
name
.
startswith
(
'_'
):
continue
...
...
@@ -539,10 +739,10 @@ class Module(Composite):
return
'
\n
'
.
join
(
strings
)
def
__str__
(
self
):
return
"
Module
(
%
s)"
%
', '
.
join
(
x
for
x
in
sorted
(
map
(
str
,
self
.
_components
))
if
x
[
0
]
!=
'_'
)
return
"
ComponentDict
(
%
s)"
%
', '
.
join
(
x
for
x
in
sorted
(
map
(
str
,
self
.
_components
))
if
x
[
0
]
!=
'_'
)
def
__set_name__
(
self
,
name
):
super
(
Module
,
self
)
.
__set_name__
(
name
)
super
(
ComponentDict
,
self
)
.
__set_name__
(
name
)
for
mname
,
member
in
self
.
_components
.
iteritems
():
member
.
name
=
'
%
s.
%
s'
%
(
name
,
mname
)
...
...
@@ -556,6 +756,10 @@ def register_wrapper(condition, wrapper):
__autowrappers
.
append
((
condition
,
wrapper
))
def
wrap
(
x
):
"""
Wraps x in a Component. Wrappers can be registered using
register_wrapper to allow wrapping more types.
"""
if
isinstance
(
x
,
Component
):
return
x
for
condition
,
wrapper
in
__autowrappers
:
...
...
@@ -563,12 +767,15 @@ def wrap(x):
return
wrapper
(
x
)
return
x
# Result -> External
register_wrapper
(
lambda
x
:
isinstance
(
x
,
gof
.
Result
),
lambda
x
:
External
(
x
))
# [Component1, Component2, ...] -> ComponentList(Component1, Component2, ...)
register_wrapper
(
lambda
x
:
isinstance
(
x
,
(
list
,
tuple
))
and
all
(
isinstance
(
r
,
Component
)
for
r
in
x
),
lambda
x
:
ComponentList
(
*
x
))
# [Result1, Result2, ...] -> ComponentList(Member(Result1), Member(Result2), ...)
register_wrapper
(
lambda
x
:
isinstance
(
x
,
(
list
,
tuple
))
\
and
all
(
isinstance
(
r
,
gof
.
Result
)
and
not
r
.
owner
for
r
in
x
),
lambda
x
:
ComponentList
(
*
map
(
Member
,
x
)))
...
...
@@ -589,7 +796,14 @@ class Curry:
self
.
meth
=
getattr
(
self
.
obj
,
self
.
name
)
class
FancyModuleInstance
(
ModuleInstance
):
class
ModuleInstance
(
ComponentDictInstance
):
"""
ModuleInstance is meant to be instantiated by Module. This differs
from ComponentDictInstance on a key point, which is that getattr
does a similar thing to getitem.
ModuleInstance is compatible for use as ComponentDict.InstanceType.
"""
def
__getattr__
(
self
,
attr
):
if
attr
==
'__items__'
and
'__items__'
not
in
self
.
__dict__
:
...
...
@@ -605,10 +819,14 @@ class FancyModuleInstance(ModuleInstance):
except
KeyError
:
self
.
__dict__
[
attr
]
=
value
class
FancyModule
(
Module
):
InstanceType
=
Fancy
ModuleInstance
class
Module
(
ComponentDict
):
InstanceType
=
ModuleInstance
# By default, we use build
ModuleInstance
def
__wrapper__
(
self
,
x
):
"""
This function is called whenever x is set as an attribute of
the Module.
"""
return
wrap
(
x
)
def
__getattr__
(
self
,
attr
):
...
...
@@ -619,6 +837,8 @@ class FancyModule(Module):
except
KeyError
:
raise
AttributeError
(
'
%
s has no
%
s attribute.'
%
(
self
.
__class__
,
attr
))
if
isinstance
(
rval
,
(
External
,
Member
)):
# Special treatment for External and Member, so that
# the user may use them to build graphs more easily.
return
rval
.
r
return
rval
...
...
@@ -640,25 +860,37 @@ class FancyModule(Module):
self
.
__dict__
[
attr
]
=
value
def
build
(
self
,
mode
,
memo
):
inst
=
super
(
Fancy
Module
,
self
)
.
build
(
mode
,
memo
)
inst
=
super
(
Module
,
self
)
.
build
(
mode
,
memo
)
for
method
in
dir
(
self
):
# Any method with a name like '_instance_XXX' is added to
# the object built under the name obj.XXX
if
method
.
startswith
(
'_instance_'
):
setattr
(
inst
,
method
[
10
:],
Curry
(
self
,
method
,
inst
))
return
inst
def
_instance_initialize
(
self
,
inst
,
init
=
{},
**
kwinit
):
"""
Default initialization method.
"""
for
name
,
value
in
chain
(
init
.
iteritems
(),
kwinit
.
iteritems
()):
inst
[
name
]
=
value
class
KitComponent
(
Component
):
"""
Represents a SymbolicInputKit (see io.py).
"""
def
__init__
(
self
,
kit
):
super
(
KitComponent
,
self
)
.
__init__
()
self
.
kit
=
kit
def
allocate
(
self
,
memo
):
"""
Allocates a Container for each input in the kit. Sets a key in
the memo that maps the SymbolicInputKit to the list of
Containers.
"""
kit
=
self
.
kit
if
kit
in
memo
:
return
memo
[
kit
]
...
...
theano/tensor/raw_random.py
浏览文件 @
197ad003
...
...
@@ -238,7 +238,7 @@ class RandomKit(SymbolicInputKit):
rk
=
RandomKit
(
'rk'
,
0xBAD5EED
)
class
RModule
(
compile
.
Fancy
Module
):
class
RModule
(
compile
.
Module
):
def
__init__
(
self
,
components
=
{},
**
kwcomponents
):
super
(
RModule
,
self
)
.
__init__
(
components
,
**
kwcomponents
)
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论